[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:
parent
d9d280d1ef
commit
015dafc25f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue