Moving the order of generated hidden tags for checkbox to end of the form

- #2994
- Affects both HtmlHelper and TagHelper scenarios
- Checkboxes not enclosed in a form will generate inline hidden tags
- Added necessary properties to FormContext
- Added some functional and unit tests
This commit is contained in:
Ajay Bhargav Baaskaran 2015-10-09 17:28:52 -07:00
parent 03625c38af
commit d40e6612a4
14 changed files with 313 additions and 20 deletions

View File

@ -278,7 +278,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (hiddenForCheckboxTag != null) if (hiddenForCheckboxTag != null)
{ {
hiddenForCheckboxTag.TagRenderMode = renderingMode; hiddenForCheckboxTag.TagRenderMode = renderingMode;
output.Content.Append(hiddenForCheckboxTag);
if (ViewContext.FormContext.CanRenderAtEndOfForm)
{
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag);
}
else
{
output.Content.Append(hiddenForCheckboxTag);
}
} }
} }
} }

View File

@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
/// <summary>
/// <see cref="ITagHelper"/> implementation targeting all form elements
/// to generate content before the form end tag.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[HtmlTargetElement("form")]
public class RenderAtEndOfFormTagHelper : TagHelper
{
public override int Order => -1000;
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
/// <inheritdoc />
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
// Push the new FormContext.
ViewContext.FormContext = new FormContext
{
CanRenderAtEndOfForm = true
};
await context.GetChildContentAsync();
var formContext = ViewContext.FormContext;
if (formContext.HasEndOfFormContent)
{
foreach (var content in formContext.EndOfFormContent)
{
output.PostContent.Append(content);
}
}
// Reset the FormContext
ViewContext.FormContext = null;
}
}
}

View File

@ -3,6 +3,8 @@
using System; using System;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.Extensions.WebEncoders;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Rendering namespace Microsoft.AspNet.Mvc.Rendering
{ {
@ -19,9 +21,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
} }
_viewContext = viewContext; _viewContext = viewContext;
// Push the new FormContext; GenerateEndForm() does the corresponding pop.
_viewContext.FormContext = new FormContext();
} }
public void Dispose() public void Dispose()
@ -40,6 +39,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
protected virtual void GenerateEndForm() protected virtual void GenerateEndForm()
{ {
RenderEndOfFormContent();
_viewContext.Writer.Write("</form>"); _viewContext.Writer.Write("</form>");
_viewContext.FormContext = null; _viewContext.FormContext = null;
} }
@ -52,5 +52,33 @@ namespace Microsoft.AspNet.Mvc.Rendering
GenerateEndForm(); GenerateEndForm();
} }
} }
private void RenderEndOfFormContent()
{
var formContext = _viewContext.FormContext;
if (formContext.HasEndOfFormContent)
{
var writer = _viewContext.Writer;
var htmlWriter = writer as HtmlTextWriter;
IHtmlEncoder htmlEncoder = null;
if (htmlWriter == null)
{
htmlEncoder = _viewContext.HttpContext.RequestServices.GetRequiredService<IHtmlEncoder>();
}
foreach (var content in formContext.EndOfFormContent)
{
if (htmlWriter == null)
{
content.WriteTo(writer, htmlEncoder);
}
else
{
htmlWriter.Write(content);
}
}
}
}
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Html.Abstractions;
namespace Microsoft.AspNet.Mvc.ViewFeatures namespace Microsoft.AspNet.Mvc.ViewFeatures
{ {
@ -11,6 +12,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
private readonly Dictionary<string, bool> _renderedFields = private readonly Dictionary<string, bool> _renderedFields =
new Dictionary<string, bool>(StringComparer.Ordinal); new Dictionary<string, bool>(StringComparer.Ordinal);
private Dictionary<string, object> _formData; private Dictionary<string, object> _formData;
private IList<IHtmlContent> _endOfFormContent;
/// <summary> /// <summary>
/// Property bag for any information you wish to associate with a &lt;form/&gt; in an /// Property bag for any information you wish to associate with a &lt;form/&gt; in an
@ -29,6 +31,25 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
} }
} }
public bool HasFormData => _formData != null;
public bool HasEndOfFormContent => _endOfFormContent != null;
public IList<IHtmlContent> EndOfFormContent
{
get
{
if (_endOfFormContent == null)
{
_endOfFormContent = new List<IHtmlContent>();
}
return _endOfFormContent;
}
}
public bool CanRenderAtEndOfForm { get; set; }
public bool RenderedField(string fieldName) public bool RenderedField(string fieldName)
{ {
if (fieldName == null) if (fieldName == null)

View File

@ -272,12 +272,24 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
FormMethod method, FormMethod method,
object htmlAttributes) object htmlAttributes)
{ {
// Push the new FormContext; MvcForm.GenerateEndForm() does the corresponding pop.
_viewContext.FormContext = new FormContext
{
CanRenderAtEndOfForm = true
};
return GenerateForm(actionName, controllerName, routeValues, method, htmlAttributes); return GenerateForm(actionName, controllerName, routeValues, method, htmlAttributes);
} }
/// <inheritdoc /> /// <inheritdoc />
public MvcForm BeginRouteForm(string routeName, object routeValues, FormMethod method, object htmlAttributes) public MvcForm BeginRouteForm(string routeName, object routeValues, FormMethod method, object htmlAttributes)
{ {
// Push the new FormContext; MvcForm.GenerateEndForm() does the corresponding pop.
_viewContext.FormContext = new FormContext
{
CanRenderAtEndOfForm = true
};
return GenerateRouteForm(routeName, routeValues, method, htmlAttributes); return GenerateRouteForm(routeName, routeValues, method, htmlAttributes);
} }
@ -708,13 +720,24 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
isChecked, isChecked,
htmlAttributes); htmlAttributes);
var hidden = _htmlGenerator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, expression); var hiddenForCheckboxTag = _htmlGenerator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, expression);
if (checkbox == null || hidden == null) if (checkbox == null || hiddenForCheckboxTag == null)
{ {
return HtmlString.Empty; return HtmlString.Empty;
} }
return new BufferedHtmlContent().Append(checkbox).Append(hidden); var checkboxContent = new BufferedHtmlContent().Append(checkbox);
if (ViewContext.FormContext.CanRenderAtEndOfForm)
{
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag);
}
else
{
checkboxContent.Append(hiddenForCheckboxTag);
}
return checkboxContent;
} }
protected virtual string GenerateDisplayName(ModelExplorer modelExplorer, string expression) protected virtual string GenerateDisplayName(ModelExplorer modelExplorer, string expression)

View File

@ -24,7 +24,7 @@ EmployeeName_0</textarea>
</div> </div>
<div> <div>
<label class="employee" for="z0__Remote">Remote</label> <label class="employee" for="z0__Remote">Remote</label>
<input data-val="true" data-val-required="The Remote field is required." id="z0__Remote" name="[0].Remote" type="checkbox" value="true" /><input name="[0].Remote" type="hidden" value="false" /> <input data-val="true" data-val-required="The Remote field is required." id="z0__Remote" name="[0].Remote" type="checkbox" value="true" />
</div> </div>
<div> <div>
<label class="employee" for="z0__OfficeNumber">OfficeNumber</label> <label class="employee" for="z0__OfficeNumber">OfficeNumber</label>
@ -55,7 +55,7 @@ EmployeeName_1</textarea>
</div> </div>
<div> <div>
<label class="employee" for="z1__Remote">Remote</label> <label class="employee" for="z1__Remote">Remote</label>
<input data-val="true" data-val-required="The Remote field is required." id="z1__Remote" name="[1].Remote" type="checkbox" value="true" /><input name="[1].Remote" type="hidden" value="false" /> <input data-val="true" data-val-required="The Remote field is required." id="z1__Remote" name="[1].Remote" type="checkbox" value="true" />
</div> </div>
<div> <div>
<label class="employee" for="z1__OfficeNumber">OfficeNumber</label> <label class="employee" for="z1__OfficeNumber">OfficeNumber</label>
@ -86,7 +86,7 @@ EmployeeName_2</textarea>
</div> </div>
<div> <div>
<label class="employee" for="z2__Remote">Remote</label> <label class="employee" for="z2__Remote">Remote</label>
<input checked="checked" data-val="true" data-val-required="The Remote field is required." id="z2__Remote" name="[2].Remote" type="checkbox" value="true" /><input name="[2].Remote" type="hidden" value="false" /> <input checked="checked" data-val="true" data-val-required="The Remote field is required." id="z2__Remote" name="[2].Remote" type="checkbox" value="true" />
</div> </div>
<div> <div>
<label class="employee" for="z2__OfficeNumber">OfficeNumber</label> <label class="employee" for="z2__OfficeNumber">OfficeNumber</label>
@ -94,5 +94,5 @@ EmployeeName_2</textarea>
<option>1002</option> <option>1002</option>
</select> </select>
</div> <input type="submit" /> </div> <input type="submit" />
</form></body> <input name="[0].Remote" type="hidden" value="false" /><input name="[1].Remote" type="hidden" value="false" /><input name="[2].Remote" type="hidden" value="false" /></form></body>
</html> </html>

View File

@ -33,7 +33,7 @@
</div> </div>
<div> <div>
<label class="order" for="HtmlEncode[[NeedSpecialHandle]]">HtmlEncode[[NeedSpecialHandle]]</label> <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 name="HtmlEncode[[NeedSpecialHandle]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[false]]" /> <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]]" />
</div> </div>
<div> <div>
<label class="order" for="HtmlEncode[[PaymentMethod]]">HtmlEncode[[PaymentMethod]]</label> <label class="order" for="HtmlEncode[[PaymentMethod]]">HtmlEncode[[PaymentMethod]]</label>
@ -75,6 +75,6 @@
</ul></div> </ul></div>
<input type="HtmlEncode[[hidden]]" id="HtmlEncode[[Customer_Key]]" name="HtmlEncode[[Customer.Key]]" value="HtmlEncode[[KeyA]]" /> <input type="HtmlEncode[[hidden]]" id="HtmlEncode[[Customer_Key]]" name="HtmlEncode[[Customer.Key]]" value="HtmlEncode[[KeyA]]" />
<input type="submit" /> <input type="submit" />
<input name="HtmlEncode[[__RequestVerificationToken]]" type="HtmlEncode[[hidden]]" value="{0}" /></form> <input name="HtmlEncode[[__RequestVerificationToken]]" type="HtmlEncode[[hidden]]" value="{0}" /><input name="HtmlEncode[[NeedSpecialHandle]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[false]]" /></form>
</body> </body>
</html> </html>

View File

@ -33,7 +33,7 @@
</div> </div>
<div> <div>
<label class="order" for="NeedSpecialHandle">NeedSpecialHandle</label> <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 name="NeedSpecialHandle" type="hidden" value="false" /> <input checked="checked" data-val="true" data-val-required="The NeedSpecialHandle field is required." id="NeedSpecialHandle" name="NeedSpecialHandle" type="checkbox" value="true" />
</div> </div>
<div> <div>
<label class="order" for="PaymentMethod">PaymentMethod</label> <label class="order" for="PaymentMethod">PaymentMethod</label>
@ -75,6 +75,6 @@
</ul></div> </ul></div>
<input type="hidden" id="Customer_Key" name="Customer.Key" value="KeyA" /> <input type="hidden" id="Customer_Key" name="Customer.Key" value="KeyA" />
<input type="submit" /> <input type="submit" />
<input name="__RequestVerificationToken" type="hidden" value="{0}" /></form> <input name="__RequestVerificationToken" type="hidden" value="{0}" /><input name="NeedSpecialHandle" type="hidden" value="false" /></form>
</body> </body>
</html> </html>

View File

@ -33,7 +33,7 @@
</div> </div>
<div> <div>
<label class="HtmlEncode[[order]]" for="HtmlEncode[[NeedSpecialHandle]]">HtmlEncode[[NeedSpecialHandle]]</label> <label class="HtmlEncode[[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 name="HtmlEncode[[NeedSpecialHandle]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[false]]" /> <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]]" />
</div> </div>
<div> <div>
<label class="HtmlEncode[[order]]" for="HtmlEncode[[PaymentMethod]]">HtmlEncode[[PaymentMethod]]</label> <label class="HtmlEncode[[order]]" for="HtmlEncode[[PaymentMethod]]">HtmlEncode[[PaymentMethod]]</label>
@ -74,5 +74,5 @@
</ul></div> </ul></div>
<input id="HtmlEncode[[Customer_Key]]" name="HtmlEncode[[Customer.Key]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[KeyA]]" /> <input id="HtmlEncode[[Customer_Key]]" name="HtmlEncode[[Customer.Key]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[KeyA]]" />
<input type="submit"/> <input type="submit"/>
<input name="HtmlEncode[[__RequestVerificationToken]]" type="HtmlEncode[[hidden]]" value="{0}" /></form></body> <input name="HtmlEncode[[__RequestVerificationToken]]" type="HtmlEncode[[hidden]]" value="{0}" /><input name="HtmlEncode[[NeedSpecialHandle]]" type="HtmlEncode[[hidden]]" value="HtmlEncode[[false]]" /></form></body>
</html> </html>

View File

@ -33,7 +33,7 @@
</div> </div>
<div> <div>
<label class="order" for="NeedSpecialHandle">NeedSpecialHandle</label> <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 name="NeedSpecialHandle" type="hidden" value="false" /> <input checked="checked" data-val="true" data-val-required="The NeedSpecialHandle field is required." id="NeedSpecialHandle" name="NeedSpecialHandle" type="checkbox" value="true" />
</div> </div>
<div> <div>
<label class="order" for="PaymentMethod">PaymentMethod</label> <label class="order" for="PaymentMethod">PaymentMethod</label>
@ -74,5 +74,5 @@
</ul></div> </ul></div>
<input id="Customer_Key" name="Customer.Key" type="hidden" value="KeyA" /> <input id="Customer_Key" name="Customer.Key" type="hidden" value="KeyA" />
<input type="submit"/> <input type="submit"/>
<input name="__RequestVerificationToken" type="hidden" value="{0}" /></form></body> <input name="__RequestVerificationToken" type="hidden" value="{0}" /><input name="NeedSpecialHandle" type="hidden" value="false" /></form></body>
</html> </html>

View File

@ -0,0 +1,90 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Xunit;
namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class RenderAtEndOfFormTagHelperTest
{
public static TheoryData RenderAtEndOfFormTagHelperData
{
get
{
// tagBuilderList, expectedOutput
return new TheoryData<List<TagBuilder>, string>
{
{
new List<TagBuilder>
{
GetTagBuilder("input", "SomeName", "hidden", "false", TagRenderMode.SelfClosing)
},
@"<input name=""SomeName"" type=""hidden"" value=""false"" />"
},
{
new List<TagBuilder>
{
GetTagBuilder("input", "SomeName", "hidden", "false", TagRenderMode.SelfClosing),
GetTagBuilder("input", "SomeOtherName", "hidden", "false", TagRenderMode.SelfClosing)
},
@"<input name=""SomeName"" type=""hidden"" value=""false"" />" +
@"<input name=""SomeOtherName"" type=""hidden"" value=""false"" />"
}
};
}
}
[Theory]
[MemberData(nameof(RenderAtEndOfFormTagHelperData))]
public async Task Process_AddsHiddenInputTag_FromEndOfFormContent(List<TagBuilder> tagBuilderList, string expectedOutput)
{
// Arrange
var viewContext = new ViewContext();
var tagHelperOutput = new TagHelperOutput(
tagName: "form",
attributes: new TagHelperAttributeList());
var tagHelperContext = new TagHelperContext(
Enumerable.Empty<IReadOnlyTagHelperAttribute>(),
new Dictionary<object, object>(),
"someId",
(useCachedResult) =>
{
Assert.True(viewContext.FormContext.CanRenderAtEndOfForm);
foreach (var item in tagBuilderList)
{
viewContext.FormContext.EndOfFormContent.Add(item);
}
return Task.FromResult<TagHelperContent>(new DefaultTagHelperContent());
});
var tagHelper = new RenderAtEndOfFormTagHelper
{
ViewContext = viewContext
};
// Act
await tagHelper.ProcessAsync(context: tagHelperContext, output: tagHelperOutput);
// Assert
Assert.Equal(expectedOutput, tagHelperOutput.PostContent.GetContent());
}
private static TagBuilder GetTagBuilder(string tag, string name, string type, string value, TagRenderMode mode)
{
var tagBuilder = new TagBuilder(tag);
tagBuilder.MergeAttribute("name", name);
tagBuilder.MergeAttribute("type", type);
tagBuilder.MergeAttribute("value", value);
tagBuilder.TagRenderMode = mode;
return tagBuilder;
}
}
}

View File

@ -5,11 +5,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.IO;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.Rendering namespace Microsoft.AspNet.Mvc.Rendering
@ -127,6 +128,32 @@ namespace Microsoft.AspNet.Mvc.Rendering
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html)); Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html));
} }
[Fact]
public void CheckBox_WithCanRenderAtEndOfFormSet_DoesNotGenerateInlineHiddenTag()
{
// Arrange
// Mono issue - https://github.com/aspnet/External/issues/19
var expected = PlatformNormalizer.NormalizeContent(
@"<input checked=""HtmlEncode[[checked]]"" data-val=""HtmlEncode[[true]]"" " +
@"data-val-required=""HtmlEncode[[The Boolean field is required.]]"" id=""HtmlEncode[[Property1]]"" " +
@"name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[checkbox]]"" " +
@"value=""HtmlEncode[[true]]"" />");
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData());
helper.ViewContext.FormContext.CanRenderAtEndOfForm = true;
// Act
var html = helper.CheckBox("Property1", isChecked: true, htmlAttributes: null);
// Assert
Assert.True(helper.ViewContext.FormContext.HasEndOfFormContent);
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html));
var writer = new StringWriter();
var hiddenTag = Assert.Single(helper.ViewContext.FormContext.EndOfFormContent);
hiddenTag.WriteTo(writer, new CommonTestEncoder());
Assert.Equal("<input name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />",
writer.ToString());
}
[Fact] [Fact]
public void CheckBoxUsesAttemptedValueFromModelState() public void CheckBoxUsesAttemptedValueFromModelState()
{ {

View File

@ -2,12 +2,15 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if MOCK_SUPPORT #if MOCK_SUPPORT
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Mvc.ViewFeatures; using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.Extensions.WebEncoders;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -318,6 +321,37 @@ namespace Microsoft.AspNet.Mvc.Rendering
urlHelper.Verify(); urlHelper.Verify();
} }
[Fact]
public void EndForm_RendersHiddenTagForCheckBox()
{
// Arrange
var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper();
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(s => s.GetService(typeof(IHtmlEncoder))).Returns(new CommonTestEncoder());
var viewContext = htmlHelper.ViewContext;
viewContext.HttpContext.RequestServices = serviceProvider.Object;
var writer = viewContext.Writer as StringWriter;
Assert.NotNull(writer);
var builder = writer.GetStringBuilder();
var tagBuilder = new TagBuilder("input");
tagBuilder.MergeAttribute("name", "SomeName");
tagBuilder.MergeAttribute("type", "hidden");
tagBuilder.MergeAttribute("value", "false");
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
htmlHelper.ViewContext.FormContext.EndOfFormContent.Add(tagBuilder);
// Act
htmlHelper.EndForm();
// Assert
Assert.Equal(
"<input name=\"HtmlEncode[[SomeName]]\" type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" /></form>",
builder.ToString());
}
private string GetHtmlAttributesAsString(object htmlAttributes) private string GetHtmlAttributesAsString(object htmlAttributes)
{ {
var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

View File

@ -11,6 +11,7 @@
@addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNet.Mvc.TagHelpers" @addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
@addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNet.Mvc.TagHelpers" @addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
@addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationSummaryTagHelper, Microsoft.AspNet.Mvc.TagHelpers" @addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationSummaryTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
@addTagHelper "Microsoft.AspNet.Mvc.TagHelpers.RenderAtEndOfFormTagHelper, Microsoft.AspNet.Mvc.TagHelpers"
<html> <html>
<head> <head>