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:
Doug Bunting 2015-01-15 16:14:30 -08:00
parent a77d071830
commit d2fe1ebad7
13 changed files with 96 additions and 61 deletions

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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" />

View File

@ -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
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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])
}

View File

@ -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" />

View File

@ -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" />
}

View File

@ -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"/>