Allow `@Model` in bound tag helper attribute value
- part II of #1253 - an expected case in template .cshtml files - expression has name `""`; led to `ArgumentException` in `ModelExpression` - test `@Model` and `@model.Property` in unit and functional tests - update baselines to match nits: - remove a few unecessary `@`s in .cshtml files - correct field names & ids in ProductList.cshtml (`foreach` confuses MVC) - led to correct valiation attributes as well
This commit is contained in:
parent
a77d071830
commit
d2fe1ebad7
|
|
@ -21,13 +21,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <param name="metadata">
|
||||
/// Metadata about the <see cref="System.Linq.Expressions.Expression"/> of interest.
|
||||
/// </param>
|
||||
public ModelExpression(string name, [NotNull] ModelMetadata metadata)
|
||||
public ModelExpression([NotNull] string name, [NotNull] ModelMetadata metadata)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name));
|
||||
}
|
||||
|
||||
Name = name;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
<div>
|
||||
<label class="order" for="Customer_Gender">Gender</label>
|
||||
<input type="radio" value="Male" id="Customer_Gender" name="Customer.Gender" /> Male
|
||||
<input type="radio" value="Female" checked="checked" id="Customer_Gender" name="Customer.Gender" /> Female
|
||||
<input type="radio" value="Female" checked="checked" id="Customer_Gender" name="Customer.Gender" /> Female
|
||||
<span class="field-validation-valid" data-valmsg-for="Customer.Gender" data-valmsg-replace="true"></span>
|
||||
</div>
|
||||
<div class="order validation-summary-valid" data-valmsg-summary="true"><ul><li style="display:none"></li>
|
||||
|
|
|
|||
|
|
@ -4,56 +4,56 @@
|
|||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<form action="/MvcTagHelper_Product" method="post">
|
||||
<form action="/MvcTagHelper_Product" method="post"> <div>
|
||||
<label class="product" for="z0__HomePage">HomePage</label>
|
||||
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z0__HomePage" name="[0].HomePage" value="http://www.contoso.com/" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="product" for="HomePage">HomePage</label>
|
||||
<input size="50" type="url" id="HomePage" name="HomePage" value="http://www.contoso.com/" />
|
||||
<label class="product" for="z0__Number">Number</label>
|
||||
<input type="number" id="z0__Number" name="[0].Number" value="0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Number">Number</label>
|
||||
<input type="number" id="Number" name="Number" value="0" />
|
||||
<label class="product" for="z0__ProductName">ProductName</label>
|
||||
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z0__ProductName" name="[0].ProductName" value="Product_0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="ProductName">ProductName</label>
|
||||
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="ProductName" name="ProductName" value="Product_0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="Description" name="Description">
|
||||
<label class="product" for="z0__Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="z0__Description" name="[0].Description">
|
||||
</textarea>
|
||||
</div> <div>
|
||||
<label class="product" for="z1__HomePage">HomePage</label>
|
||||
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z1__HomePage" name="[1].HomePage" value="" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="product" for="z1__Number">Number</label>
|
||||
<input type="number" id="z1__Number" name="[1].Number" value="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="HomePage">HomePage</label>
|
||||
<input size="50" type="url" id="HomePage" name="HomePage" value="" />
|
||||
<label class="product" for="z1__ProductName">ProductName</label>
|
||||
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z1__ProductName" name="[1].ProductName" value="Product_1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Number">Number</label>
|
||||
<input type="number" id="Number" name="Number" value="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="ProductName">ProductName</label>
|
||||
<input type="text" id="ProductName" name="ProductName" value="Product_1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="Description" name="Description">
|
||||
<label class="product" for="z1__Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="z1__Description" name="[1].Description">
|
||||
</textarea>
|
||||
</div> <div>
|
||||
<label class="product" for="z2__HomePage">HomePage</label>
|
||||
<input size="50" disabled="disabled" readonly="readonly" type="url" id="z2__HomePage" name="[2].HomePage" value="" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="product" for="z2__Number">Number</label>
|
||||
<input type="number" id="z2__Number" name="[2].Number" value="2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="HomePage">HomePage</label>
|
||||
<input size="50" type="url" id="HomePage" name="HomePage" value="" />
|
||||
<label class="product" for="z2__ProductName">ProductName</label>
|
||||
<input type="text" data-val="true" data-val-required="The ProductName field is required." id="z2__ProductName" name="[2].ProductName" value="Product_2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Number">Number</label>
|
||||
<input type="number" id="Number" name="Number" value="2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="ProductName">ProductName</label>
|
||||
<input type="text" id="ProductName" name="ProductName" value="Product_2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="product" for="Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="Description" name="Description">
|
||||
<label class="product" for="z2__Description">Description</label>
|
||||
<textarea rows="4" cols="50" class="product" id="z2__Description" name="[2].Description">
|
||||
Product_2 desription</textarea>
|
||||
</div> <input type="submit"></input>
|
||||
</form></body>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
generatedAbsoluteIndex: 2105,
|
||||
generatedLineIndex: 53,
|
||||
generatedCharacterIndex: 95,
|
||||
contentLength: 3)
|
||||
contentLength: 3),
|
||||
BuildLineMapping(
|
||||
documentAbsoluteIndex: 166,
|
||||
documentLineIndex: 5,
|
||||
documentCharacterIndex: 18,
|
||||
generatedAbsoluteIndex: 2418,
|
||||
generatedLineIndex: 59,
|
||||
generatedCharacterIndex: 87,
|
||||
contentLength: 5),
|
||||
};
|
||||
|
||||
// Act and Assert
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
@addtaghelper "Microsoft.AspNet.Mvc.Razor.InputTestTagHelper, Microsoft.AspNet.Mvc.Razor.Host.Test"
|
||||
|
||||
<input-test for="Now" />
|
||||
<input-test for="Now" />
|
||||
<input-test for="@Model" />
|
||||
|
|
@ -53,6 +53,12 @@
|
|||
#line 5 "TestFiles/Input/ModelExpressionTagHelper.cshtml"
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => __model.Now);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper<Microsoft.AspNet.Mvc.Razor.InputTestTagHelper>();
|
||||
#line 6 "TestFiles/Input/ModelExpressionTagHelper.cshtml"
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => Model);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#pragma checksum "TestFiles/Input/ModelExpressionTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "5de24be79bca2d0ce7e180eab7b197442352f137"
|
||||
#pragma checksum "TestFiles/Input/ModelExpressionTagHelper.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "f068e0ad45f53d090f04213f542f87fb2cf750d2"
|
||||
namespace Asp
|
||||
{
|
||||
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
|
||||
|
|
@ -51,6 +51,22 @@ namespace Asp
|
|||
#line 5 "TestFiles/Input/ModelExpressionTagHelper.cshtml"
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => __model.Now);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For);
|
||||
__tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result;
|
||||
WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag());
|
||||
WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag());
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.End();
|
||||
BeginContext(146, 2, true);
|
||||
WriteLiteral("\r\n");
|
||||
EndContext();
|
||||
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input-test", "test");
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper<Microsoft.AspNet.Mvc.Razor.InputTestTagHelper>();
|
||||
__tagHelperExecutionContext.Add(__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper);
|
||||
#line 6 "TestFiles/Input/ModelExpressionTagHelper.cshtml"
|
||||
__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => Model);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
__tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For);
|
||||
|
|
|
|||
|
|
@ -315,24 +315,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test");
|
||||
var output = new TagHelperOutput(expectedTagName, originalAttributes, content);
|
||||
|
||||
// TODO: https://github.com/aspnet/Mvc/issues/1253
|
||||
// In real (model => model) scenario, ModelExpression should have name "" and
|
||||
// TemplateInfo.HtmlFieldPrefix should be "Property1" but empty ModelExpression name is not currently
|
||||
// supported, see also #1408.
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
string model = null;
|
||||
var metadata = metadataProvider.GetMetadataForType(() => model, typeof(string));
|
||||
var modelExpression = new ModelExpression(propertyName, metadata);
|
||||
|
||||
var htmlGenerator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator.Object, metadataProvider);
|
||||
|
||||
// Simulate a (model => model) scenario. E.g. the calling helper may appear in a low-level template.
|
||||
var modelExpression = new ModelExpression(string.Empty, metadata);
|
||||
viewContext.ViewData.TemplateInfo.HtmlFieldPrefix = propertyName;
|
||||
|
||||
ICollection<string> selectedValues = new string[0];
|
||||
htmlGenerator
|
||||
.Setup(real => real.GenerateSelect(
|
||||
viewContext,
|
||||
metadata,
|
||||
null, // optionLabel
|
||||
propertyName, // name
|
||||
string.Empty, // name
|
||||
expectedItems,
|
||||
expectedAllowMultiple,
|
||||
null, // htmlAttributes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
@using MvcTagHelpersWebSite.Models
|
||||
@model Gender
|
||||
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
|
||||
<input asp-for="@Model" type="radio" value="Male" /> Male
|
||||
<input asp-for="@Model" type="radio" value="Female" /> Female
|
||||
|
|
@ -10,9 +10,9 @@
|
|||
<body>
|
||||
@if (Model != null && Model.Count() != 0)
|
||||
{
|
||||
@using (@Html.BeginForm("EmployeeList", "MvcTagHelper_Home", FormMethod.Post))
|
||||
using (@Html.BeginForm("EmployeeList", "MvcTagHelper_Home", FormMethod.Post))
|
||||
{
|
||||
@for (int i = 0; i < Model.Count; ++i)
|
||||
for (int i = 0; i < Model.Count; ++i)
|
||||
{
|
||||
@Html.EditorFor(m => m[i])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<label asp-for="Customer.Gender" class="order" />
|
||||
<input asp-for="Customer.Gender" type="radio" value="Male" /> Male
|
||||
<input asp-for="Customer.Gender" type="radio" value="Female" /> Female
|
||||
@Html.EditorFor(model => model.Customer.Gender)
|
||||
<span asp-validation-for="Customer.Gender" />
|
||||
</div>
|
||||
<div asp-validation-summary="All" class="order" />
|
||||
|
|
|
|||
|
|
@ -10,9 +10,18 @@
|
|||
<body>
|
||||
@using (@Html.BeginForm("Index", "MvcTagHelper_Product", FormMethod.Post))
|
||||
{
|
||||
@foreach (var product in Model)
|
||||
var index = 0;
|
||||
var fieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;
|
||||
foreach (var model in Model)
|
||||
{
|
||||
@await Html.PartialAsync("_ProductPartial", product)
|
||||
@* Update HtmlFieldPrefix so generated for, id and name attribute values are correct. *@
|
||||
ViewData.TemplateInfo.HtmlFieldPrefix = fieldPrefix + string.Format("[{0}]", index++);
|
||||
<div>
|
||||
<label asp-for="@(model.HomePage)" class="product" />
|
||||
<input asp-for="@(model.HomePage)" type="url" size="50" disabled="disabled" readonly="readonly" />
|
||||
</div>
|
||||
@await Html.PartialAsync("_ProductPartial", model)
|
||||
ViewData.TemplateInfo.HtmlFieldPrefix = fieldPrefix;
|
||||
}
|
||||
<input type="submit" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers"
|
||||
|
||||
<div>
|
||||
<label asp-for="HomePage" class="product" />
|
||||
<input asp-for="HomePage" type="url" size="50" />
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Number" class="product" />
|
||||
<input asp-for="Number" type="number"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue