Skip to content

Commit ccde043

Browse files
authored
fix: Account for form attribute on checkbox tag input helper #49201 (#49323)
* fix: account for form attribute on checkbox tag input helper * add xmldoc for FormName * updated Public API specs * formatting
1 parent d62fc37 commit ccde043

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ public InputTagHelper(IHtmlGenerator generator)
116116
[HtmlAttributeName("type")]
117117
public string InputTypeName { get; set; }
118118

119+
/// <summary>
120+
/// The name of the associated form
121+
/// </summary>
122+
/// <remarks>
123+
/// Used to associate a hidden checkbox tag to the respecting form when <see cref="CheckBoxHiddenInputRenderMode"/> is not <see cref="CheckBoxHiddenInputRenderMode.None"/>.
124+
/// </remarks>
125+
[HtmlAttributeName("form")]
126+
public string FormName { get; set; }
127+
119128
/// <summary>
120129
/// The name of the &lt;input&gt; element.
121130
/// </summary>
@@ -161,6 +170,11 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
161170
output.CopyHtmlAttribute(nameof(Value), context);
162171
}
163172

173+
if (FormName != null)
174+
{
175+
output.CopyHtmlAttribute("form", context);
176+
}
177+
164178
// Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient.
165179
// IHtmlGenerator will enforce name requirements.
166180
var metadata = For.Metadata;
@@ -328,6 +342,16 @@ private TagBuilder GenerateCheckBox(
328342
hiddenForCheckboxTag.MergeAttribute("name", Name);
329343
}
330344

345+
if (output.Attributes.TryGetAttribute("form", out var formAttribute))
346+
{
347+
// If the original checkbox has a form attribute, the hidden field should respect it and the
348+
// attribute should be passed on
349+
if (formAttribute.Value is string formAttributeString)
350+
{
351+
hiddenForCheckboxTag.MergeAttribute("form", formAttributeString);
352+
}
353+
}
354+
331355
if (ViewContext.CheckBoxHiddenInputRenderMode == CheckBoxHiddenInputRenderMode.EndOfForm && ViewContext.FormContext.CanRenderAtEndOfForm)
332356
{
333357
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
~Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.FormName.get -> string
3+
~Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper.FormName.set -> void

src/Mvc/Mvc.TagHelpers/test/InputTagHelperTest.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,69 @@ public async Task ProcessAsync_CallsGenerateTextBox_WithExpectedParametersForHid
13191319
Assert.Equal(expectedTagName, output.TagName);
13201320
}
13211321

1322+
[Fact]
1323+
public async Task ProcessAsync_GenerateCheckBox_WithHiddenInputExpectedFormAttribute()
1324+
{
1325+
var propertyName = "-expression-";
1326+
var expectedTagName = "input";
1327+
var formName = "form-name";
1328+
var expectedEndOfFormContent = $"<input form=\"HtmlEncode[[{formName}]]\" name=\"HtmlEncode[[{propertyName}]]\" " +
1329+
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
1330+
var inputTypeName = "checkbox";
1331+
var expectedAttributes = new TagHelperAttributeList
1332+
{
1333+
{ "name", propertyName },
1334+
{ "type", inputTypeName },
1335+
{ "form", formName },
1336+
{ "value", "true" },
1337+
};
1338+
1339+
var metadataProvider = new EmptyModelMetadataProvider();
1340+
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
1341+
var model = false;
1342+
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
1343+
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
1344+
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
1345+
viewContext.FormContext.CanRenderAtEndOfForm = true;
1346+
viewContext.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.EndOfForm;
1347+
1348+
var tagHelper = new InputTagHelper(htmlGenerator)
1349+
{
1350+
For = modelExpression,
1351+
InputTypeName = inputTypeName,
1352+
Name = propertyName,
1353+
FormName = formName,
1354+
ViewContext = viewContext,
1355+
};
1356+
1357+
var attributes = new TagHelperAttributeList
1358+
{
1359+
{ "name", propertyName },
1360+
{ "type", inputTypeName },
1361+
{ "form", formName}
1362+
};
1363+
1364+
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
1365+
var output = new TagHelperOutput(
1366+
expectedTagName,
1367+
new TagHelperAttributeList(),
1368+
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
1369+
{
1370+
TagMode = TagMode.SelfClosing,
1371+
};
1372+
1373+
// Act
1374+
await tagHelper.ProcessAsync(context, output);
1375+
1376+
// Assert
1377+
Assert.Equal(expectedAttributes, output.Attributes);
1378+
Assert.False(output.IsContentModified);
1379+
Assert.Equal(expectedTagName, output.TagName);
1380+
1381+
Assert.Equal(expectedEndOfFormContent, string.Join("", viewContext.FormContext.EndOfFormContent.Select(html => HtmlContentUtilities.HtmlContentToString(html))));
1382+
Assert.True(string.IsNullOrEmpty(HtmlContentUtilities.HtmlContentToString(output.PostElement)));
1383+
}
1384+
13221385
[Theory]
13231386
[InlineData(null, "password", null)]
13241387
[InlineData(null, "Password", "not-null")]

0 commit comments

Comments
 (0)