[Feature] String should be an acceptable value for a checkbox if it can be parsed as a boolean (#5845)

- `string` should be an acceptable value for a checkbox if it can be parsed as a `bool`
- `throw` with different resources when `ModelType` isn't `bool` or `string` and when `string` value is not acceptable
This commit is contained in:
Nick Chapsas 2017-03-08 22:22:28 +00:00 committed by Doug Bunting
parent d9d280d1ef
commit 015dafc25f
4 changed files with 182 additions and 12 deletions

View File

@ -254,15 +254,30 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
private void GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
{
if (typeof(bool) != modelExplorer.ModelType)
if (modelExplorer.ModelType == typeof(string))
{
if (modelExplorer.Model != null)
{
bool potentialBool;
if (!bool.TryParse(modelExplorer.Model.ToString(), out potentialBool))
{
throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidStringResult(
ForAttributeName,
modelExplorer.Model.ToString(),
typeof(bool).FullName));
}
}
}
else if (modelExplorer.ModelType != typeof(bool))
{
throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult(
"<input>",
ForAttributeName,
modelExplorer.ModelType.FullName,
typeof(bool).FullName,
"type",
"checkbox"));
"<input>",
ForAttributeName,
modelExplorer.ModelType.FullName,
typeof(bool).FullName,
typeof(string).FullName,
"type",
"checkbox"));
}
// Prepare to move attributes from current element to <input type="checkbox"/> generated just below.

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.
/// Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' or '{4}' that can be parsed as a '{3}' if '{5}' is '{6}'.
/// </summary>
internal static string InputTagHelper_InvalidExpressionResult
{
@ -67,11 +67,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
}
/// <summary>
/// Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.
/// Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' or '{4}' that can be parsed as a '{3}' if '{5}' is '{6}'.
/// </summary>
internal static string FormatInputTagHelper_InvalidExpressionResult(object p0, object p1, object p2, object p3, object p4, object p5)
internal static string FormatInputTagHelper_InvalidExpressionResult(object p0, object p1, object p2, object p3, object p4, object p5, object p6)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_InvalidExpressionResult"), p0, p1, p2, p3, p4, p5);
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_InvalidExpressionResult"), p0, p1, p2, p3, p4, p5, p6);
}
/// <summary>
/// Unexpected expression result value '{1}' for {0}. '{1}' cannot be parsed as a '{2}'.
/// </summary>
internal static string InputTagHelper_InvalidStringResult
{
get { return GetString("InputTagHelper_InvalidStringResult"); }
}
/// <summary>
/// Unexpected expression result value '{1}' for {0}. '{1}' cannot be parsed as a '{2}'.
/// </summary>
internal static string FormatInputTagHelper_InvalidStringResult(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_InvalidStringResult"), p0, p1, p2);
}
/// <summary>

View File

@ -127,7 +127,10 @@
<value>Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{7}' or an '{2}', '{3}', '{4}', '{5}', or '{6}' attribute.</value>
</data>
<data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve">
<value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value>
<value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' or '{4}' that can be parsed as a '{3}' if '{5}' is '{6}'.</value>
</data>
<data name="InputTagHelper_InvalidStringResult" xml:space="preserve">
<value>Unexpected expression result value '{1}' for {0}. '{1}' cannot be parsed as a '{2}'.</value>
</data>
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>

View File

@ -113,6 +113,142 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Assert.Null(output.TagName); // Cleared
}
[Theory]
[InlineData("bad")]
[InlineData("notbool")]
public void CheckBoxHandlesNonParsableStringsAsBoolsCorrectly(
string possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string forAttributeName = "asp-for";
var expected = Resources.FormatInputTagHelper_InvalidStringResult(
forAttributeName,
possibleBool,
typeof(bool).FullName);
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => tagHelper.Process(context, output));
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData(10)]
[InlineData(1337)]
public void CheckBoxHandlesInvalidDataTypesCorrectly(
int possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string forAttributeName = "asp-for";
var expected = Resources.FormatInputTagHelper_InvalidExpressionResult(
"<input>",
forAttributeName,
possibleBool.GetType().FullName,
typeof(bool).FullName,
typeof(string).FullName,
"type",
"checkbox");
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => tagHelper.Process(context, output));
Assert.Equal(expected, ex.Message);
}
[Theory]
[InlineData("trUE")]
[InlineData("FAlse")]
public void CheckBoxHandlesParsableStringsAsBoolsCorrectly(
string possibleBool)
{
// Arrange
const string content = "original content";
const string tagName = "input";
const string isCheckedAttr = " checked=\"HtmlEncode[[checked]]\"";
var isChecked = (bool.Parse(possibleBool) ? isCheckedAttr : string.Empty);
var expectedContent = $"{content}<input{isChecked} class=\"HtmlEncode[[form-control]]\" " +
"id=\"HtmlEncode[[IsACar]]\" name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[checkbox]]\" " +
"value=\"HtmlEncode[[true]]\" /><input name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[hidden]]\" " +
"value=\"HtmlEncode[[false]]\" />";
var attributes = new TagHelperAttributeList
{
{ "class", "form-control" },
};
var context = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
tagName,
attributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(content);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: possibleBool, propertyName: nameof(Model.IsACar));
// Act
tagHelper.Process(context, output);
// Assert
Assert.Empty(output.Attributes);
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Null(output.TagName);
}
// Top-level container (List<Model> or Model instance), immediate container type (Model or NestModel),
// model accessor, expression path / id, expected value.
public static TheoryData<object, Type, object, NameAndId, string> TestDataSet