diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
index 97443f5e05..bacbfa51c8 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
+using System.Reflection;
using System.Text;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
@@ -20,6 +21,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
public class DefaultHtmlGenerator : IHtmlGenerator
{
private const string HiddenListItem = @"
";
+ private static readonly MethodInfo ConvertEnumFromStringMethod =
+ typeof(DefaultHtmlGenerator).GetTypeInfo().GetDeclaredMethod(nameof(ConvertEnumFromString));
private readonly AntiForgery _antiForgery;
private readonly IScopedInstance _bindingContextAccessor;
@@ -377,16 +380,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
bool allowMultiple,
object htmlAttributes)
{
- ICollection ignored;
+ var currentValues = GetCurrentValues(viewContext, modelExplorer, expression, allowMultiple);
return GenerateSelect(
viewContext,
modelExplorer,
optionLabel,
expression,
selectList,
+ currentValues,
allowMultiple,
- htmlAttributes,
- selectedValues: out ignored);
+ htmlAttributes);
}
///
@@ -396,9 +399,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
string optionLabel,
string expression,
IEnumerable selectList,
+ IReadOnlyCollection currentValues,
bool allowMultiple,
- object htmlAttributes,
- out ICollection selectedValues)
+ object htmlAttributes)
{
var fullName = GetFullHtmlFieldName(viewContext, expression);
if (string.IsNullOrEmpty(fullName))
@@ -407,7 +410,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
// If we got a null selectList, try to use ViewData to get the list of items.
- var usedViewData = false;
if (selectList == null)
{
if (string.IsNullOrEmpty(expression))
@@ -419,39 +421,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
selectList = GetSelectListItems(viewContext, expression);
- usedViewData = true;
}
- var type = allowMultiple ? typeof(string[]) : typeof(string);
- var defaultValue = GetModelStateValue(viewContext, fullName, type);
-
- // If ModelState did not contain a current value, fall back to ViewData- or ModelExplorer-supplied value.
- if (defaultValue == null)
+ modelExplorer = modelExplorer ??
+ ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
+ if (currentValues != null)
{
- if (modelExplorer == null)
- {
- // Html.DropDownList() and Html.ListBox() helper case.
- // Cannot use ViewData if it contains the select list.
- if (!usedViewData)
- {
- defaultValue = viewContext.ViewData.Eval(expression);
- }
- }
- else
- {
- // , Html.DropDownListFor() and Html.ListBoxFor() helper case. Do not use ViewData.
- defaultValue = modelExplorer.Model;
- }
- }
-
- if (defaultValue != null)
- {
- selectList =
- UpdateSelectListItemsWithDefaultValue(selectList, defaultValue, allowMultiple, out selectedValues);
- }
- else
- {
- selectedValues = new string[0];
+ selectList = UpdateSelectListItemsWithDefaultValue(modelExplorer, selectList, currentValues);
}
// Convert each ListItem to an tag and wrap them with if requested.
@@ -746,18 +722,137 @@ namespace Microsoft.AspNet.Mvc.Rendering
modelExplorer.Metadata,
_metadataProvider,
viewContext.HttpContext.RequestServices);
-
+
var validatorProviderContext = new ModelValidatorProviderContext(modelExplorer.Metadata);
validatorProvider.GetValidators(validatorProviderContext);
var validators = validatorProviderContext.Validators;
- return
+ return
validators
.OfType()
.SelectMany(v => v.GetClientValidationRules(validationContext));
}
+ ///
+ public virtual IReadOnlyCollection GetCurrentValues(
+ [NotNull] ViewContext viewContext,
+ ModelExplorer modelExplorer,
+ string expression,
+ bool allowMultiple)
+ {
+ var fullName = GetFullHtmlFieldName(viewContext, expression);
+ if (string.IsNullOrEmpty(fullName))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(expression));
+ }
+
+ var type = allowMultiple ? typeof(string[]) : typeof(string);
+ var rawValue = GetModelStateValue(viewContext, fullName, type);
+
+ // If ModelState did not contain a current value, fall back to ViewData- or ModelExplorer-supplied value.
+ if (rawValue == null)
+ {
+ if (modelExplorer == null)
+ {
+ // Html.DropDownList() and Html.ListBox() helper case.
+ rawValue = viewContext.ViewData.Eval(expression);
+ if (rawValue is IEnumerable)
+ {
+ // This ViewData item contains the fallback selectList collection for GenerateSelect().
+ // Do not try to use this collection.
+ rawValue = null;
+ }
+ }
+ else
+ {
+ // , Html.DropDownListFor() and Html.ListBoxFor() helper case. Do not use ViewData.
+ rawValue = modelExplorer.Model;
+ }
+
+ if (rawValue == null)
+ {
+ return null;
+ }
+ }
+
+ // Convert raw value to a collection.
+ IEnumerable rawValues;
+ if (allowMultiple)
+ {
+ rawValues = rawValue as IEnumerable;
+ if (rawValues == null || rawValues is string)
+ {
+ throw new InvalidOperationException(
+ Resources.FormatHtmlHelper_SelectExpressionNotEnumerable(nameof(expression)));
+ }
+ }
+ else
+ {
+ rawValues = new[] { rawValue };
+ }
+
+ modelExplorer = modelExplorer ??
+ ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider);
+
+ var enumNames = modelExplorer.Metadata.EnumNamesAndValues;
+ var isTargetEnum = modelExplorer.Metadata.IsEnum;
+ var innerType =
+ Nullable.GetUnderlyingType(modelExplorer.Metadata.ModelType) ?? modelExplorer.Metadata.ModelType;
+
+ // Convert raw value collection to strings.
+ var currentValues = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (var value in rawValues)
+ {
+ // Add original or converted string.
+ var stringValue = (value as string) ?? Convert.ToString(value, CultureInfo.CurrentCulture);
+
+ // Do not add simple names of enum properties here because whitespace isn't relevant for their binding.
+ // Will add matching names just below.
+ if (enumNames == null || !enumNames.ContainsKey(stringValue.Trim()))
+ {
+ currentValues.Add(stringValue);
+ }
+
+ // Remainder handles isEnum cases. Convert.ToString() returns field names for enum values but select
+ // list may (well, should) contain integer values.
+ var enumValue = value as Enum;
+ if (isTargetEnum && enumValue == null && value != null)
+ {
+ var valueType = value.GetType();
+ if (typeof(long).IsAssignableFrom(valueType) || typeof(ulong).IsAssignableFrom(valueType))
+ {
+ // E.g. user added an int to a ViewData entry and called a string-based HTML helper.
+ enumValue = ConvertEnumFromInteger(value, innerType);
+ }
+ else if (!string.IsNullOrEmpty(stringValue))
+ {
+ // E.g. got a string from ModelState.
+ var methodInfo = ConvertEnumFromStringMethod.MakeGenericMethod(innerType);
+ enumValue = (Enum)methodInfo.Invoke(obj: null, parameters: new[] { stringValue });
+ }
+ }
+
+ if (enumValue != null)
+ {
+ // Add integer value.
+ var integerString = enumValue.ToString("d");
+ currentValues.Add(integerString);
+
+ // Add all simple names for this value.
+ var matchingNames = enumNames
+ .Where(kvp => string.Equals(integerString, kvp.Value, StringComparison.Ordinal))
+ .Select(kvp => kvp.Key);
+ foreach (var name in matchingNames)
+ {
+ currentValues.Add(name);
+ }
+ }
+ }
+
+ return (IReadOnlyCollection)currentValues;
+ }
+
internal static string EvalString(ViewContext viewContext, string key, string format)
{
return Convert.ToString(viewContext.ViewData.Eval(key, format), CultureInfo.CurrentCulture);
@@ -990,6 +1085,32 @@ namespace Microsoft.AspNet.Mvc.Rendering
return UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules);
}
+ private static Enum ConvertEnumFromInteger(object value, Type targetType)
+ {
+ try
+ {
+ return (Enum)Enum.ToObject(targetType, value);
+ }
+ catch (Exception exception)
+ when (exception is FormatException || exception.InnerException is FormatException)
+ {
+ // The integer was too large for this enum type.
+ return null;
+ }
+ }
+
+ private static object ConvertEnumFromString(string value) where TEnum : struct
+ {
+ TEnum enumValue;
+ if (Enum.TryParse(value, out enumValue))
+ {
+ return enumValue;
+ }
+
+ // Do not return default(TEnum) when parse was unsuccessful.
+ return null;
+ }
+
private static bool EvalBoolean(ViewContext viewContext, string key)
{
return Convert.ToBoolean(viewContext.ViewData.Eval(key), CultureInfo.InvariantCulture);
@@ -1060,48 +1181,26 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
private static IEnumerable UpdateSelectListItemsWithDefaultValue(
+ ModelExplorer modelExplorer,
IEnumerable selectList,
- object defaultValue,
- bool allowMultiple,
- out ICollection selectedValues)
+ IReadOnlyCollection currentValues)
{
- IEnumerable defaultValues;
- if (allowMultiple)
- {
- defaultValues = defaultValue as IEnumerable;
- if (defaultValues == null || defaultValues is string)
- {
- throw new InvalidOperationException(
- Resources.FormatHtmlHelper_SelectExpressionNotEnumerable("expression"));
- }
- }
- else
- {
- defaultValues = new[] { defaultValue };
- }
-
- var values =
- defaultValues.OfType().Select(value => Convert.ToString(value, CultureInfo.CurrentCulture));
-
- // ToString() by default returns an enum value's name. But selectList may use numeric values.
- var enumValues = defaultValues.OfType().Select(value => value.ToString());
- values = values.Concat(enumValues);
-
- selectedValues = new HashSet(values, StringComparer.OrdinalIgnoreCase);
-
// Perform deep copy of selectList to avoid changing user's Selected property values.
var newSelectList = new List();
foreach (SelectListItem item in selectList)
{
- var newItem = new SelectListItem
+ var value = item.Value ?? item.Text;
+ var selected = currentValues.Contains(value);
+ var copy = new SelectListItem
{
Disabled = item.Disabled,
Group = item.Group,
- Selected = selectedValues.Contains(item.Value ?? item.Text),
+ Selected = selected,
Text = item.Text,
Value = item.Value,
};
- newSelectList.Add(newItem);
+
+ newSelectList.Add(copy);
}
return newSelectList;
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
index 2191b575b3..5510e41f90 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
@@ -691,8 +691,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
ViewContext,
modelExplorer,
optionLabel,
- expression: expression,
- selectList: selectList,
+ expression,
+ selectList,
allowMultiple: false,
htmlAttributes: htmlAttributes);
if (tagBuilder == null)
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/IHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/IHtmlGenerator.cs
index 4646b6e8ad..cab71cfb76 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/IHtmlGenerator.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/IHtmlGenerator.cs
@@ -159,6 +159,40 @@ namespace Microsoft.AspNet.Mvc.Rendering
object routeValues,
object htmlAttributes);
+ ///
+ /// Generate a <select> element for the .
+ ///
+ /// A instance for the current scope.
+ ///
+ /// for the . If null , determines validation
+ /// attributes using and the .
+ ///
+ /// Optional text for a default empty <option> element.
+ /// Expression name, relative to the current model.
+ ///
+ /// A collection of objects used to populate the <select> element with
+ /// <optgroup> and <option> elements. If null , finds this collection at
+ /// ViewContext.ViewData[expression] .
+ ///
+ ///
+ /// If true , includes a multiple attribute in the generated HTML. Otherwise generates a
+ /// single-selection <select> element.
+ ///
+ ///
+ /// An that contains the HTML attributes for the <select> element. Alternatively, an
+ /// instance containing the HTML attributes.
+ ///
+ /// A new describing the <select> element.
+ ///
+ ///
+ /// Combines and to set
+ /// <select> element's "name" attribute. Sanitizes to set element's "id"
+ /// attribute.
+ ///
+ ///
+ /// See for information about how current values are determined.
+ ///
+ ///
TagBuilder GenerateSelect(
[NotNull] ViewContext viewContext,
ModelExplorer modelExplorer,
@@ -168,15 +202,55 @@ namespace Microsoft.AspNet.Mvc.Rendering
bool allowMultiple,
object htmlAttributes);
+ ///
+ /// Generate a <select> element for the .
+ ///
+ /// A instance for the current scope.
+ ///
+ /// for the . If null , determines validation
+ /// attributes using and the .
+ ///
+ /// Optional text for a default empty <option> element.
+ /// Expression name, relative to the current model.
+ ///
+ /// A collection of objects used to populate the <select> element with
+ /// <optgroup> and <option> elements. If null , finds this collection at
+ /// ViewContext.ViewData[expression] .
+ ///
+ ///
+ /// An containing values for <option> elements to select. If
+ /// null , selects <option> elements based on values in
+ /// .
+ ///
+ ///
+ /// If true , includes a multiple attribute in the generated HTML. Otherwise generates a
+ /// single-selection <select> element.
+ ///
+ ///
+ /// An that contains the HTML attributes for the <select> element. Alternatively, an
+ /// instance containing the HTML attributes.
+ ///
+ /// A new describing the <select> element.
+ ///
+ ///
+ /// Combines and to set
+ /// <select> element's "name" attribute. Sanitizes to set element's "id"
+ /// attribute.
+ ///
+ ///
+ /// See for information about how the
+ /// collection may be created.
+ ///
+ ///
TagBuilder GenerateSelect(
[NotNull] ViewContext viewContext,
ModelExplorer modelExplorer,
string optionLabel,
string expression,
IEnumerable selectList,
+ IReadOnlyCollection currentValues,
bool allowMultiple,
- object htmlAttributes,
- out ICollection selectedValues);
+ object htmlAttributes);
TagBuilder GenerateTextArea(
[NotNull] ViewContext viewContext,
@@ -216,5 +290,45 @@ namespace Microsoft.AspNet.Mvc.Rendering
[NotNull] ViewContext viewContext,
ModelExplorer modelExplorer,
string expression);
+
+ ///
+ /// Gets the collection of current values for the given .
+ ///
+ /// A instance for the current scope.
+ ///
+ /// for the . If null , calculates the
+ /// result using .
+ ///
+ /// Expression name, relative to the current model.
+ ///
+ /// If true , require a collection result. Otherwise, treat result as a
+ /// single value.
+ ///
+ ///
+ ///
+ /// null if no result is found. Otherwise an
+ /// containing current values for the given
+ /// .
+ ///
+ ///
+ /// Converts the result to a . If that result is an
+ /// type, instead converts each item in the collection and returns
+ /// them separately.
+ ///
+ ///
+ /// If the result or the element type is an , returns a
+ /// containing the integer representation of the value as well
+ /// as all names for that value. Otherwise returns the default
+ /// conversion of the value.
+ ///
+ ///
+ ///
+ /// See for information about how the return value may be used.
+ ///
+ IReadOnlyCollection GetCurrentValues(
+ [NotNull] ViewContext viewContext,
+ ModelExplorer modelExplorer,
+ string expression,
+ bool allowMultiple);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs
index 54b9e5077a..8dd5ac79c2 100644
--- a/src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.TagHelpers/SelectTagHelper.cs
@@ -79,16 +79,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
var items = Items ?? Enumerable.Empty();
- ICollection selectedValues;
+ var currentValues = Generator.GetCurrentValues(
+ ViewContext,
+ For.ModelExplorer,
+ expression: For.Name,
+ allowMultiple: allowMultiple);
var tagBuilder = Generator.GenerateSelect(
ViewContext,
For.ModelExplorer,
optionLabel: null,
expression: For.Name,
selectList: items,
+ currentValues: currentValues,
allowMultiple: allowMultiple,
- htmlAttributes: null,
- selectedValues: out selectedValues);
+ htmlAttributes: null);
if (tagBuilder != null)
{
@@ -98,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Whether or not (not being highly unlikely) we generate anything, could update contained
// elements. Provide selected values for tag helpers. They'll run next.
- ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = selectedValues;
+ ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = currentValues;
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs
new file mode 100644
index 0000000000..f863aeca69
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultHtmlGeneratorTest.cs
@@ -0,0 +1,646 @@
+// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
+using System.IO;
+using System.Linq;
+using Microsoft.AspNet.DataProtection;
+using Microsoft.AspNet.Mvc.ModelBinding;
+using Microsoft.Framework.OptionsModel;
+using Microsoft.Framework.WebEncoders;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Rendering
+{
+ public class DefaultHtmlGeneratorTest
+ {
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetCurrentValues_WithEmptyViewData_ReturnsNull(bool allowMultiple)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: allowMultiple);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetCurrentValues_WithNullExpressionResult_ReturnsNull(bool allowMultiple)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider);
+ var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ expression: nameof(Model.Name),
+ allowMultiple: allowMultiple);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetCurrentValues_WithSelectListInViewData_ReturnsNull(bool allowMultiple)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = Enumerable.Empty();
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: allowMultiple);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Theory]
+ [InlineData("some string")] // treated as if it were not IEnumerable
+ [InlineData(23)]
+ [InlineData(RegularEnum.Three)]
+ public void GetCurrentValues_AllowMultipleWithNonEnumerableInViewData_Throws(object value)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = value;
+
+ // Act & Assert
+ var exception = Assert.Throws(() => htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: true));
+ Assert.Equal(
+ "The parameter 'expression' must evaluate to an IEnumerable when multiple selection is allowed.",
+ exception.Message);
+ }
+
+ // rawValue, allowMultiple -> expected current values
+ public static TheoryData> GetCurrentValues_StringAndCollectionData
+ {
+ get
+ {
+ return new TheoryData>
+ {
+ // ModelStateDictionary converts single values to arrays and visa-versa.
+ { string.Empty, false, new [] { string.Empty } },
+ { string.Empty, true, new [] { string.Empty } },
+ { "some string", false, new [] { "some string" } },
+ { "some string", true, new [] { "some string" } },
+ { new [] { "some string" }, false, new [] { "some string" } },
+ { new [] { "some string" }, true, new [] { "some string" } },
+ { new [] { "some string", "some other string" }, false, new [] { "some string" } },
+ {
+ new [] { "some string", "some other string" },
+ true,
+ new [] { "some string", "some other string" }
+ },
+ // { new string[] { null }, false, null } would fall back to other sources.
+ { new string[] { null }, true, new [] { string.Empty } },
+ { new [] { string.Empty }, false, new [] { string.Empty } },
+ { new [] { string.Empty }, true, new [] { string.Empty } },
+ {
+ new [] { null, "some string", "some other string" },
+ true,
+ new [] { string.Empty, "some string", "some other string" }
+ },
+ // ignores duplicates
+ {
+ new [] { null, "some string", null, "some other string", null, "some string", null },
+ true,
+ new [] { string.Empty, "some string", "some other string" }
+ },
+ // ignores case of duplicates
+ {
+ new [] { "some string", "SoMe StriNg", "Some String", "soME STRing", "SOME STRING" },
+ true,
+ new [] { "some string" }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringAndCollectionData))]
+ public void GetCurrentValues_WithModelStateEntryAndViewData_ReturnsModelStateEntry(
+ object rawValue,
+ bool allowMultiple,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Name = "ignored property value" };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
+
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: allowMultiple);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringAndCollectionData))]
+ public void GetCurrentValues_WithModelStateEntryModelExplorerAndViewData_ReturnsModelStateEntry(
+ object rawValue,
+ bool allowMultiple,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Name = "ignored property value" };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
+
+ var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), "ignored model value");
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ expression: nameof(Model.Name),
+ allowMultiple: allowMultiple);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expected, result);
+ }
+
+ // rawValue -> expected current values
+ public static TheoryData GetCurrentValues_StringData
+ {
+ get
+ {
+ return new TheoryData
+ {
+ // 1. If given a ModelExplorer, GetCurrentValues does not use ViewData even if expression result is
+ // null.
+ // 2. Otherwise if ViewData entry exists, GetCurrentValue does not fall back to ViewData.Model even
+ // if entry is null.
+ // 3. Otherwise, GetCurrentValue does not fall back anywhere else even if ViewData.Model is null.
+ { null, null },
+ { string.Empty, new [] { string.Empty } },
+ { "some string", new [] { "some string" } },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringData))]
+ public void GetCurrentValues_WithModelExplorerAndViewData_ReturnsExpressionResult(
+ string rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Name = "ignored property value" };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
+
+ var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), rawValue);
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ expression: nameof(Model.Name),
+ allowMultiple: false);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringData))]
+ public void GetCurrentValues_WithViewData_ReturnsViewDataEntry(
+ object rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Name = "ignored property value" };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Name)] = rawValue;
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: false);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringData))]
+ public void GetCurrentValues_WithModel_ReturnsModel(string rawValue, IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Name = rawValue };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Name),
+ allowMultiple: false);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ // rawValue -> expected current values
+ public static TheoryData GetCurrentValues_StringCollectionData
+ {
+ get
+ {
+ return new TheoryData
+ {
+ { new string[] { null }, new [] { string.Empty } },
+ { new [] { string.Empty }, new [] { string.Empty } },
+ { new [] { "some string" }, new [] { "some string" } },
+ { new [] { "some string", "some other string" }, new [] { "some string", "some other string" } },
+ {
+ new [] { null, "some string", "some other string" },
+ new [] { string.Empty, "some string", "some other string" }
+ },
+ // ignores duplicates
+ {
+ new [] { null, "some string", null, "some other string", null, "some string", null },
+ new [] { string.Empty, "some string", "some other string" }
+ },
+ // ignores case of duplicates
+ {
+ new [] { "some string", "SoMe StriNg", "Some String", "soME STRing", "SOME STRING" },
+ new [] { "some string" }
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringCollectionData))]
+ public void GetCurrentValues_CollectionWithModelExplorerAndViewData_ReturnsExpressionResult(
+ string[] rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Collection = { "ignored property value" } };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Collection)] = new[] { "ignored ViewData value" };
+
+ var modelExplorer =
+ metadataProvider.GetModelExplorerForType(typeof(List), new List(rawValue));
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ expression: nameof(Model.Collection),
+ allowMultiple: true);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringCollectionData))]
+ public void GetCurrentValues_CollectionWithViewData_ReturnsViewDataEntry(
+ object[] rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model { Collection = { "ignored property value" } };
+
+ var viewContext = GetViewContext(model, metadataProvider);
+ viewContext.ViewData[nameof(Model.Collection)] = rawValue;
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Collection),
+ allowMultiple: true);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_StringCollectionData))]
+ public void GetCurrentValues_CollectionWithModel_ReturnsModel(
+ string[] rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = new TestModelMetadataProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var model = new Model();
+ model.Collection.AddRange(rawValue);
+
+ var viewContext = GetViewContext(model, metadataProvider);
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue: null,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: nameof(Model.Collection),
+ allowMultiple: true);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ // property name, rawValue -> expected current values
+ public static TheoryData GetCurrentValues_ValueToConvertData
+ {
+ get
+ {
+ return new TheoryData
+ {
+ { nameof(Model.FlagsEnum), FlagsEnum.All, new [] { "-1", "All" } },
+ { nameof(Model.FlagsEnum), FlagsEnum.FortyTwo, new [] { "42", "FortyTwo" } },
+ { nameof(Model.FlagsEnum), FlagsEnum.None, new [] { "0", "None" } },
+ { nameof(Model.FlagsEnum), FlagsEnum.Two, new [] { "2", "Two" } },
+ { nameof(Model.FlagsEnum), string.Empty, new [] { string.Empty } },
+ { nameof(Model.FlagsEnum), "All", new [] { "-1", "All" } },
+ { nameof(Model.FlagsEnum), "FortyTwo", new [] { "42", "FortyTwo" } },
+ { nameof(Model.FlagsEnum), "None", new [] { "0", "None" } },
+ { nameof(Model.FlagsEnum), "Two", new [] { "2", "Two" } },
+ { nameof(Model.FlagsEnum), "Two, Four", new [] { "Two, Four", "6" } },
+ { nameof(Model.FlagsEnum), "garbage", new [] { "garbage" } },
+ { nameof(Model.FlagsEnum), "0", new [] { "0", "None" } },
+ { nameof(Model.FlagsEnum), " 43", new [] { " 43", "43" } },
+ { nameof(Model.FlagsEnum), "-5 ", new [] { "-5 ", "-5" } },
+ { nameof(Model.FlagsEnum), 0, new [] { "0", "None" } },
+ { nameof(Model.FlagsEnum), 1, new [] { "1", "One" } },
+ { nameof(Model.FlagsEnum), 43, new [] { "43" } },
+ { nameof(Model.FlagsEnum), -5, new [] { "-5" } },
+ { nameof(Model.FlagsEnum), int.MaxValue, new [] { "2147483647" } },
+ { nameof(Model.FlagsEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
+ { nameof(Model.FlagsEnum), uint.MaxValue, new [] { "4294967295" } }, // converted to string & used
+
+ { nameof(Model.Id), string.Empty, new [] { string.Empty } },
+ { nameof(Model.Id), "garbage", new [] { "garbage" } }, // no compatibility checks
+ { nameof(Model.Id), "0", new [] { "0" } },
+ { nameof(Model.Id), " 43", new [] { " 43" } },
+ { nameof(Model.Id), "-5 ", new [] { "-5 " } },
+ { nameof(Model.Id), 0, new [] { "0" } },
+ { nameof(Model.Id), 1, new [] { "1" } },
+ { nameof(Model.Id), 43, new [] { "43" } },
+ { nameof(Model.Id), -5, new [] { "-5" } },
+ { nameof(Model.Id), int.MaxValue, new [] { "2147483647" } },
+ { nameof(Model.Id), (uint)int.MaxValue + 1, new [] { "2147483648" } }, // no limit checks
+ { nameof(Model.Id), uint.MaxValue, new [] { "4294967295" } }, // no limit checks
+
+ { nameof(Model.NullableEnum), RegularEnum.Zero, new [] { "0", "Zero" } },
+ { nameof(Model.NullableEnum), RegularEnum.One, new [] { "1", "One" } },
+ { nameof(Model.NullableEnum), RegularEnum.Two, new [] { "2", "Two" } },
+ { nameof(Model.NullableEnum), RegularEnum.Three, new [] { "3", "Three" } },
+ { nameof(Model.NullableEnum), string.Empty, new [] { string.Empty } },
+ { nameof(Model.NullableEnum), "Zero", new [] { "0", "Zero" } },
+ { nameof(Model.NullableEnum), "Two", new [] { "2", "Two" } },
+ { nameof(Model.NullableEnum), "One, Two", new [] { "One, Two", "3", "Three" } },
+ { nameof(Model.NullableEnum), "garbage", new [] { "garbage" } },
+ { nameof(Model.NullableEnum), "0", new [] { "0", "Zero" } },
+ { nameof(Model.NullableEnum), " 43", new [] { " 43", "43" } },
+ { nameof(Model.NullableEnum), "-5 ", new [] { "-5 ", "-5" } },
+ { nameof(Model.NullableEnum), 0, new [] { "0", "Zero" } },
+ { nameof(Model.NullableEnum), 1, new [] { "1", "One" } },
+ { nameof(Model.NullableEnum), 43, new [] { "43" } },
+ { nameof(Model.NullableEnum), -5, new [] { "-5" } },
+ { nameof(Model.NullableEnum), int.MaxValue, new [] { "2147483647" } },
+ { nameof(Model.NullableEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
+ { nameof(Model.NullableEnum), uint.MaxValue, new [] { "4294967295" } },
+
+ { nameof(Model.RegularEnum), RegularEnum.Zero, new [] { "0", "Zero" } },
+ { nameof(Model.RegularEnum), RegularEnum.One, new [] { "1", "One" } },
+ { nameof(Model.RegularEnum), RegularEnum.Two, new [] { "2", "Two" } },
+ { nameof(Model.RegularEnum), RegularEnum.Three, new [] { "3", "Three" } },
+ { nameof(Model.RegularEnum), string.Empty, new [] { string.Empty } },
+ { nameof(Model.RegularEnum), "Zero", new [] { "0", "Zero" } },
+ { nameof(Model.RegularEnum), "Two", new [] { "2", "Two" } },
+ { nameof(Model.RegularEnum), "One, Two", new [] { "One, Two", "3", "Three" } },
+ { nameof(Model.RegularEnum), "garbage", new [] { "garbage" } },
+ { nameof(Model.RegularEnum), "0", new [] { "0", "Zero" } },
+ { nameof(Model.RegularEnum), " 43", new [] { " 43", "43" } },
+ { nameof(Model.RegularEnum), "-5 ", new [] { "-5 ", "-5" } },
+ { nameof(Model.RegularEnum), 0, new [] { "0", "Zero" } },
+ { nameof(Model.RegularEnum), 1, new [] { "1", "One" } },
+ { nameof(Model.RegularEnum), 43, new [] { "43" } },
+ { nameof(Model.RegularEnum), -5, new [] { "-5" } },
+ { nameof(Model.RegularEnum), int.MaxValue, new [] { "2147483647" } },
+ { nameof(Model.RegularEnum), (uint)int.MaxValue + 1, new [] { "2147483648" } },
+ { nameof(Model.RegularEnum), uint.MaxValue, new [] { "4294967295" } },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetCurrentValues_ValueToConvertData))]
+ public void GetCurrentValues_ValueConvertedAsExpected(
+ string propertyName,
+ object rawValue,
+ IReadOnlyCollection expected)
+ {
+ // Arrange
+ var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
+ var htmlGenerator = GetGenerator(metadataProvider);
+ var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider);
+
+ var valueProviderResult = new ValueProviderResult(
+ rawValue,
+ attemptedValue: null,
+ culture: CultureInfo.InvariantCulture);
+ viewContext.ModelState.SetModelValue(propertyName, valueProviderResult);
+
+ // Act
+ var result = htmlGenerator.GetCurrentValues(
+ viewContext,
+ modelExplorer: null,
+ expression: propertyName,
+ allowMultiple: false);
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
+ // GetCurrentValues uses only the IModelMetadataProvider passed to the DefaultHtmlGenerator constructor.
+ private static IHtmlGenerator GetGenerator(IModelMetadataProvider metadataProvider)
+ {
+ var mvcOptionsAccessor = new Mock>();
+ mvcOptionsAccessor.SetupGet(accessor => accessor.Options).Returns(new MvcOptions());
+ var htmlEncoder = Mock.Of();
+ var dataOptionsAccessor = new Mock>();
+ dataOptionsAccessor.SetupGet(accessor => accessor.Options).Returns(new DataProtectionOptions());
+ var antiForgery = new AntiForgery(
+ Mock.Of(),
+ Mock.Of(),
+ Mock.Of(),
+ mvcOptionsAccessor.Object,
+ htmlEncoder,
+ dataOptionsAccessor.Object);
+
+ return new DefaultHtmlGenerator(
+ antiForgery,
+ Mock.Of>(),
+ metadataProvider,
+ Mock.Of(),
+ htmlEncoder);
+ }
+
+ // GetCurrentValues uses only the ModelStateDictionary and ViewDataDictionary from the passed ViewContext.
+ private static ViewContext GetViewContext(TModel model, IModelMetadataProvider metadataProvider)
+ {
+ var actionContext = new ActionContext();
+ var viewData = new ViewDataDictionary(metadataProvider, actionContext.ModelState)
+ {
+ Model = model,
+ };
+
+ return new ViewContext(
+ actionContext,
+ Mock.Of(),
+ viewData,
+ Mock.Of(),
+ TextWriter.Null);
+ }
+
+ public enum RegularEnum
+ {
+ Zero,
+ One,
+ Two,
+ Three,
+ }
+
+ public enum FlagsEnum
+ {
+ None = 0,
+ One = 1,
+ Two = 2,
+ Four = 4,
+ FortyTwo = 42,
+ All = -1,
+ }
+
+ private class Model
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public RegularEnum RegularEnum { get; set; }
+
+ public FlagsEnum FlagsEnum { get; set; }
+
+ public RegularEnum? NullableEnum { get; set; }
+
+ public List Collection { get; } = new List();
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
index e647958b5b..edcc515f3d 100644
--- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
+++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs
@@ -243,12 +243,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
Assert.NotNull(viewContext.FormContext?.FormData);
- var keyValuePair = Assert.Single(
+ Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
- Assert.NotNull(keyValuePair.Value);
- var selectedValues = Assert.IsAssignableFrom>(keyValuePair.Value);
- Assert.InRange(selectedValues.Count, 0, 1);
}
[Theory]
@@ -341,12 +338,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
Assert.NotNull(viewContext.FormContext?.FormData);
- var keyValuePair = Assert.Single(
+ Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
- Assert.NotNull(keyValuePair.Value);
- var selectedValues = Assert.IsAssignableFrom>(keyValuePair.Value);
- Assert.InRange(selectedValues.Count, 0, 1);
Assert.Equal(savedDisabled, items.Select(item => item.Disabled));
Assert.Equal(savedGroup, items.Select(item => item.Group));
@@ -446,12 +440,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
Assert.NotNull(viewContext.FormContext?.FormData);
- var keyValuePair = Assert.Single(
+ Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
- Assert.NotNull(keyValuePair.Value);
- var selectedValues = Assert.IsAssignableFrom>(keyValuePair.Value);
- Assert.InRange(selectedValues.Count, 0, 1);
Assert.Equal(savedDisabled, items.Select(item => item.Disabled));
Assert.Equal(savedGroup, items.Select(item => item.Group));
@@ -504,17 +495,25 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var modelExpression = new ModelExpression(string.Empty, modelExplorer);
viewContext.ViewData.TemplateInfo.HtmlFieldPrefix = propertyName;
- ICollection selectedValues = new string[0];
+ var currentValues = new string[0];
+ htmlGenerator
+ .Setup(real => real.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ string.Empty, // expression
+ false)) // allowMultiple
+ .Returns(currentValues)
+ .Verifiable();
htmlGenerator
.Setup(real => real.GenerateSelect(
viewContext,
modelExplorer,
- null, // optionLabel
- string.Empty, // name
+ null, // optionLabel
+ string.Empty, // expression
expectedItems,
- false, // allowMultiple
- null, // htmlAttributes
- out selectedValues))
+ currentValues,
+ false, // allowMultiple
+ null)) // htmlAttributes
.Returns((TagBuilder)null)
.Verifiable();
@@ -536,7 +535,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var keyValuePair = Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
- Assert.Same(selectedValues, keyValuePair.Value);
+ Assert.Same(currentValues, keyValuePair.Value);
}
[Theory]
@@ -569,17 +568,25 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var htmlGenerator = new Mock(MockBehavior.Strict);
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator.Object, metadataProvider);
- ICollection selectedValues = new string[0];
+ var currentValues = new string[0];
+ htmlGenerator
+ .Setup(real => real.GetCurrentValues(
+ viewContext,
+ modelExplorer,
+ propertyName, // expression
+ allowMultiple))
+ .Returns(currentValues)
+ .Verifiable();
htmlGenerator
.Setup(real => real.GenerateSelect(
viewContext,
modelExplorer,
- null, // optionLabel
- propertyName, // name
+ null, // optionLabel
+ propertyName, // expression
It.IsAny>(),
+ currentValues,
allowMultiple,
- null, // htmlAttributes
- out selectedValues))
+ null)) // htmlAttributes
.Returns((TagBuilder)null)
.Verifiable();
@@ -600,7 +607,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var keyValuePair = Assert.Single(
viewContext.FormContext.FormData,
entry => entry.Key == SelectTagHelper.SelectedValuesFormDataKey);
- Assert.Same(selectedValues, keyValuePair.Value);
+ Assert.Same(currentValues, keyValuePair.Value);
}
public class NameAndId