Refactor GenerateCheckBox (#6229)

Addresses #5981 and #5983
This commit is contained in:
Jass Bagga 2017-05-05 11:53:53 -07:00 committed by GitHub
parent a80594d706
commit 87bb1d0ff5
4 changed files with 48 additions and 76 deletions

View File

@ -194,8 +194,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
break;
case "checkbox":
GenerateCheckBox(modelExplorer, output);
return;
tagBuilder = GenerateCheckBox(modelExplorer, output);
break;
case "password":
tagBuilder = Generator.GeneratePassword(
@ -252,7 +252,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
return inputTypeHint;
}
private void GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
private TagBuilder GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
{
if (modelExplorer.ModelType == typeof(string))
{
@ -280,53 +280,30 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
"checkbox"));
}
// Prepare to move attributes from current element to <input type="checkbox"/> generated just below.
var htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// Perf: Avoid allocating enumerator
// Construct attributes correctly (first attribute wins).
for (var i = 0; i < output.Attributes.Count; i++)
// hiddenForCheckboxTag always rendered after the returned element
var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
if (hiddenForCheckboxTag != null)
{
var attribute = output.Attributes[i];
if (!htmlAttributes.ContainsKey(attribute.Name))
var renderingMode =
output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag;
hiddenForCheckboxTag.TagRenderMode = renderingMode;
if (ViewContext.FormContext.CanRenderAtEndOfForm)
{
htmlAttributes.Add(attribute.Name, attribute.Value);
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag);
}
else
{
output.PostElement.AppendHtml(hiddenForCheckboxTag);
}
}
var checkBoxTag = Generator.GenerateCheckBox(
return Generator.GenerateCheckBox(
ViewContext,
modelExplorer,
For.Name,
isChecked: null,
htmlAttributes: htmlAttributes);
if (checkBoxTag != null)
{
// Do not generate current element's attributes or tags. Instead put both <input type="checkbox"/> and
// <input type="hidden"/> into the output's Content.
output.Attributes.Clear();
output.TagName = null;
var renderingMode =
output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag;
checkBoxTag.TagRenderMode = renderingMode;
output.Content.AppendHtml(checkBoxTag);
var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
if (hiddenForCheckboxTag != null)
{
hiddenForCheckboxTag.TagRenderMode = renderingMode;
if (ViewContext.FormContext.CanRenderAtEndOfForm)
{
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag);
}
else
{
output.Content.AppendHtml(hiddenForCheckboxTag);
}
}
}
htmlAttributes: null);
}
private TagBuilder GenerateRadio(ModelExplorer modelExplorer)

View File

@ -65,7 +65,7 @@
</div>
<div>
<label class="order" for="HtmlEncode[[NeedSpecialHandle]]">HtmlEncode[[NeedSpecialHandle]]</label>
<input checked="HtmlEncode[[checked]]" data-val="HtmlEncode[[true]]" data-val-required="HtmlEncode[[The NeedSpecialHandle field is required.]]" id="HtmlEncode[[NeedSpecialHandle]]" name="HtmlEncode[[NeedSpecialHandle]]" type="HtmlEncode[[checkbox]]" value="HtmlEncode[[true]]" />
<input type="HtmlEncode[[checkbox]]" checked="HtmlEncode[[checked]]" data-val="HtmlEncode[[true]]" data-val-required="HtmlEncode[[The NeedSpecialHandle field is required.]]" id="HtmlEncode[[NeedSpecialHandle]]" name="HtmlEncode[[NeedSpecialHandle]]" value="HtmlEncode[[true]]" />
</div>
<div>
<label class="order" for="HtmlEncode[[PaymentMethod]]">HtmlEncode[[PaymentMethod]]</label>

View File

@ -65,7 +65,7 @@
</div>
<div>
<label class="order" for="NeedSpecialHandle">NeedSpecialHandle</label>
<input checked="checked" data-val="true" data-val-required="The NeedSpecialHandle field is required." id="NeedSpecialHandle" name="NeedSpecialHandle" type="checkbox" value="true" />
<input type="checkbox" checked="checked" data-val="true" data-val-required="The NeedSpecialHandle field is required." id="NeedSpecialHandle" name="NeedSpecialHandle" value="true" />
</div>
<div>
<label class="order" for="PaymentMethod">PaymentMethod</label>

View File

@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "hello", "world" },
{ "hello", "world2" }
},
"hello=\"HtmlEncode[[world]]\""
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
},
{
new TagHelperAttributeList
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "hello", "world2" },
{ "hello", "world3" }
},
"hello=\"HtmlEncode[[world]]\""
"hello=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
},
{
new TagHelperAttributeList
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "HelLO", "world" },
{ "HELLO", "world2" }
},
"HelLO=\"HtmlEncode[[world]]\""
"HelLO=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\""
},
{
new TagHelperAttributeList
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "HELLO", "world2" },
{ "hello", "world3" }
},
"Hello=\"HtmlEncode[[world]]\""
"Hello=\"HtmlEncode[[world]]\" HELLO=\"HtmlEncode[[world2]]\" hello=\"HtmlEncode[[world3]]\""
},
{
new TagHelperAttributeList
@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "HeLlO", "world" },
{ "hello", "world2" }
},
"HeLlO=\"HtmlEncode[[world]]\""
"HeLlO=\"HtmlEncode[[world]]\" hello=\"HtmlEncode[[world2]]\""
},
};
}
@ -76,15 +76,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
[Theory]
[MemberData(nameof(MultiAttributeCheckBoxData))]
public async Task CheckBoxHandlesMultipleAttributesSameNameCorrectly(
public async Task CheckBoxHandlesMultipleAttributesSameNameArePreserved(
TagHelperAttributeList outputAttributes,
string expectedAttributeString)
{
// Arrange
var originalContent = "original content";
var originalTagName = "not-input";
var expectedContent = $"{originalContent}<input {expectedAttributeString} id=\"HtmlEncode[[IsACar]]\" " +
"name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[checkbox]]\" value=\"HtmlEncode[[true]]\" />" +
var expectedContent = $"<input {expectedAttributeString} type=\"HtmlEncode[[checkbox]]\" id=\"HtmlEncode[[IsACar]]\" " +
$"name=\"HtmlEncode[[IsACar]]\" value=\"HtmlEncode[[true]]\" />" +
"<input name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
var context = new TagHelperContext(
@ -94,12 +93,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
originalTagName,
"input",
outputAttributes,
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
{
TagMode = TagMode.SelfClosing,
};
output.Content.AppendHtml(originalContent);
var htmlGenerator = new TestableHtmlGenerator(new EmptyModelMetadataProvider());
var tagHelper = GetTagHelper(htmlGenerator, model: false, propertyName: nameof(Model.IsACar));
@ -108,10 +108,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
await tagHelper.ProcessAsync(context, output);
// Assert
Assert.Empty(output.Attributes); // Moved to Content and cleared
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Null(output.TagName); // Cleared
Assert.NotNull(output.PostElement);
Assert.Equal(originalContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
}
[Theory]
@ -213,13 +212,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Arrange
const string content = "original content";
const string tagName = "input";
const string isCheckedAttr = " checked=\"HtmlEncode[[checked]]\"";
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]]\" " +
var expectedContent = $"<input class=\"HtmlEncode[[form-control]]\" type=\"HtmlEncode[[checkbox]]\" " +
$"{isChecked}id=\"HtmlEncode[[IsACar]]\" name=\"HtmlEncode[[IsACar]]\" " +
"value=\"HtmlEncode[[true]]\" /><input name=\"HtmlEncode[[IsACar]]\" type=\"HtmlEncode[[hidden]]\" " +
"value=\"HtmlEncode[[false]]\" />";
var expectedPostElement = "<input name=\"IsACar\" type=\"hidden\" value=\"false\" />";
var attributes = new TagHelperAttributeList
{
@ -245,12 +244,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// 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);
Assert.Equal(content, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
}
// Top-level container (List<Model> or Model instance), immediate container type (Model or NestModel),
@ -464,10 +462,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
// Arrange
var originalContent = "original content";
var originalTagName = "not-input";
var expectedPreContent = "original pre-content";
var expectedContent = originalContent + "<input class=\"HtmlEncode[[form-control]]\" /><hidden />";
var expectedContent = "<input class=\"HtmlEncode[[form-control]]\" type=\"HtmlEncode[[checkbox]]\" /><hidden />";
var expectedPostContent = "original post-content";
var expectedPostElement = "<hidden />";
var context = new TagHelperContext(
tagName: "input",
@ -480,7 +478,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{ "class", "form-control" },
};
var output = new TagHelperOutput(
originalTagName,
"input",
originalAttributes,
getChildContentAsync: (useCachedResult, encoder) =>
{
@ -501,10 +499,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var tagBuilder = new TagBuilder("input")
{
Attributes =
{
{ "class", "form-control" },
},
TagRenderMode = TagRenderMode.SelfClosing
};
htmlGenerator
@ -530,12 +524,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// Assert
htmlGenerator.Verify();
Assert.Empty(output.Attributes); // Moved to Content and cleared
Assert.NotEmpty(output.Attributes);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(originalContent, HtmlContentUtilities.HtmlContentToString(output.Content));
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output));
Assert.Equal(expectedPostContent, output.PostContent.GetContent());
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
Assert.Equal(TagMode.SelfClosing, output.TagMode);
Assert.Null(output.TagName); // Cleared
}
[Theory]