Add `ModelState` entries for greedy and type-matching model binders

- part II of II for #2445
- `FormCollectionModelBinder` is an exception because container is not user-provided
 - no `ModelState` entry added
- enable tests that #2445 was blocking
 - fix these and other tests expecting different `ModelState` entries
- simplify logic in `FormFileModelBinder`

`ValueProviderResult`
- remove `protected` setters and parameterless constructor
 - no scenario for their use in subclasses; however `ConvertTo()` remains `virtual`
- add single-parameter constructor
 - use in most of the greedy and type-matching model binders
- add doc comments throughout class

nits:
- use new `ValueProviderResult` constructor in many existing tests
- `""` -> `string.Empty` and `vpr` -> `valueProviderResult` in `ValueProviderResultTest`
- improve some test names in `BodyValidationIntegrationTests`
- do not check `Message` of a Json.NET `Exception`
This commit is contained in:
Doug Bunting 2015-06-23 22:34:55 -07:00
parent 4a4b8ec87e
commit 715a0b6021
20 changed files with 357 additions and 345 deletions

View File

@ -12,45 +12,83 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Result of an <see cref="IValueProvider.GetValueAsync"/> operation.
/// </summary>
public class ValueProviderResult
{
private static readonly CultureInfo _staticCulture = CultureInfo.InvariantCulture;
private CultureInfo _instanceCulture;
// default constructor so that subclassed types can set the properties themselves
protected ValueProviderResult()
/// <summary>
/// Instantiates a new instance of the <see cref="ValueProviderResult"/> class with given
/// <paramref name="rawValue"/>. Initializes <see cref="Culture"/> to
/// <see cref="CultureInfo.InvariantCulture"/>.
/// </summary>
/// <param name="rawValue">The <see cref="RawValue"/> value of the new instance.</param>
public ValueProviderResult(object rawValue)
: this(rawValue, attemptedValue: null, culture: _staticCulture)
{
}
/// <summary>
/// Instantiates a new instance of the <see cref="ValueProviderResult"/> class with given
/// <paramref name="rawValue"/>, <paramref name="attemptedValue"/>, and <paramref name="culture"/>.
/// </summary>
/// <param name="rawValue">The <see cref="RawValue"/> value of the new instance.</param>
/// <param name="attemptedValue">The <see cref="AttemptedValue"/> value of the new instance.</param>
/// <param name="culture">The <see cref="Culture"/> value of the new instance.</param>
public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture)
{
RawValue = rawValue;
AttemptedValue = attemptedValue;
Culture = culture;
Culture = culture ?? _staticCulture;
}
public string AttemptedValue { get; protected set; }
/// <summary>
/// <see cref="string"/> conversion of <see cref="RawValue"/>.
/// </summary>
/// <remarks>
/// Used in helpers that generate <c>&lt;textarea&gt;</c> elements as well as some error messages.
/// </remarks>
public string AttemptedValue { get; }
public CultureInfo Culture
{
get
{
if (_instanceCulture == null)
{
_instanceCulture = _staticCulture;
}
return _instanceCulture;
}
protected set { _instanceCulture = value; }
}
/// <summary>
/// <see cref="CultureInfo"/> to use in <see cref="ConvertTo(Type)"/> or
/// <see cref="ConvertTo(Type, CultureInfo)"/> if passed <see cref="CultureInfo"/> is <c>null</c>.
/// </summary>
public CultureInfo Culture { get; }
public object RawValue { get; protected set; }
/// <summary>
/// The provided <see cref="object"/>.
/// </summary>
public object RawValue { get; }
/// <summary>
/// Converts <see cref="RawValue"/> to the given <paramref name="type"/>. Uses <see cref="Culture"/> for
/// <see cref="TypeConverter"/> operations.
/// </summary>
/// <param name="type">The target <see cref="Type"/> of the conversion.</param>
/// <returns>
/// <see cref="RawValue"/> converted to the given <paramref name="type"/>. <c>null</c> if the conversion fails.
/// </returns>
public object ConvertTo(Type type)
{
return ConvertTo(type, culture: null);
}
/// <summary>
/// Converts <see cref="RawValue"/> to the given <paramref name="type"/> using the given
/// <paramref name="culture"/>.
/// </summary>
/// <param name="type">The target <see cref="Type"/> of the conversion.</param>
/// <param name="culture">
/// The <see cref="CultureInfo"/> to use for <see cref="TypeConverter"/> operations. Uses
/// <see cref="Culture"/> if this parameter is <c>null</c>.
/// </param>
/// <returns>
/// <see cref="RawValue"/> converted to the given <paramref name="type"/> using the given
/// <paramref name="culture"/>. <c>null</c> if the conversion fails.
/// </returns>
public virtual object ConvertTo([NotNull] Type type, CultureInfo culture)
{
var value = RawValue;

View File

@ -66,6 +66,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new ModelBindingResult(modelBindingKey);
}
var valueProviderResult = new ValueProviderResult(rawValue: model);
bindingContext.ModelState.SetModelValue(modelBindingKey, valueProviderResult);
var validationNode = new ModelValidationNode(modelBindingKey, bindingContext.ModelMetadata, model)
{

View File

@ -21,17 +21,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null;
}
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
// Check for missing data case 1: There was no <input ... /> element containing this data.
var valueProviderResult = await bindingContext.ValueProvider.GetValueAsync(bindingContext.ModelName);
if (valueProviderResult == null)
{
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
}
var value = valueProviderResult.AttemptedValue;
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
// Check for missing data case 2: There was an <input ... /> element but it was left blank.
var value = valueProviderResult.AttemptedValue;
if (string.IsNullOrEmpty(value))
{
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null;
}
object model = null;
object model;
var request = bindingContext.OperationBindingContext.HttpContext.Request;
if (request.HasFormContentType)
{
@ -36,8 +36,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
else
{
var formValuesLookup = form.ToDictionary(p => p.Key,
p => p.Value);
var formValuesLookup = form.ToDictionary(p => p.Key, p => p.Value);
model = new FormCollection(formValuesLookup, form.Files);
}
}

View File

@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
#if DNXCORE50
using System.Reflection;
#endif
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
@ -20,33 +22,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <inheritdoc />
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
{
object value;
if (bindingContext.ModelType == typeof(IFormFile))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = postedFiles.FirstOrDefault();
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value);
return new ModelBindingResult(
value,
bindingContext.ModelName,
isModelSet: value != null,
validationNode: validationNode);
value = postedFiles.FirstOrDefault();
}
else if (typeof(IEnumerable<IFormFile>).GetTypeInfo().IsAssignableFrom(
bindingContext.ModelType.GetTypeInfo()))
else if (typeof(IEnumerable<IFormFile>).IsAssignableFrom(bindingContext.ModelType))
{
var postedFiles = await GetFormFilesAsync(bindingContext);
var value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
var validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value);
return new ModelBindingResult(
value,
bindingContext.ModelName,
isModelSet: value != null,
validationNode: validationNode);
value = ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, postedFiles);
}
else
{
// This binder does not support the requested type.
return null;
}
return null;
ModelValidationNode validationNode = null;
if (value != null)
{
validationNode =
new ModelValidationNode(bindingContext.ModelName, bindingContext.ModelMetadata, value);
var valueProviderResult = new ValueProviderResult(rawValue: value);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
}
return new ModelBindingResult(
value,
bindingContext.ModelName,
isModelSet: value != null,
validationNode: validationNode);
}
private async Task<List<IFormFile>> GetFormFilesAsync(ModelBindingContext bindingContext)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Globalization;
#if DNXCORE50
using System.Reflection;
#endif
@ -59,6 +60,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);
var attemptedValue = (model as string) ?? request.Headers.Get(headerName);
var valueProviderResult = new ValueProviderResult(model, attemptedValue, CultureInfo.InvariantCulture);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
}
return Task.FromResult(

View File

@ -2,7 +2,6 @@
// 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 Microsoft.Framework.Internal;
using Xunit;
@ -75,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Act
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldSkipped("key"));
// Assert
Assert.Equal(
"A field previously marked invalid should not be marked skipped.",
@ -138,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Act
var exception = Assert.Throws<InvalidOperationException>(() => source.MarkFieldValid("key"));
// Assert
Assert.Equal(
"A field previously marked invalid should not be marked valid.",
@ -278,7 +277,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var validState = new ModelState
{
Value = new ValueProviderResult(null, null, null),
Value = new ValueProviderResult(rawValue: null),
ValidationState = ModelValidationState.Valid
};
var msd = new ModelStateDictionary
@ -317,7 +316,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var validState = new ModelState
{
Value = new ValueProviderResult(null, null, null),
Value = new ValueProviderResult(rawValue: null),
ValidationState = ModelValidationState.Valid
};
var msd = new ModelStateDictionary

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
public void ConvertTo_ReturnsNullForReferenceTypes_WhenValueIsNull()
{
var valueProviderResult = new ValueProviderResult(null, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
var convertedValue = valueProviderResult.ConvertTo(typeof(string));
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
public void ConvertTo_ReturnsDefaultForValueTypes_WhenValueIsNull()
{
var valueProviderResult = new ValueProviderResult(null, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
var convertedValue = valueProviderResult.ConvertTo(typeof(int));
@ -34,10 +34,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToCanConvertArraysToSingleElements()
{
// Arrange
var vpr = new ValueProviderResult(new int[] { 1, 20, 42 }, "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(
new int[] { 1, 20, 42 },
string.Empty,
CultureInfo.InvariantCulture);
// Act
var converted = (string)vpr.ConvertTo(typeof(string));
var converted = (string)valueProviderResult.ConvertTo(typeof(string));
// Assert
Assert.Equal("1", converted);
@ -47,10 +50,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToCanConvertSingleElementsToArrays()
{
// Arrange
var vpr = new ValueProviderResult(42, "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(42, string.Empty, CultureInfo.InvariantCulture);
// Act
var converted = (string[])vpr.ConvertTo(typeof(string[]));
var converted = (string[])valueProviderResult.ConvertTo(typeof(string[]));
// Assert
Assert.NotNull(converted);
@ -62,10 +65,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToCanConvertSingleElementsToSingleElements()
{
// Arrange
var vpr = new ValueProviderResult(42, "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(42, string.Empty, CultureInfo.InvariantCulture);
// Act
var converted = (string)vpr.ConvertTo(typeof(string));
var converted = (string)valueProviderResult.ConvertTo(typeof(string));
// Assert
Assert.NotNull(converted);
@ -77,10 +80,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
object original = null;
var vpr = new ValueProviderResult(original, "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(original, string.Empty, CultureInfo.InvariantCulture);
// Act
var returned = (int?)vpr.ConvertTo(typeof(int?));
var returned = (int?)valueProviderResult.ConvertTo(typeof(int?));
// Assert
Assert.Equal(returned, null);
@ -91,10 +94,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var original = " ";
var vpr = new ValueProviderResult(original, "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(original, string.Empty, CultureInfo.InvariantCulture);
// Act
var returned = (int?)vpr.ConvertTo(typeof(int?));
var returned = (int?)valueProviderResult.ConvertTo(typeof(int?));
// Assert
Assert.Equal(returned, null);
@ -104,10 +107,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsNullIfArrayElementValueIsNull()
{
// Arrange
var vpr = new ValueProviderResult(new string[] { null }, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: new string[] { null });
// Act
var outValue = vpr.ConvertTo(typeof(int));
var outValue = valueProviderResult.ConvertTo(typeof(int));
// Assert
Assert.Null(outValue);
@ -117,10 +120,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsNullIfTryingToConvertEmptyArrayToSingleElement()
{
// Arrange
var vpr = new ValueProviderResult(new int[0], "", CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(new int[0], string.Empty, CultureInfo.InvariantCulture);
// Act
var outValue = vpr.ConvertTo(typeof(int));
var outValue = valueProviderResult.ConvertTo(typeof(int));
// Assert
Assert.Null(outValue);
@ -132,10 +135,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsNullIfTrimmedValueIsEmptyString(object value)
{
// Arrange
var vpr = new ValueProviderResult(value, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: value);
// Act
var outValue = vpr.ConvertTo(typeof(int));
var outValue = valueProviderResult.ConvertTo(typeof(int));
// Assert
Assert.Null(outValue);
@ -145,12 +148,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsNullIfTrimmedValueIsEmptyString()
{
// Arrange
var vpr = new ValueProviderResult(rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
// Act
var outValue = vpr.ConvertTo(typeof(int[]));
var outValue = valueProviderResult.ConvertTo(typeof(int[]));
// Assert
Assert.Null(outValue);
@ -160,7 +161,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfArrayElementIsIntegerAndDestinationTypeIsEnum()
{
// Arrange
var result = new ValueProviderResult(new object[] { 1 }, null, CultureInfo.InvariantCulture);
var result = new ValueProviderResult(rawValue: new object[] { 1 });
// Act
var outValue = result.ConvertTo(typeof(IntEnum));
@ -194,7 +195,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
object expected)
{
// Arrange
var result = new ValueProviderResult(new object[] { input }, null, CultureInfo.InvariantCulture);
var result = new ValueProviderResult(rawValue: new object[] { input });
// Act
var outValue = result.ConvertTo(enumType);
@ -207,10 +208,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfArrayElementIsStringValueAndDestinationTypeIsEnum()
{
// Arrange
var vpr = new ValueProviderResult(new object[] { "1" }, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "1" });
// Act
var outValue = vpr.ConvertTo(typeof(IntEnum));
var outValue = valueProviderResult.ConvertTo(typeof(IntEnum));
// Assert
Assert.Equal(outValue, IntEnum.Value1);
@ -220,10 +221,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfArrayElementIsStringKeyAndDestinationTypeIsEnum()
{
// Arrange
var vpr = new ValueProviderResult(new object[] { "Value1" }, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "Value1" });
// Act
var outValue = vpr.ConvertTo(typeof(IntEnum));
var outValue = valueProviderResult.ConvertTo(typeof(IntEnum));
// Assert
Assert.Equal(outValue, IntEnum.Value1);
@ -233,10 +234,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableInteger()
{
// Arrange
var vpr = new ValueProviderResult("12", null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: "12");
// Act
var outValue = vpr.ConvertTo(typeof(int?));
var outValue = valueProviderResult.ConvertTo(typeof(int?));
// Assert
Assert.Equal(12, outValue);
@ -246,10 +247,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableDouble()
{
// Arrange
var vpr = new ValueProviderResult("12.5", null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: "12.5");
// Act
var outValue = vpr.ConvertTo(typeof(double?));
var outValue = valueProviderResult.ConvertTo(typeof(double?));
// Assert
Assert.Equal(12.5, outValue);
@ -259,10 +260,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableInteger()
{
// Arrange
var vpr = new ValueProviderResult(12M, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: 12M);
// Act
var outValue = vpr.ConvertTo(typeof(int?));
var outValue = valueProviderResult.ConvertTo(typeof(int?));
// Assert
Assert.Equal(12, outValue);
@ -272,10 +273,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableDouble()
{
// Arrange
var vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: 12.5M);
// Act
var outValue = vpr.ConvertTo(typeof(double?));
var outValue = valueProviderResult.ConvertTo(typeof(double?));
// Assert
Assert.Equal(12.5, outValue);
@ -285,10 +286,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableInteger()
{
// Arrange
var vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: 12.5M);
// Act
var outValue = vpr.ConvertTo(typeof(int?));
var outValue = valueProviderResult.ConvertTo(typeof(int?));
// Assert
Assert.Equal(12, outValue);
@ -298,10 +299,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableLong()
{
// Arrange
var vpr = new ValueProviderResult(12.5M, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: 12.5M);
// Act
var outValue = vpr.ConvertTo(typeof(long?));
var outValue = valueProviderResult.ConvertTo(typeof(long?));
// Assert
Assert.Equal(12L, outValue);
@ -311,10 +312,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType()
{
// Arrange
var vpr = new ValueProviderResult(new object[] { "some string" }, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: new object[] { "some string" });
// Act
var outValue = vpr.ConvertTo(typeof(string));
var outValue = valueProviderResult.ConvertTo(typeof(string));
// Assert
Assert.Equal("some string", outValue);
@ -327,10 +328,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertTo_ConvertsEnumArrays(object value)
{
// Arrange
var vpr = new ValueProviderResult(value, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: value);
// Act
var outValue = vpr.ConvertTo(typeof(IntEnum[]));
var outValue = valueProviderResult.ConvertTo(typeof(IntEnum[]));
// Assert
var result = Assert.IsType<IntEnum[]>(outValue);
@ -346,10 +347,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertTo_ConvertsFlagsEnumArrays(object value, FlagsEnum[] expected)
{
// Arrange
var vpr = new ValueProviderResult(value, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: value);
// Act
var outValue = vpr.ConvertTo(typeof(FlagsEnum[]));
var outValue = valueProviderResult.ConvertTo(typeof(FlagsEnum[]));
// Assert
var result = Assert.IsType<FlagsEnum[]>(outValue);
@ -363,10 +364,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var original = new[] { "some string" };
var vpr = new ValueProviderResult(original, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: original);
// Act
var outValue = vpr.ConvertTo(typeof(string[]));
var outValue = valueProviderResult.ConvertTo(typeof(string[]));
// Assert
Assert.Same(original, outValue);
@ -379,21 +380,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToThrowsIfConverterThrows(Type destinationType)
{
// Arrange
var vpr = new ValueProviderResult("this-is-not-a-valid-value", null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: "this-is-not-a-valid-value");
// Act & Assert
var ex = Assert.Throws(typeof(FormatException), () => vpr.ConvertTo(destinationType));
var ex = Assert.Throws(typeof(FormatException), () => valueProviderResult.ConvertTo(destinationType));
}
[Fact]
public void ConvertToThrowsIfNoConverterExists()
{
// Arrange
var vpr = new ValueProviderResult("x", null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: "x");
var destinationType = typeof(MyClassWithoutConverter);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => vpr.ConvertTo(destinationType));
var ex = Assert.Throws<InvalidOperationException>(() => valueProviderResult.ConvertTo(destinationType));
Assert.Equal("The parameter conversion from type 'System.String' to type " +
"'Microsoft.AspNet.Mvc.ModelBinding.ValueProviderResultTest+MyClassWithoutConverter' " +
"failed because no type converter can convert between these types.",
@ -405,22 +406,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var original = "12,5";
var vpr = new ValueProviderResult(original, null, new CultureInfo("en-GB"));
var valueProviderResult = new ValueProviderResult(
rawValue: original,
attemptedValue: null,
culture: new CultureInfo("en-GB"));
var frCulture = new CultureInfo("fr-FR");
// Act
var cultureResult = vpr.ConvertTo(typeof(decimal), frCulture);
var cultureResult = valueProviderResult.ConvertTo(typeof(decimal), frCulture);
// Assert
Assert.Equal(12.5M, cultureResult);
Assert.Throws<FormatException>(() => vpr.ConvertTo(typeof(decimal)));
Assert.Throws<FormatException>(() => valueProviderResult.ConvertTo(typeof(decimal)));
}
[Fact]
public void CulturePropertyDefaultsToInvariantCulture()
{
// Arrange
var result = new ValueProviderResult(null, null, null);
var result = new ValueProviderResult(rawValue: null, attemptedValue: null, culture: null);
// Act & assert
Assert.Same(CultureInfo.InvariantCulture, result.Culture);
@ -431,7 +435,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertToCanConvertIntrinsics<T>(object initialValue, T expectedValue)
{
// Arrange
var result = new ValueProviderResult(initialValue, "", CultureInfo.InvariantCulture);
var result = new ValueProviderResult(initialValue, string.Empty, CultureInfo.InvariantCulture);
// Act & Assert
Assert.Equal(expectedValue, result.ConvertTo(typeof(T)));
@ -471,7 +475,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertTo_Throws_IfValueIsNotStringData(Type destinationType)
{
// Arrange
var result = new ValueProviderResult(new MyClassWithoutConverter(), "", CultureInfo.InvariantCulture);
var result = new ValueProviderResult(
new MyClassWithoutConverter(),
string.Empty,
CultureInfo.InvariantCulture);
// Act
var ex = Assert.Throws<InvalidOperationException>(() => result.ConvertTo(destinationType));
@ -489,7 +496,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var value = "Hello world";
var destinationType = typeof(MyClassWithoutConverter);
var result = new ValueProviderResult(value, "", CultureInfo.InvariantCulture);
var result = new ValueProviderResult(value, string.Empty, CultureInfo.InvariantCulture);
// Act
var ex = Assert.Throws<InvalidOperationException>(() => result.ConvertTo(destinationType));
@ -513,10 +520,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public void ConvertTo_ConvertsEnumFlags(object value, object expected)
{
// Arrange
var vpr = new ValueProviderResult(value, null, CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: value);
// Act
var outValue = (FlagsEnum)vpr.ConvertTo(typeof(FlagsEnum));
var outValue = (FlagsEnum)valueProviderResult.ConvertTo(typeof(FlagsEnum));
// Assert
Assert.Equal(expected, outValue);

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
// Returns true because it understands the metadata type.
// Returns non-null because it understands the metadata type.
Assert.NotNull(binderResult);
Assert.True(binderResult.IsFatalError);
Assert.False(binderResult.IsModelSet);
@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
// Returns true because it understands the metadata type.
// Returns non-null because it understands the metadata type.
Assert.NotNull(binderResult);
Assert.True(binderResult.IsFatalError);
Assert.False(binderResult.IsModelSet);

View File

@ -34,7 +34,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
Assert.Equal("foo", binderResult.Key);
Assert.Null(binderResult.Model);
Assert.Empty(bindingContext.ModelState); // No submitted value for "foo".
var modelState = Assert.Single(bindingContext.ModelState);
Assert.Equal("foo", modelState.Key);
Assert.NotNull(modelState.Value.Value);
Assert.Equal(value, modelState.Value.Value.RawValue);
}
[Fact]
@ -64,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Arrange
var expected = TestPlatformHelper.IsMono ?
"Invalid length." :
"The supplied value is invalid for foo.";
"The value '\"Fys1\"' is not valid for foo.";
var valueProvider = new SimpleHttpValueProvider()
{

View File

@ -490,9 +490,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
var validationContext = testValidationContext.ModelValidationContext;
// Set the value on model state as a model binder would.
validationContext.ModelState.SetModelValue(
"user.Password",
Mock.Of<ValueProviderResult>());
var valueProviderResult = new ValueProviderResult(rawValue: "password");
validationContext.ModelState.SetModelValue("user.Password", valueProviderResult);
var validator = new DefaultObjectValidator(
testValidationContext.ExcludeFilters,
testValidationContext.ModelMetadataProvider);
@ -513,6 +512,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
Assert.Equal("user.Password", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(entry.Value.ValidationState, ModelValidationState.Skipped);
Assert.Same(valueProviderResult, entry.Value.Value);
}
private class Person2
@ -536,7 +536,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
validationContext.ModelState.Add("person.Address", new ModelState());
var validator = new DefaultObjectValidator(
testValidationContext.ExcludeFilters,
testValidationContext.ExcludeFilters,
testValidationContext.ModelMetadataProvider);
var modelExplorer = testValidationContext.ModelValidationContext.ModelExplorer;
var topLevelValidationNode = new ModelValidationNode(

View File

@ -166,10 +166,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
viewContext.ViewData[nameof(Model.Name)] = "ignored ViewData value";
var valueProviderResult = new ValueProviderResult(
rawValue,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue);
viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
// Act
@ -201,10 +198,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), "ignored model value");
var valueProviderResult = new ValueProviderResult(
rawValue,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue);
viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
// Act
@ -254,10 +248,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), rawValue);
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
// Act
@ -285,10 +276,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewContext = GetViewContext<Model>(model, metadataProvider);
viewContext.ViewData[nameof(Model.Name)] = rawValue;
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
// Act
@ -313,10 +301,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewContext = GetViewContext<Model>(model, metadataProvider);
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Name), valueProviderResult);
// Act
@ -376,10 +361,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var modelExplorer =
metadataProvider.GetModelExplorerForType(typeof(List<string>), new List<string>(rawValue));
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
// Act
@ -407,10 +389,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewContext = GetViewContext<Model>(model, metadataProvider);
viewContext.ViewData[nameof(Model.Collection)] = rawValue;
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
// Act
@ -438,10 +417,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewContext = GetViewContext<Model>(model, metadataProvider);
var valueProviderResult = new ValueProviderResult(
rawValue: null,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue: null);
viewContext.ModelState.SetModelValue(nameof(Model.Collection), valueProviderResult);
// Act
@ -552,10 +528,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var htmlGenerator = GetGenerator(metadataProvider);
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
var valueProviderResult = new ValueProviderResult(
rawValue,
attemptedValue: null,
culture: CultureInfo.InvariantCulture);
var valueProviderResult = new ValueProviderResult(rawValue);
viewContext.ModelState.SetModelValue(propertyName, valueProviderResult);
// Act

View File

@ -3,12 +3,9 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
using Xunit;
namespace Microsoft.AspNet.Mvc.IntegrationTests
@ -71,10 +68,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
BindingSource = BindingSource.Body
@ -99,8 +96,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
Assert.Null(modelBindingResult.Model);
Assert.Empty(modelState.Keys);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Empty(entry.Key);
Assert.Null(entry.Value.Value.RawValue);
}
private class Person4
@ -111,14 +111,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
}
[Fact]
public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_AddsModelStateError()
public async Task FromBodyAndRequiredOnValueTypeProperty_EmptyBody_JsonFormatterAddsModelStateError()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
},
@ -141,16 +141,16 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
var boundPerson = Assert.IsType<Person4>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.False(modelState.IsValid);
// The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and
// use that along with the path. The expected key here would be CustomParameter.Address.
var key = Assert.Single(modelState.Keys, k => k == "");
var error = Assert.Single(modelState[""].Errors);
Assert.StartsWith(
"No JSON content found and type 'System.Int32' is not nullable.",
error.Exception.Message);
Assert.False(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal(string.Empty, entry.Key);
var error = Assert.Single(entry.Value.Errors);
Assert.NotNull(error.Exception);
// Json.NET currently throws an exception starting with "No JSON content found and type 'System.Int32' is
// not nullable." but do not tie test to a particular Json.NET build.
Assert.NotEmpty(error.Exception.Message);
}
private class Person2
@ -167,16 +167,16 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public int Zip { get; set; }
}
[Theory(Skip = "There should be entries for all model properties which are bound. #2445")]
[Theory]
[InlineData("{ \"Zip\" : 123 }")]
[InlineData("{}")]
public async Task FromBodyOnTopLevelProperty_RequiredOnSubProperty_AddsModelStateError(string inputText)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
},
@ -200,14 +200,15 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var boundPerson = Assert.IsType<Person2>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.False(modelState.IsValid);
Assert.Equal(2, modelState.Keys.Count);
var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip");
Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState);
var address = Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address").Value;
Assert.Equal(ModelValidationState.Unvalidated, address.ValidationState);
var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street");
Assert.Equal(ModelValidationState.Invalid, modelState[street].ValidationState);
var error = Assert.Single(modelState[street].Errors);
var street = Assert.Single(modelState, kvp => kvp.Key == "CustomParameter.Address.Street").Value;
Assert.Equal(ModelValidationState.Invalid, street.ValidationState);
var error = Assert.Single(street.Errors);
Assert.Equal("The Street field is required.", error.ErrorMessage);
}
@ -225,16 +226,16 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public int Zip { get; set; }
}
[Theory(Skip = "There should be entries for all model properties which are bound. #2445")]
[Theory]
[InlineData("{ \"Street\" : \"someStreet\" }")]
[InlineData("{}")]
public async Task FromBodyOnProperty_RequiredOnValueTypeSubProperty_AddsModelStateError(string inputText)
public async Task FromBodyOnProperty_Succeeds_IgnoresRequiredOnValueTypeSubProperty(string inputText)
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
},
@ -255,20 +256,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Assert
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
var boundPerson = Assert.IsType<Person3>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.False(modelState.IsValid);
var street = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Street");
Assert.Equal(ModelValidationState.Valid, modelState[street].ValidationState);
Assert.IsType<Person3>(modelBindingResult.Model);
// The error with an empty key is a bug(#2416) in our implementation which does not append the prefix and
// use that along with the path. The expected key here would be Address.
var zip = Assert.Single(modelState.Keys, k => k == "CustomParameter.Address.Zip");
Assert.Equal(ModelValidationState.Valid, modelState[zip].ValidationState);
var error = Assert.Single(modelState[""].Errors);
Assert.StartsWith(
"Required property 'Zip' not found in JSON. Path ''",
error.Exception.Message);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState);
Assert.Equal("CustomParameter.Address", entry.Key);
}
}
}

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public byte[] Token { get; set; }
}
[Theory(Skip = "ModelState.Value not set due to #2445")]
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task BindProperty_WithData_GetsBound(bool fallBackScenario)
@ -61,19 +61,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Keys.Count);
Assert.Single(modelState.Keys, k => k == prefix);
Assert.Single(modelState.Keys, k => k == queryStringKey);
var key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[0]");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
key = Assert.Single(modelState.Keys, k => k == queryStringKey + "[1]");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal(queryStringKey, entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
Assert.Equal(value, entry.Value.Value.AttemptedValue);
Assert.Equal(value, entry.Value.Value.RawValue);
}
[Fact]
@ -109,19 +102,18 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Empty(modelState.Keys);
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
[Fact]
public async Task BindParameter_WithData_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(byte[])
};
@ -151,16 +143,11 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(3, modelState.Count);
Assert.Single(modelState.Keys, k => k == "CustomParameter[0]");
Assert.Single(modelState.Keys, k => k == "CustomParameter[1]");
var key = Assert.Single(modelState.Keys, k => k == "CustomParameter[2]");
Assert.NotNull(modelState[key].Value);
Assert.Equal(value, modelState[key].Value.AttemptedValue);
Assert.Equal(expectedValue, modelState[key].Value.RawValue);
Assert.Empty(modelState[key].Errors);
var entry = Assert.Single(modelState);
Assert.Equal("CustomParameter", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(value, entry.Value.Value.AttemptedValue);
Assert.Equal(value, entry.Value.Value.RawValue);
}
}
}

View File

@ -27,12 +27,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public FormCollection FileCollection { get; set; }
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
[Fact]
public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
@ -69,12 +69,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Count);
Assert.Single(modelState.Keys, k => k == "Address.Zip");
var key = Assert.Single(modelState.Keys, k => k == "Address.File");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
var entry = Assert.Single(modelState);
Assert.Equal("Address.Zip", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
}
[Fact]
@ -82,15 +80,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
// Setting a custom parameter prevents it from falling back to an empty prefix.
BinderModelName = "CustomParameter",
},
ParameterType = typeof(FormCollection)
};
@ -113,7 +110,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model
var formCollection = Assert.IsType<FormCollection>(modelBindingResult.Model);
Assert.NotNull(formCollection);
var file = Assert.Single(formCollection.Files);
Assert.NotNull(file);
Assert.Equal("form-data; name=CustomParameter; filename=text.txt", file.ContentDisposition);
@ -122,10 +118,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
// Validation should be skipped because we do not validate any parameters and since IFormFile is not
// IValidatableObject, we should have no entries in the model state dictionary.
Assert.Empty(modelState.Keys);
Assert.Empty(modelState);
}
[Fact]
@ -133,21 +126,18 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(FormCollection)
};
// No data is passed.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
});
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var modelState = new ModelStateDictionary();
@ -162,7 +152,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
Assert.Empty(modelState);
// FormCollection
Assert.Empty(collection);

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public IFormFile File { get; set; }
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
[Fact]
public async Task BindProperty_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal(2, modelState.Count);
Assert.Single(modelState.Keys, k => k == "Address.Zip");
var key = Assert.Single(modelState.Keys, k => k == "Address.File");
Assert.NotNull(modelState[key].Value); // should be non null bug #2445.
Assert.NotNull(modelState[key].Value);
Assert.Empty(modelState[key].Errors);
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
}
@ -81,15 +81,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
// Setting a custom parameter prevents it from falling back to an empty prefix.
BinderModelName = "CustomParameter",
},
ParameterType = typeof(IFormFile)
};
@ -119,10 +118,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
// Validation should be skipped because we do not validate any parameters and since IFormFile is not
// IValidatableObject, we should have no entries in the model state dictionary.
Assert.Empty(modelState.Keys);
var entry = Assert.Single(modelState);
Assert.Equal("CustomParameter", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
Assert.Null(entry.Value.Value.AttemptedValue);
Assert.Equal(file, entry.Value.Value.RawValue);
}
[Fact]

View File

@ -18,8 +18,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// This isn't an especially useful scenario - but it exercises what happens when you
// try to use a Collection of something that is bound greedily by model-type.
//
// In this example we choose IFormCollection - because IFormCollection has a dedicated
// model binder.
// In this example we choose IFormCollection because IFormCollection has a dedicated
// model binder.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_WithPrefix_Success()
{
@ -47,12 +47,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<IFormCollection>>(modelBindingResult.Model);
Assert.Equal(1, model.Count);
Assert.NotNull(model[0]);
var formCollection = Assert.Single(model);
Assert.NotNull(formCollection);
Assert.Equal(0, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
}
// This isn't an especially useful scenario - but it exercises what happens when you
@ -86,12 +86,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<IFormCollection>>(modelBindingResult.Model);
Assert.Equal(1, model.Count);
Assert.NotNull(model[0]);
var formCollection = Assert.Single(model);
Assert.NotNull(formCollection);
Assert.Equal(0, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
}
// This isn't an especially useful scenario - but it exercises what happens when you
@ -217,7 +217,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, int>[])
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter[0][0].Key=key0&parameter[0][0].Value=10");
});
@ -263,7 +263,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, int>[])
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?[0][0].Key=key0&[0][0].Value=10");
});
@ -309,7 +309,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, int>[])
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?");
});
@ -345,7 +345,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(ICollection<KeyValuePair<string, int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter[0].Key=key0&parameter[0].Value=10");
});
@ -390,7 +390,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(ICollection<KeyValuePair<string, int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10");
});
@ -435,7 +435,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Collection<KeyValuePair<string, int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?");
});
@ -471,7 +471,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, List<int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString(
"?parameter[0].Key=key0&parameter[0].Value[0]=10&parameter[0].Value[1]=11");
@ -521,7 +521,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, List<int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?[0].Key=key0&[0].Value[0]=10&[0].Value[1]=11");
});
@ -570,7 +570,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Dictionary<string, List<int>>)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext((request) =>
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?");
});

View File

@ -65,7 +65,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("The Street field is required.", error.ErrorMessage);
}
[Fact(Skip = "ModelState.Value not set due to #2445")]
[Fact]
public async Task BindPropertyFromHeader_WithPrefix_GetsBound()
{
// Arrange
@ -104,19 +104,17 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(3, modelState.Count);
Assert.Single(modelState.Keys, k => k == "prefix.Address");
Assert.Single(modelState.Keys, k => k == "prefix");
var key = Assert.Single(modelState.Keys, k => k == "prefix.Address.Header");
Assert.NotNull(modelState[key].Value);
Assert.Equal("someValue", modelState[key].Value.RawValue);
Assert.Equal("someValue", modelState[key].Value.AttemptedValue);
var entry = Assert.Single(modelState);
Assert.Equal("prefix.Address.Header", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
Assert.Equal("someValue", entry.Value.Value.AttemptedValue);
Assert.Equal("someValue", entry.Value.Value.RawValue);
}
// The scenario is interesting as we to bind the top level model we fallback to empty prefix,
// and hence the model state keys have an empty prefix.
[Fact(Skip = "ModelState.Value not set due to #2445.")]
[Fact]
public async Task BindPropertyFromHeader_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
@ -128,11 +126,8 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = typeof(Person)
};
// Do not add any headers.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request => {
request.Headers.Add("Header", new[] { "someValue" });
});
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request => request.Headers.Add("Header", new[] { "someValue" }));
var modelState = new ModelStateDictionary();
// Act
@ -152,27 +147,29 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
Assert.Equal(2, modelState.Count);
Assert.Single(modelState.Keys, k => k == "Address");
var key = Assert.Single(modelState.Keys, k => k == "Address.Header");
Assert.NotNull(modelState[key].Value);
Assert.Equal("someValue", modelState[key].Value.RawValue);
Assert.Equal("someValue", modelState[key].Value.AttemptedValue);
var entry = Assert.Single(modelState);
Assert.Equal("Address.Header", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
Assert.Equal("someValue", entry.Value.Value.AttemptedValue);
Assert.Equal("someValue", entry.Value.Value.RawValue);
}
[Theory(Skip = "Greedy Model Binders should add a value in model state #2445.")]
[Theory]
[InlineData(typeof(string[]), "value1, value2, value3")]
[InlineData(typeof(string), "value")]
public async Task BindParameterFromHeader_WithData_WithPrefix_ModelGetsBound(Type modelType, string value)
{
// Arrange
var expectedValue = value.Split(',').Select(v => v.Trim());
var expectedValue = modelType == typeof(string) ?
(object)value :
(object)value.Split(',').Select(v => v.Trim()).ToArray();
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
BindingInfo = new BindingInfo
{
BinderModelName = "CustomParameter",
BindingSource = BindingSource.Header
@ -180,7 +177,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
ParameterType = modelType
};
Action<HttpRequest> action = (r) => r.Headers.Add("CustomParameter", new[] { value });
Action<HttpRequest> action = r => r.Headers.Add("CustomParameter", new[] { value });
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(action);
// Do not add any headers.
@ -202,12 +199,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelState
Assert.True(modelState.IsValid);
var key = Assert.Single(modelState.Keys);
Assert.Equal("CustomParameter", key);
Assert.NotNull(modelState[key].Value);
Assert.Equal(expectedValue, modelState[key].Value.RawValue);
Assert.Equal(value, modelState[key].Value.AttemptedValue);
var entry = Assert.Single(modelState);
Assert.Equal("CustomParameter", entry.Key);
Assert.Empty(entry.Value.Errors);
Assert.Equal(ModelValidationState.Valid, entry.Value.ValidationState);
Assert.Equal(value, entry.Value.Value.AttemptedValue);
Assert.Equal(expectedValue, entry.Value.Value.RawValue);
}
}
}

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public string Street { get; set; }
}
[Fact(Skip = "ModelState.Value not set due to #2445.")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_Success()
{
// Arrange
@ -85,13 +85,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", entry.Value.AttemptedValue);
Assert.Equal("bill", entry.Value.RawValue);
// These fail due to #2445
entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Address").Value;
Assert.Null(entry.Value.AttemptedValue); // ModelState entries for body don't include original text.
Assert.Same(model.Customer.Address, entry.Value.RawValue);
}
[Fact(Skip = "ModelState.Value not set due to #2445.")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithEmptyPrefix_Success()
{
// Arrange
@ -132,7 +131,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", entry.Value.AttemptedValue);
Assert.Equal("bill", entry.Value.RawValue);
// These fail due to #2445
entry = Assert.Single(modelState, e => e.Key == "Customer.Address").Value;
Assert.Null(entry.Value.AttemptedValue); // ModelState entries for body don't include original text.
Assert.Same(model.Customer.Address, entry.Value.RawValue);
@ -170,11 +168,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", model.Customer.Name);
Assert.Null(model.Customer.Address);
Assert.Equal(1, modelState.Count);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value;
var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Address").Value;
Assert.Null(entry.Value.AttemptedValue);
Assert.Null(entry.Value.RawValue);
entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value;
Assert.Equal("bill", entry.Value.AttemptedValue);
Assert.Equal("bill", entry.Value.RawValue);
}
@ -446,7 +447,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public byte[] Token { get; set; }
}
[Fact(Skip = "Greedy model binders should set value. #2445")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithPrefix_Success()
{
// Arrange
@ -478,7 +479,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", model.Customer.Name);
Assert.Equal(ByteArrayContent, model.Customer.Token);
Assert.Equal(2, modelState.Count); // This fails due to #2445
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
@ -486,13 +487,12 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", entry.Value.AttemptedValue);
Assert.Equal("bill", entry.Value.RawValue);
// These fail due to #2445
entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Token").Value;
Assert.Equal(ByteArrayEncoded, entry.Value.AttemptedValue);
Assert.Equal(ByteArrayEncoded, entry.Value.RawValue);
}
[Fact(Skip = "Greedy model binders should set value. #2445")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithEmptyPrefix_Success()
{
// Arrange
@ -544,17 +544,13 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Order1)
ParameterType = typeof(Order3)
};
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter.Customer.Name=bill");
// This is set so that the input formatter does not add an error to model state.
// Thus this prevents addition of an extra error unrelated to the test scenario.
request.ContentType = "application/json";
});
var modelState = new ModelStateDictionary();
@ -566,10 +562,10 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.NotNull(modelBindingResult);
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Order1>(modelBindingResult.Model);
var model = Assert.IsType<Order3>(modelBindingResult.Model);
Assert.NotNull(model.Customer);
Assert.Equal("bill", model.Customer.Name);
Assert.Null(model.Customer.Address);
Assert.Null(model.Customer.Token);
Assert.Equal(1, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
@ -594,7 +590,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
public IEnumerable<IFormFile> Documents { get; set; }
}
[Fact(Skip = "Greedy model binders should set value. #2445")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_Success()
{
// Arrange
@ -626,7 +622,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", model.Customer.Name);
Assert.Single(model.Customer.Documents);
Assert.Equal(2, modelState.Count); // This fails due to #2445
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
@ -639,7 +635,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Same(model.Customer.Documents, entry.Value.RawValue);
}
[Fact(Skip = "Greedy model binders should set value. #2445")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithEmptyPrefix_Success()
{
// Arrange
@ -717,11 +713,16 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("bill", model.Customer.Name);
Assert.Empty(model.Customer.Documents);
Assert.Equal(1, modelState.Count);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value;
var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Documents").Value;
Assert.Null(entry.Value.AttemptedValue);
var documents = Assert.IsAssignableFrom<IEnumerable<IFormFile>>(entry.Value.RawValue);
Assert.Empty(documents);
entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value;
Assert.Equal("bill", entry.Value.AttemptedValue);
Assert.Equal("bill", entry.Value.RawValue);
}
@ -1529,7 +1530,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// If a nested POCO object has all properties bound from a greedy source, then it should be populated
// if the top-level object is created.
[Fact(Skip = "ModelState.Value not set due to #2445.")]
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithAllGreedyBoundProperties()
{
// Arrange
@ -1567,7 +1568,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "Customer.Address").Value;
Assert.Null(entry.Value.AttemptedValue); // This fails due to #2445
Assert.Null(entry.Value.AttemptedValue);
Assert.Same(model.Customer.Address, entry.Value.RawValue);
}

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Name = "parameter",
ParameterType = typeof(Order1)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter.CustomerName=bill");
@ -1020,6 +1020,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
private class Address
{
public int Street { get; set; }
public string State { get; set; }
[Range(10000, 99999)]
@ -1032,30 +1033,32 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public string Name { get; set; }
}
[Fact]
public async Task TypeBasedExclusion_ForBodyAndNonBodyBoundModels()
{
// Arrange
var parameter = new ParameterDescriptor()
var parameter = new ParameterDescriptor
{
Name = "parameter",
ParameterType = typeof(Order11)
};
MvcOptions testOptions = null;
var input = "{\"OfficeAddress.Zip\":\"45\"}";
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString =
new QueryString("?HomeAddress.Country.Name=US&ShippingAddresses[0].Zip=45&HomeAddress.Zip=46");
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input));
request.ContentType = "application/json";
},
options => {
options.ValidationExcludeFilters.Add(typeof(Address));
testOptions = options;
});
var input = "{\"Zip\":\"47\"}";
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(
request =>
{
request.QueryString =
new QueryString("?HomeAddress.Country.Name=US&ShippingAddresses[0].Zip=45&HomeAddress.Zip=46");
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input));
request.ContentType = "application/json";
},
options =>
{
options.ValidationExcludeFilters.Add(typeof(Address));
testOptions = options;
});
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder(testOptions);
var modelState = new ModelStateDictionary();
@ -1063,7 +1066,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
Assert.Equal(3, modelState.Count);
Assert.Equal(4, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
@ -1081,6 +1084,14 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.Equal("46", entry.Value.AttemptedValue);
Assert.Equal("46", entry.Value.RawValue);
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
entry = Assert.Single(modelState, e => e.Key == "OfficeAddress").Value;
Assert.Null(entry.Value.AttemptedValue);
var address = Assert.IsType<Address>(entry.Value.RawValue);
Assert.Equal(47, address.Zip);
// Address itself is not excluded from validation.
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
}
private static void AssertRequiredError(string key, ModelError error)