Make `string`-based expression evaluations behave consistently
- #1485, #1487 - handle `TemplateInfo.HtmlFieldPrefix` in `ViewDataEvaluator.Eval()` - attempt lookup in the `ViewDataDictionary` using full name then evaluate relative `expression` against `viewData.Model` - handle `null` or empty `expression` special case in this method (remove `throw`s) - always pass relative `expression` name into `Eval()` - remove `null` or empty `expression` handling from higher-level code - in a couple of cases, special-case returned `ViewDataInfo` - #2662 - remove incorrect guard from `DefaultHtmlGenerator.GenerateRadioButtion()` - add doc comments for the core methods that have changed - enable unit tests skipped due to one of above bugs - fix one (yeah, just one) other test with incorrect expectations - remove functional test comments about the above bugs and update expectations nits: - move some comments describing `ViewDataEvaluator` methods above the methods
This commit is contained in:
parent
8b5931d758
commit
27283ec098
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
ModelMetadata metadata;
|
||||
if (propertyName == null)
|
||||
{
|
||||
// Ex:
|
||||
// Ex:
|
||||
// m => 5 (arbitrary expression)
|
||||
// m => foo (arbitrary expression)
|
||||
// m => m.Widgets[0] (expression ending with non-property-access)
|
||||
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
}
|
||||
else
|
||||
{
|
||||
// Ex:
|
||||
// Ex:
|
||||
// m => m.Color (simple property access)
|
||||
// m => m.Color.Red (nested property access)
|
||||
// m => m.Widgets[0].Size (expression ending with property-access)
|
||||
|
|
@ -89,22 +89,27 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
return viewData.ModelExplorer.GetExplorerForExpression(metadata, modelAccessor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="ModelExplorer"/> for named <paramref name="expression"/> in given
|
||||
/// <paramref name="viewData"/>.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression name, relative to <c>viewData.Model</c>.</param>
|
||||
/// <param name="viewData">
|
||||
/// The <see cref="ViewDataDictionary"/> that may contain the <paramref name="expression"/> value.
|
||||
/// </param>
|
||||
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <returns>
|
||||
/// <see cref="ModelExplorer"/> for named <paramref name="expression"/> in given <paramref name="viewData"/>.
|
||||
/// </returns>
|
||||
public static ModelExplorer FromStringExpression(
|
||||
string expression,
|
||||
[NotNull] ViewDataDictionary viewData,
|
||||
IModelMetadataProvider metadataProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
// Empty string really means "ModelMetadata for the current model".
|
||||
return FromModel(viewData, metadataProvider);
|
||||
}
|
||||
|
||||
var viewDataInfo = ViewDataEvaluator.Eval(viewData, expression);
|
||||
|
||||
if (viewDataInfo == null)
|
||||
{
|
||||
// Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
|
||||
// Try getting a property from ModelMetadata if we couldn't find an answer in ViewData
|
||||
var propertyExplorer = viewData.ModelExplorer.GetExplorerForProperty(expression);
|
||||
if (propertyExplorer != null)
|
||||
{
|
||||
|
|
@ -114,11 +119,20 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
|
||||
if (viewDataInfo != null)
|
||||
{
|
||||
if (viewDataInfo.Container == viewData &&
|
||||
viewDataInfo.Value == viewData.Model &&
|
||||
string.IsNullOrEmpty(expression))
|
||||
{
|
||||
// Nothing for empty expression in ViewData and ViewDataEvaluator just returned the model. Handle
|
||||
// using FromModel() for its object special case.
|
||||
return FromModel(viewData, metadataProvider);
|
||||
}
|
||||
|
||||
ModelExplorer containerExplorer = viewData.ModelExplorer;
|
||||
if (viewDataInfo.Container != null)
|
||||
{
|
||||
containerExplorer = metadataProvider.GetModelExplorerForType(
|
||||
viewDataInfo.Container.GetType(),
|
||||
viewDataInfo.Container.GetType(),
|
||||
viewDataInfo.Container);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Extensions;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
||||
{
|
||||
public static class ViewDataEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in given
|
||||
/// <paramref name="viewData"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewData">
|
||||
/// The <see cref="ViewDataDictionary"/> that may contain the <paramref name="expression"/> value.
|
||||
/// </param>
|
||||
/// <param name="expression">Expression name, relative to <c>viewData.Model</c>.</param>
|
||||
/// <returns>
|
||||
/// <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in given <paramref name="viewData"/>.
|
||||
/// </returns>
|
||||
public static ViewDataInfo Eval([NotNull] ViewDataDictionary viewData, string expression)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(expression));
|
||||
}
|
||||
// While it is not valid to generate a field for the top-level model itself because the result is an
|
||||
// unnamed input element, do not throw here if full name is null or empty. Support is needed for cases
|
||||
// such as Html.Label() and Html.Value(), where the user's code is not creating a name attribute. Checks
|
||||
// are in place at higher levels for the invalid cases.
|
||||
var fullName = viewData.TemplateInfo.GetFullHtmlFieldName(expression);
|
||||
|
||||
// Given an expression "one.two.three.four" we look up the following (pseudocode):
|
||||
// Given an expression "one.two.three.four" we look up the following (pseudo-code):
|
||||
// this["one.two.three.four"]
|
||||
// this["one.two.three"]["four"]
|
||||
// this["one.two"]["three.four]
|
||||
|
|
@ -28,21 +38,60 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
// this["one"]["two"]["three.four"]
|
||||
// this["one"]["two"]["three"]["four"]
|
||||
|
||||
return EvalComplexExpression(viewData, expression);
|
||||
}
|
||||
|
||||
public static ViewDataInfo Eval(object indexableObject, string expression)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
// Try to find a matching ViewData entry using the full expression name. If that fails, fall back to
|
||||
// ViewData.Model using the expression's relative name.
|
||||
var result = EvalComplexExpression(viewData, fullName);
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(expression));
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
// Null or empty expression name means current model even if that model is null.
|
||||
result = new ViewDataInfo(container: viewData, value: viewData.Model);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = EvalComplexExpression(viewData.Model, expression);
|
||||
}
|
||||
}
|
||||
|
||||
// Run through same cases as other Eval() overload but allow a null container.
|
||||
return (indexableObject == null) ? null : EvalComplexExpression(indexableObject, expression);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in given
|
||||
/// <paramref name="indexableObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="indexableObject">
|
||||
/// The <see cref="object"/> that may contain the <paramref name="expression"/> value.
|
||||
/// </param>
|
||||
/// <param name="expression">Expression name, relative to <paramref name="indexableObject"/>.</param>
|
||||
/// <returns>
|
||||
/// <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in given
|
||||
/// <paramref name="indexableObject"/>.
|
||||
/// </returns>
|
||||
public static ViewDataInfo Eval(object indexableObject, string expression)
|
||||
{
|
||||
// Run through many of the same cases as other Eval() overload.
|
||||
return EvalComplexExpression(indexableObject, expression);
|
||||
}
|
||||
|
||||
private static ViewDataInfo EvalComplexExpression(object indexableObject, string expression)
|
||||
{
|
||||
if (indexableObject == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (expression == null)
|
||||
{
|
||||
// In case a Dictionary indexableObject contains a "" entry, don't short-circuit the logic below.
|
||||
expression = string.Empty;
|
||||
}
|
||||
|
||||
return InnerEvalComplexExpression(indexableObject, expression);
|
||||
}
|
||||
|
||||
private static ViewDataInfo InnerEvalComplexExpression(object indexableObject, string expression)
|
||||
{
|
||||
foreach (var expressionPair in GetRightToLeftExpressions(expression))
|
||||
{
|
||||
|
|
@ -59,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
|
||||
if (subTargetInfo.Value != null)
|
||||
{
|
||||
var potential = EvalComplexExpression(subTargetInfo.Value, postExpression);
|
||||
var potential = InnerEvalComplexExpression(subTargetInfo.Value, postExpression);
|
||||
if (potential != null)
|
||||
{
|
||||
return potential;
|
||||
|
|
@ -71,12 +120,15 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
return null;
|
||||
}
|
||||
|
||||
// Produces an enumeration of combinations of property names given a complex expression in the following order:
|
||||
// this["one.two.three.four"]
|
||||
// this["one.two.three][four"]
|
||||
// this["one.two][three.four"]
|
||||
// this["one][two.three.four"]
|
||||
// Recursion of InnerEvalComplexExpression() further sub-divides these cases to cover the full set of
|
||||
// combinations shown in Eval(ViewDataDictionary, string) comments.
|
||||
private static IEnumerable<ExpressionPair> GetRightToLeftExpressions(string expression)
|
||||
{
|
||||
// Produces an enumeration of all the combinations of complex property names
|
||||
// given a complex expression. See the list above for an example of the result
|
||||
// of the enumeration.
|
||||
|
||||
yield return new ExpressionPair(expression, string.Empty);
|
||||
|
||||
var lastDot = expression.LastIndexOf('.');
|
||||
|
|
@ -122,34 +174,24 @@ namespace Microsoft.AspNet.Mvc.Rendering.Expressions
|
|||
return null;
|
||||
}
|
||||
|
||||
// This method handles one "segment" of a complex property expression
|
||||
private static ViewDataInfo GetPropertyValue(object container, string propertyName)
|
||||
{
|
||||
// This method handles one "segment" of a complex property expression
|
||||
|
||||
// First, we try to evaluate the property based on its indexer
|
||||
// First, try to evaluate the property based on its indexer.
|
||||
var value = GetIndexedPropertyValue(container, propertyName);
|
||||
if (value != null)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// If the indexer didn't return anything useful, continue...
|
||||
|
||||
// If the container is a ViewDataDictionary then treat its Model property
|
||||
// as the container instead of the ViewDataDictionary itself.
|
||||
var viewData = container as ViewDataDictionary;
|
||||
if (viewData != null)
|
||||
{
|
||||
container = viewData.Model;
|
||||
}
|
||||
|
||||
// If the container is null, we're out of options
|
||||
if (container == null)
|
||||
// Do not attempt to find a property with an empty name and or of a ViewDataDictionary.
|
||||
if (string.IsNullOrEmpty(propertyName) || container is ViewDataDictionary)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Finally try to use PropertyInfo and treat the expression as a property name
|
||||
// If the indexer didn't return anything useful, try to use PropertyInfo and treat the expression
|
||||
// as a property name.
|
||||
var propertyInfo = container.GetType().GetRuntimeProperty(propertyName);
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -322,11 +322,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
// isChecked not provided nor found in the given attributes; fall back to view data.
|
||||
var valueString = Convert.ToString(value, CultureInfo.CurrentCulture);
|
||||
isChecked = !string.IsNullOrEmpty(expression) &&
|
||||
string.Equals(
|
||||
EvalString(viewContext, expression),
|
||||
valueString,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
isChecked = string.Equals(
|
||||
EvalString(viewContext, expression),
|
||||
valueString,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -419,14 +418,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
// If we got a null selectList, try to use ViewData to get the list of items.
|
||||
if (selectList == null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
// Do not call ViewData.Eval(); that would return ViewData.Model, which is not correct here.
|
||||
// Note this case has a simple workaround: users must pass a non-null selectList to use
|
||||
// DropDownList() or ListBox() in a template, where a null or empty name has meaning.
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(expression));
|
||||
}
|
||||
|
||||
selectList = GetSelectListItems(viewContext, expression);
|
||||
}
|
||||
|
||||
|
|
@ -1012,7 +1003,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
if (!usedModelState && useViewData)
|
||||
{
|
||||
isChecked = EvalBoolean(viewContext, fullName);
|
||||
isChecked = EvalBoolean(viewContext, expression);
|
||||
}
|
||||
|
||||
if (isChecked)
|
||||
|
|
@ -1036,7 +1027,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
var attributeValue = (string)GetModelStateValue(viewContext, fullName, typeof(string));
|
||||
if (attributeValue == null)
|
||||
{
|
||||
attributeValue = useViewData ? EvalString(viewContext, fullName, format) : valueParameter;
|
||||
attributeValue = useViewData ? EvalString(viewContext, expression, format) : valueParameter;
|
||||
}
|
||||
|
||||
var addValue = true;
|
||||
|
|
@ -1193,14 +1184,20 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
[NotNull] ViewContext viewContext,
|
||||
string expression)
|
||||
{
|
||||
// Method is called only if user did not pass a select list in. They must provide select list items in the
|
||||
// ViewData dictionary and definitely not as the Model. (Even if the Model datatype were correct, a
|
||||
// <select> element generated for a collection of SelectListItems would be useless.)
|
||||
var value = viewContext.ViewData.Eval(expression);
|
||||
if (value == null)
|
||||
|
||||
// First check whether above evaluation was successful and did not match ViewData.Model.
|
||||
if (value == null || value == viewContext.ViewData.Model)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_MissingSelectData(
|
||||
$"IEnumerable<{nameof(SelectListItem)}>",
|
||||
expression));
|
||||
}
|
||||
|
||||
// Second check the Eval() call returned a collection of SelectListItems.
|
||||
var selectList = value as IEnumerable<SelectListItem>;
|
||||
if (selectList == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.Extensions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering.Expressions;
|
||||
|
|
@ -301,12 +300,37 @@ namespace Microsoft.AspNet.Mvc
|
|||
get { return _data; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value of named <paramref name="expression"/> in this <see cref="ViewDataDictionary"/>.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <returns>Value of named <paramref name="expression"/> in this <see cref="ViewDataDictionary"/>.</returns>
|
||||
/// <remarks>
|
||||
/// Looks up <paramref name="expression"/> in the dictionary first. Falls back to evaluating it against
|
||||
/// <see cref="Model"/>.
|
||||
/// </remarks>
|
||||
public object Eval(string expression)
|
||||
{
|
||||
var info = GetViewDataInfo(expression);
|
||||
return (info != null) ? info.Value : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value of named <paramref name="expression"/> in this <see cref="ViewDataDictionary"/>, formatted
|
||||
/// using given <paramref name="format"/>.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <param name="format">
|
||||
/// The composite format <see cref="string"/> (see http://msdn.microsoft.com/en-us/library/txafckwd.aspx).
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Value of named <paramref name="expression"/> in this <see cref="ViewDataDictionary"/>, formatted using
|
||||
/// given <paramref name="format"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Looks up <paramref name="expression"/> in the dictionary first. Falls back to evaluating it against
|
||||
/// <see cref="Model"/>.
|
||||
/// </remarks>
|
||||
public string Eval(string expression, string format)
|
||||
{
|
||||
var value = Eval(expression);
|
||||
|
|
@ -330,14 +354,21 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in this
|
||||
/// <see cref="ViewDataDictionary"/>.
|
||||
/// </summary>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <returns>
|
||||
/// <see cref="ViewDataInfo"/> for named <paramref name="expression"/> in this
|
||||
/// <see cref="ViewDataDictionary"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Looks up <paramref name="expression"/> in the dictionary first. Falls back to evaluating it against
|
||||
/// <see cref="Model"/>.
|
||||
/// </remarks>
|
||||
public ViewDataInfo GetViewDataInfo(string expression)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
// Null or empty expression name means current model.
|
||||
return new ViewDataInfo(container: null, value: Model);
|
||||
}
|
||||
|
||||
return ViewDataEvaluator.Eval(this, expression);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expected, html.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1485, unable to get Model value.")]
|
||||
[Fact]
|
||||
public void CheckBoxInTemplate_GetsModelValue_IfModelStateAndViewDataEmpty()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayText_ReturnsModelStateEntry()
|
||||
public void DisplayText_IgnoresModelStateEntry_ReturnsViewDataEntry()
|
||||
{
|
||||
// Arrange
|
||||
var model = new OverriddenToStringModel("Model value")
|
||||
|
|
@ -257,7 +257,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
};
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
var viewData = helper.ViewData;
|
||||
viewData["Name"] = "View data dictionary value";
|
||||
viewData["FieldPrefix.Name"] = "View data dictionary value";
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix";
|
||||
|
||||
var modelState = new ModelState();
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expected, result.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1485, unable to get Model value.")]
|
||||
[Fact]
|
||||
public void HiddenInTemplate_GetsModelValue_IfModelStateAndViewDataEmpty()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -488,7 +488,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expectedHtml, html.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||
[Fact]
|
||||
public void DropDownListInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -516,7 +516,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expectedHtml, html.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||
[Fact]
|
||||
public void DropDownListInTemplate_GetsPropertyOfViewDataEntry_IfModelStateEmptyAndNoViewDataEntryWithPrefix()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -899,7 +899,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expectedHtml, html.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||
[Fact]
|
||||
public void ListBoxInTemplate_GetsViewDataEntry_IfModelStateEmpty()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -927,7 +927,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
Assert.Equal(expectedHtml, html.ToString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "#1487, incorrectly matches Property1 entry (without prefix) in ViewData.")]
|
||||
[Fact]
|
||||
public void ListBoxInTemplate_GetsPropertyOfViewDataEntry_IfModelStateEmptyAndNoViewDataEntryWithPrefix()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
Assert.Equal("ViewDataValue", html);
|
||||
}
|
||||
|
||||
[Fact(Skip = "$1487, finds 'StringProperty' entry (without prefix) instead.")]
|
||||
[Fact]
|
||||
public void ValueInTemplate_GetsValueFromPrefixedViewDataEntry()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
Assert.Equal("ContainedViewDataValue", html);
|
||||
}
|
||||
|
||||
[Fact(Skip = "$1487, finds 'StringProperty' entry (without prefix) instead.")]
|
||||
[Fact]
|
||||
public void ValueInTemplate_GetsValueFromPropertyOfViewDataEntry()
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
Assert.Empty(html);
|
||||
}
|
||||
|
||||
[Fact(Skip = "$1487, finds 'StringProperty' entry (without prefix) instead.")]
|
||||
[Fact]
|
||||
public void ValueInTemplate_GetsValueFromViewData_EvenIfNull()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -3,15 +3,13 @@
|
|||
<form action="/HtmlGeneration_Home/EmployeeList" method="post">
|
||||
<div>
|
||||
<label for="z0__Number">Number</label>
|
||||
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z0__Number" name="[0].Number" readonly="readonly" type="text" value="" />
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z0__Number" name="[0].Number" readonly="readonly" type="text" value="0" />
|
||||
<input class="form-control" type="number" id="z0__Number" name="[0].Number" value="0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z0__Name">Name</label>
|
||||
|
||||
<textarea id="z0__Name" name="[0].Name">
|
||||
Name value that should not be seen.</textarea>
|
||||
EmployeeName_0</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z0__PhoneNumber">PhoneNumber</label>
|
||||
|
|
@ -20,9 +18,7 @@ Name value that should not be seen.</textarea>
|
|||
<div>
|
||||
|
||||
<label class="employee" for="z0__Gender">Gender</label>
|
||||
|
||||
<input disabled="disabled" id="z0__Gender" name="[0].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
|
||||
<input data-val="true" data-val-required="The Gender field is required." disabled="disabled" id="z0__Gender" name="[0].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
<select id="z0__Gender" name="[0].Gender"><option selected="selected">Male</option>
|
||||
<option>Female</option>
|
||||
</select>
|
||||
|
|
@ -40,15 +36,13 @@ Name value that should not be seen.</textarea>
|
|||
</div>
|
||||
<div>
|
||||
<label for="z1__Number">Number</label>
|
||||
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z1__Number" name="[1].Number" readonly="readonly" type="text" value="" />
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z1__Number" name="[1].Number" readonly="readonly" type="text" value="1" />
|
||||
<input class="form-control" type="number" id="z1__Number" name="[1].Number" value="1" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z1__Name">Name</label>
|
||||
|
||||
<textarea id="z1__Name" name="[1].Name">
|
||||
Name value that should not be seen.</textarea>
|
||||
EmployeeName_1</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z1__PhoneNumber">PhoneNumber</label>
|
||||
|
|
@ -57,9 +51,7 @@ Name value that should not be seen.</textarea>
|
|||
<div>
|
||||
|
||||
<label class="employee" for="z1__Gender">Gender</label>
|
||||
|
||||
<input disabled="disabled" id="z1__Gender" name="[1].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
|
||||
<input checked="checked" data-val="true" data-val-required="The Gender field is required." disabled="disabled" id="z1__Gender" name="[1].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
<select id="z1__Gender" name="[1].Gender"><option>Male</option>
|
||||
<option selected="selected">Female</option>
|
||||
</select>
|
||||
|
|
@ -77,15 +69,13 @@ Name value that should not be seen.</textarea>
|
|||
</div>
|
||||
<div>
|
||||
<label for="z2__Number">Number</label>
|
||||
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z2__Number" name="[2].Number" readonly="readonly" type="text" value="" />
|
||||
<input data-val="true" data-val-range="The field Number must be between 1 and 100." data-val-range-max="100" data-val-range-min="1" data-val-required="The Number field is required." disabled="disabled" id="z2__Number" name="[2].Number" readonly="readonly" type="text" value="2" />
|
||||
<input class="form-control" type="number" id="z2__Number" name="[2].Number" value="2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z2__Name">Name</label>
|
||||
|
||||
<textarea id="z2__Name" name="[2].Name">
|
||||
Name value that should not be seen.</textarea>
|
||||
EmployeeName_2</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="employee" for="z2__PhoneNumber">PhoneNumber</label>
|
||||
|
|
@ -94,9 +84,7 @@ Name value that should not be seen.</textarea>
|
|||
<div>
|
||||
|
||||
<label class="employee" for="z2__Gender">Gender</label>
|
||||
|
||||
<input disabled="disabled" id="z2__Gender" name="[2].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
|
||||
<input data-val="true" data-val-required="The Gender field is required." disabled="disabled" id="z2__Gender" name="[2].Gender" readonly="readonly" type="radio" value="Female" />
|
||||
<select id="z2__Gender" name="[2].Gender"><option selected="selected">Male</option>
|
||||
<option>Female</option>
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@
|
|||
<div>
|
||||
<label class="HtmlEncode[[order]]" for="HtmlEncode[[Customer_Gender]]">HtmlEncode[[Gender]]</label>
|
||||
<input data-val="HtmlEncode[[true]]" data-val-required="HtmlEncode[[The Gender field is required.]]" id="HtmlEncode[[Customer_Gender]]" name="HtmlEncode[[Customer.Gender]]" type="HtmlEncode[[radio]]" value="HtmlEncode[[Male]]" /> Male
|
||||
|
||||
<input id="HtmlEncode[[Customer_Gender]]" name="HtmlEncode[[Customer.Gender]]" type="HtmlEncode[[radio]]" value="HtmlEncode[[Female]]" /> Female
|
||||
<input checked="HtmlEncode[[checked]]" id="HtmlEncode[[Customer_Gender]]" name="HtmlEncode[[Customer.Gender]]" type="HtmlEncode[[radio]]" value="HtmlEncode[[Female]]" /> Female
|
||||
<span class="HtmlEncode[[field-validation-valid]]" data-valmsg-for="HtmlEncode[[Customer.Gender]]" data-valmsg-replace="HtmlEncode[[true]]"></span>
|
||||
</div>
|
||||
<div class="HtmlEncode[[validation-summary-valid order]]" data-valmsg-summary="HtmlEncode[[true]]"><ul><li style="display:none"></li>
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@
|
|||
<div>
|
||||
<label class="order" for="Customer_Gender">Gender</label>
|
||||
<input data-val="true" data-val-required="The Gender field is required." id="Customer_Gender" name="Customer.Gender" type="radio" value="Male" /> Male
|
||||
|
||||
<input id="Customer_Gender" name="Customer.Gender" type="radio" value="Female" /> Female
|
||||
<input checked="checked" id="Customer_Gender" name="Customer.Gender" type="radio" value="Female" /> Female
|
||||
<span class="field-validation-valid" data-valmsg-for="Customer.Gender" data-valmsg-replace="true"></span>
|
||||
</div>
|
||||
<div class="validation-summary-valid order" data-valmsg-summary="true"><ul><li style="display:none"></li>
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ namespace HtmlGenerationWebSite.Controllers
|
|||
},
|
||||
};
|
||||
|
||||
// Extra data that should be ignored within a template. But #1487 currently affects RadioButton and
|
||||
// TextArea as well as ModelMetadata for <select> tag helper.
|
||||
// Extra data that should be ignored / not used within a template.
|
||||
ViewData[nameof(Employee.Gender)] = "Gender value that will not match.";
|
||||
ViewData[nameof(Employee.Name)] = "Name value that should not be seen.";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@
|
|||
|
||||
<div>
|
||||
@Html.LabelFor(m => m.Number)
|
||||
@* Due to #1485, text box will be empty though all employees have Number values. *@
|
||||
@Html.TextBox(nameof(Model.Number), value: null, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
|
||||
<input asp-for="Number" type="number" class="form-control" />
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Name" class="employee"></label>
|
||||
@* Due to #1487, text area will contain incorrect "Value that should not be seen." *@
|
||||
@Html.TextArea(nameof(Model.Name))
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -22,9 +20,7 @@
|
|||
var genders = new SelectList(new string[] { "Male", "Female" });
|
||||
}
|
||||
<label asp-for="Gender" class="employee"></label>
|
||||
@* Due to #1487, radio button will not be checked. Employee is Female but incorrect information does not match. *@
|
||||
@Html.RadioButton(nameof(Model.Gender), "Female", htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
|
||||
@* Due to #1487, <select> tag helper will not generate expected validation attributes. *@
|
||||
<select asp-for="Gender" asp-items="genders"></select>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@using HtmlGenerationWebSite.Models
|
||||
@model Gender
|
||||
@Html.RadioButtonFor(m => m, value: "Male") Male
|
||||
@* Due to #2662 radio button will not be checked because help ignores this expression." *@
|
||||
@Html.RadioButton(expression: string.Empty, value: "Female") Female
|
||||
Loading…
Reference in New Issue