Include parameter type's attributes in ModelMetadata

- #7595
- #7595 relates to #7350 but does not have the same root cause
  - did _not_ revert the src changes in #7350 fix (d995b0418a)
- make non-`[Obsolete]` `ModelAttributes` constructor overload `internal`
  - should generally use `static` methods and not any constructor
  - change some unit tests to use `[Obsolete]` constructor overloads (with suppressions)
- fix test `ParameterBinderExtensions` to use current `ParameterBinder.BindModelAsync(...)` overload
  - found some tests updated `IModelMetadataProvider`, `MvcOptions`, etc. instances but didn't register them in DI
- extend `ModelBindingTestHelper` and `ModelBindingTestContext`
  - reorder some tests to use correct `MvcOptions` and `IModelMetadataProvider` everywhere
  - fixes above issues

nits:
- take a few VS suggestions
- remove an old comment indended only for PR "Reviewers:"
This commit is contained in:
Doug Bunting 2018-04-13 18:57:13 -07:00
parent 864a4e39c3
commit e0e96ce53b
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
23 changed files with 446 additions and 225 deletions

View File

@ -5,8 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
@ -56,7 +54,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// If this instance represents a parameter, the set of attributes for that parameter.
/// Otherwise, <c>null</c>.
/// </param>
public ModelAttributes(IEnumerable<object> typeAttributes, IEnumerable<object> propertyAttributes, IEnumerable<object> parameterAttributes)
internal ModelAttributes(
IEnumerable<object> typeAttributes,
IEnumerable<object> propertyAttributes,
IEnumerable<object> parameterAttributes)
{
if (propertyAttributes != null)
{
@ -73,7 +74,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
else if (parameterAttributes != null)
{
// Represents a parameter
Attributes = ParameterAttributes = parameterAttributes.ToArray();
if (typeAttributes == null)
{
throw new ArgumentNullException(nameof(typeAttributes));
}
ParameterAttributes = parameterAttributes.ToArray();
TypeAttributes = typeAttributes.ToArray();
Attributes = ParameterAttributes.Concat(TypeAttributes).ToArray();
}
else if (typeAttributes != null)
{
@ -89,7 +97,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <summary>
/// Gets the set of all attributes. If this instance represents the attributes for a property, the attributes
/// on the property definition are before those on the property's <see cref="Type"/>.
/// on the property definition are before those on the property's <see cref="Type"/>. If this instance
/// represents the attributes for a parameter, the attributes on the parameter definition are before those on
/// the parameter's <see cref="Type"/>.
/// </summary>
public IReadOnlyList<object> Attributes { get; }
@ -106,10 +116,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public IReadOnlyList<object> ParameterAttributes { get; }
/// <summary>
/// Gets the set of attributes on the <see cref="Type"/>. If this instance represents a property,
/// then <see cref="TypeAttributes"/> contains attributes retrieved from
/// <see cref="PropertyInfo.PropertyType"/>. If this instance represents a parameter, then
/// the value is <c>null</c>.
/// Gets the set of attributes on the <see cref="Type"/>. If this instance represents a property, then
/// <see cref="TypeAttributes"/> contains attributes retrieved from <see cref="PropertyInfo.PropertyType"/>.
/// If this instance represents a parameter, then contains attributes retrieved from
/// <see cref="ParameterInfo.ParameterType"/>.
/// </summary>
public IReadOnlyList<object> TypeAttributes { get; }
@ -120,7 +130,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </param>
/// <param name="property">A <see cref="PropertyInfo"/> for which attributes need to be resolved.
/// </param>
/// <returns>A <see cref="ModelAttributes"/> instance with the attributes of the property.</returns>
/// <returns>
/// A <see cref="ModelAttributes"/> instance with the attributes of the property and its <see cref="Type"/>.
/// </returns>
public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property)
{
if (type == null)
@ -146,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
}
}
return new ModelAttributes(typeAttributes, propertyAttributes, null);
return new ModelAttributes(typeAttributes, propertyAttributes, parameterAttributes: null);
}
/// <summary>
@ -170,18 +182,27 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
attributes = attributes.Concat(metadataType.GetTypeInfo().GetCustomAttributes());
}
return new ModelAttributes(attributes, null, null);
return new ModelAttributes(attributes, propertyAttributes: null, parameterAttributes: null);
}
/// <summary>
/// Gets the attributes for the given <paramref name="parameterInfo"/>.
/// </summary>
/// <param name="parameterInfo">The <see cref="ParameterInfo"/> for which attributes need to be resolved.
/// <param name="parameterInfo">
/// The <see cref="ParameterInfo"/> for which attributes need to be resolved.
/// </param>
/// <returns>A <see cref="ModelAttributes"/> instance with the attributes of the <see cref="ParameterInfo"/>.</returns>
/// <returns>
/// A <see cref="ModelAttributes"/> instance with the attributes of the parameter and its <see cref="Type"/>.
/// </returns>
public static ModelAttributes GetAttributesForParameter(ParameterInfo parameterInfo)
{
return new ModelAttributes(null, null, parameterInfo.GetCustomAttributes());
// Prior versions called IModelMetadataProvider.GetMetadataForType(...) and therefore
// GetAttributesForType(...) for parameters. Maintain that set of attributes (including those from an
// ModelMetadataTypeAttribute reference) for back-compatibility.
var typeAttributes = GetAttributesForType(parameterInfo.ParameterType).TypeAttributes;
var parameterAttributes = parameterInfo.GetCustomAttributes();
return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes);
}
private static Type GetMetadataType(Type type)

View File

@ -176,7 +176,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
// Arrange
var attributes = new object[] { new ModelBinderAttribute(typeof(object)), new ControllerAttribute(), new BindNeverAttribute(), };
var modelAttributes = new ModelAttributes(Enumerable.Empty<object>(), null, null);
var propertyFilterProvider = Mock.Of<IPropertyFilterProvider>();
var modelType = typeof(Guid);
var provider = new TestModelMetadataProvider();

View File

@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();
@ -302,7 +302,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();
@ -325,7 +325,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();
@ -348,7 +348,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();
@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var context = new BindingMetadataProviderContext(
ModelMetadataIdentity.ForParameter(ParameterInfos.SampleParameterInfo),
new ModelAttributes(null, null, parameterAttributes));
new ModelAttributes(Array.Empty<object>(), null, parameterAttributes));
var provider = new DefaultBindingMetadataProvider();

View File

@ -17,12 +17,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
{
private static readonly Dictionary<string, StringValues> _backingStore = new Dictionary<string, StringValues>
{
// Reviewers: A fair number of cases in the following dataset seem impossible for jQuery.ajax() to send
// given how $.ajax() and $.param() are defined i.e. $.param() won't add a bare name after ']'. Also, the
// names we generate for <input/> elements are always valid. Should we remove these weird / invalid cases
// e.g. normalizing "property[]Value"? (That normalizes to "propertyValue".) See also
// https://github.com/jquery/jquery/blob/1ea092a54b00aa4d902f4e22ada3854d195d4a18/src/serialize.js#L13-L92
// The alternative is to handle "[]name" and "[index]name" cases while normalizing keys.
{ "[]", new[] { "found" } },
{ "[]property1", new[] { "found" } },
{ "property2[]", new[] { "found" } },

View File

@ -212,7 +212,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
// Assert
var defaultMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
Assert.Empty(defaultMetadata.Attributes.Attributes);
// Not exactly "no attributes" due to SerializableAttribute on object.
Assert.IsType<SerializableAttribute>(Assert.Single(defaultMetadata.Attributes.Attributes));
}
[Fact]
@ -229,11 +231,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
// Assert
var defaultMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
Assert.Equal(2, defaultMetadata.Attributes.Attributes.Count);
var attribute1 = Assert.IsType<ModelAttribute>(defaultMetadata.Attributes.Attributes[0]);
Assert.Equal("ParamAttrib1", attribute1.Value);
var attribute2 = Assert.IsType<ModelAttribute>(defaultMetadata.Attributes.Attributes[1]);
Assert.Equal("ParamAttrib2", attribute2.Value);
Assert.Collection(
// Take(2) to ignore SerializableAttribute on object.
defaultMetadata.Attributes.Attributes.Take(2),
attribute =>
{
var modelAttribute = Assert.IsType<ModelAttribute>(attribute);
Assert.Equal("ParamAttrib1", modelAttribute.Value);
},
attribute =>
{
var modelAttribute = Assert.IsType<ModelAttribute>(attribute);
Assert.Equal("ParamAttrib2", modelAttribute.Value);
});
}
[Fact]

View File

@ -5,6 +5,7 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
@ -188,10 +189,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
.GetParameters()[0]);
// Assert
Assert.Empty(attributes.Attributes);
// Not exactly "no attributes" due to SerializableAttribute on object.
Assert.IsType<SerializableAttribute>(Assert.Single(attributes.Attributes));
Assert.Empty(attributes.ParameterAttributes);
Assert.Null(attributes.TypeAttributes);
Assert.Null(attributes.PropertyAttributes);
Assert.Equal(attributes.Attributes, attributes.TypeAttributes);
}
[Fact]
@ -204,12 +206,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
.GetParameters()[1]);
// Assert
Assert.IsType<RequiredAttribute>(attributes.Attributes[0]);
Assert.IsType<RangeAttribute>(attributes.Attributes[1]);
Assert.IsType<RequiredAttribute>(attributes.ParameterAttributes[0]);
Assert.IsType<RangeAttribute>(attributes.ParameterAttributes[1]);
Assert.Null(attributes.TypeAttributes);
Assert.Collection(
// Take(2) to ignore ComVisibleAttribute, SerializableAttribute, ... on int.
attributes.Attributes.Take(2),
attribute => Assert.IsType<RequiredAttribute>(attribute),
attribute => Assert.IsType<RangeAttribute>(attribute));
Assert.Collection(
attributes.ParameterAttributes,
attribute => Assert.IsType<RequiredAttribute>(attribute),
attribute => Assert.IsType<RangeAttribute>(attribute));
Assert.Null(attributes.PropertyAttributes);
Assert.Collection(
// Take(1) because the attribute or attributes after SerializableAttribute are framework-specific.
attributes.TypeAttributes.Take(1),
attribute => Assert.IsType<SerializableAttribute>(attribute));
}
[Fact]
public void GetAttributesForParameter_IncludesTypeAttributes()
{
// Arrange
var parameters = typeof(MethodWithParamAttributesType)
.GetMethod(nameof(MethodWithParamAttributesType.Method))
.GetParameters();
// Act
var attributes = ModelAttributes.GetAttributesForParameter(parameters[2]);
// Assert
Assert.Collection(attributes.Attributes,
attribute => Assert.IsType<BindRequiredAttribute>(attribute),
attribute => Assert.IsType<ClassValidator>(attribute));
Assert.IsType<BindRequiredAttribute>(Assert.Single(attributes.ParameterAttributes));
Assert.Null(attributes.PropertyAttributes);
Assert.IsType<ClassValidator>(Assert.Single(attributes.TypeAttributes));
}
[ClassValidator]
@ -272,7 +302,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
private class ClassValidator : ValidationAttribute
{
public override Boolean IsValid(Object value)
public override bool IsValid(object value)
{
return true;
}
@ -305,7 +335,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
private class MethodWithParamAttributesType
{
[IrrelevantAttribute] // We verify this is ignored
public void Method(object noAttribs, [Required, Range(1, 100)] int validationAttribs)
public void Method(
object noAttributes,
[Required, Range(1, 100)] int validationAttributes,
[BindRequired] BaseModel mergedAttributes)
{
}
}
@ -314,4 +347,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
}
}
}
}

View File

@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute }, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(new object[] { attribute }));
// Act
provider.CreateDisplayMetadata(context);
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new[] { dataType, };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { dataType, displayFormat, };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { editable };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateBindingMetadata(context);
@ -180,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { editable };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateBindingMetadata(context);
@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display, displayName };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -234,7 +234,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display, displayName };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -261,7 +261,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display, displayName };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -298,7 +298,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { displayName };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -441,7 +441,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -472,7 +472,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -509,7 +509,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -540,7 +540,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -588,7 +588,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(DataAnnotationsMetadataProviderTest));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -635,7 +635,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -671,7 +671,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -805,7 +805,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -833,7 +833,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new object[0];
var key = ModelMetadataIdentity.ForType(type);
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
@ -985,7 +985,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -1015,7 +1015,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(typeof(EnumWithDisplayOrder));
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
@ -1119,7 +1119,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { required };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act
provider.CreateValidationMetadata(context);
@ -1141,7 +1141,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.ValidationMetadata.IsRequired = initialValue;
// Act
@ -1164,7 +1164,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { new RequiredAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsBindingRequired = initialValue;
// Act
@ -1187,7 +1187,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsReadOnly = initialValue;
// Act
@ -1208,7 +1208,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act
provider.CreateValidationMetadata(context);
@ -1229,7 +1229,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
var context = new ValidationMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.ValidationMetadata.ValidatorMetadata.Add(attribute);
// Act
@ -1248,7 +1248,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var key = ModelMetadataIdentity.ForType(typeof(EnumWithLocalizedDisplayNames));
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes, null, null));
var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes));
provider.CreateDisplayMetadata(context);
return context.DisplayMetadata.EnumGroupedDisplayNamesAndValues;
@ -1274,6 +1274,26 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
useStringLocalizer ? stringLocalizerFactory.Object : null);
}
private ModelAttributes GetModelAttributes(IEnumerable<object> typeAttributes)
{
#pragma warning disable CS0618 // Type or member is obsolete
var modelAttributes = new ModelAttributes(typeAttributes);
#pragma warning restore CS0618 // Type or member is obsolete
return modelAttributes;
}
private ModelAttributes GetModelAttributes(
IEnumerable<object> typeAttributes,
IEnumerable<object> propertyAttributes)
{
#pragma warning disable CS0618 // Type or member is obsolete
var modelAttributes = new ModelAttributes(propertyAttributes, typeAttributes);
#pragma warning restore CS0618 // Type or member is obsolete
return modelAttributes;
}
private class KVPEnumGroupAndNameComparer : IEqualityComparer<KeyValuePair<EnumGroupAndName, string>>
{
public static readonly IEqualityComparer<KeyValuePair<EnumGroupAndName, string>> Instance = new KVPEnumGroupAndNameComparer();

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
typeof(string),
nameof(ClassWithDataMemberIsRequiredTrue.StringProperty),
typeof(ClassWithDataMemberIsRequiredTrue));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
// Act
provider.CreateBindingMetadata(context);
@ -51,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
typeof(string),
nameof(ClassWithDataMemberIsRequiredFalse.StringProperty),
typeof(ClassWithDataMemberIsRequiredFalse));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsBindingRequired = initialValue;
@ -76,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
};
var key = ModelMetadataIdentity.ForType(typeof(ClassWithDataMemberIsRequiredTrue));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsBindingRequired = initialValue;
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
typeof(string),
nameof(ClassWithoutAttributes.StringProperty),
typeof(ClassWithoutAttributes));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], new object[0], null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], new object[0]));
context.BindingMetadata.IsBindingRequired = initialValue;
@ -127,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
typeof(string),
nameof(ClassWithDataMemberIsRequiredTrueWithoutDataContract.StringProperty),
typeof(ClassWithDataMemberIsRequiredTrueWithoutDataContract));
var context = new BindingMetadataProviderContext(key, new ModelAttributes(new object[0], attributes, null));
var context = new BindingMetadataProviderContext(key, GetModelAttributes(new object[0], attributes));
context.BindingMetadata.IsBindingRequired = initialValue;
@ -138,6 +139,17 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Equal(initialValue, context.BindingMetadata.IsBindingRequired);
}
private ModelAttributes GetModelAttributes(
IEnumerable<object> typeAttributes,
IEnumerable<object> propertyAttributes)
{
#pragma warning disable CS0618 // Type or member is obsolete
var modelAttributes = new ModelAttributes(propertyAttributes, typeAttributes);
#pragma warning restore CS0618 // Type or member is obsolete
return modelAttributes;
}
[DataContract]
private class ClassWithDataMemberIsRequiredTrue
{

View File

@ -1067,7 +1067,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{
return new DefaultMetadataDetails(
key,
new ModelAttributes(_attributes.Concat(entry.ModelAttributes.TypeAttributes).ToArray(), null, null));
#pragma warning disable CS0618 // Type or member is obsolete
new ModelAttributes(_attributes.Concat(entry.ModelAttributes.TypeAttributes).ToArray()));
#pragma warning restore CS0618 // Type or member is obsolete
}
return entry;
@ -1080,10 +1082,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{
return new DefaultMetadataDetails(
e.Key,
new ModelAttributes(e.ModelAttributes.TypeAttributes, _attributes.Concat(e.ModelAttributes.PropertyAttributes), null));
#pragma warning disable CS0618 // Type or member is obsolete
new ModelAttributes(
_attributes.Concat(e.ModelAttributes.PropertyAttributes),
e.ModelAttributes.TypeAttributes));
#pragma warning restore CS0618 // Type or member is obsolete
})
.ToArray();
}
}
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
@ -469,14 +468,17 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public async Task ActionParameter_CustomModelBinder_CanCreateModels_ForParameterlessConstructorTypes()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(binderProvider: new CustomComplexTypeModelBinderProvider());
var parameter = new ParameterDescriptor()
var testContext = ModelBindingTestHelper.GetTestContext();
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(
testContext.MvcOptions,
new CustomComplexTypeModelBinderProvider());
var parameter = new ParameterDescriptor
{
Name = "prefix",
ParameterType = typeof(ClassWithNoDefaultConstructor)
};
var testContext = ModelBindingTestHelper.GetTestContext();
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -850,7 +852,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public IEnumerator<T> GetEnumerator()
{
foreach (T t in _original)
foreach (var t in _original)
{
yield return t;
}
@ -891,4 +893,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Testing;
using Xunit;
@ -148,13 +149,63 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
// type. This should behave identically to such an attribute on an action parameter. (Tests such as
// BindParameter_WithData_WithPrefix_GetsBound cover associating [ModelBinder] with an action parameter.)
//
// This is a regression test for aspnet/Mvc#4652
// This is a regression test for aspnet/Mvc#4652 and aspnet/Mvc#7595
[Theory]
[MemberData(nameof(NullAndEmptyBindingInfo))]
public async Task BinderTypeOnParameterType_WithData_EmptyPrefix_GetsBound(BindingInfo bindingInfo)
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameters = typeof(TestController).GetMethod(nameof(TestController.Action)).GetParameters();
var parameter = new ControllerParameterDescriptor
{
Name = "Parameter1",
BindingInfo = bindingInfo,
ParameterInfo = parameters[0],
ParameterType = typeof(Address),
};
var testContext = ModelBindingTestHelper.GetTestContext();
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
// ModelBindingResult
Assert.True(modelBindingResult.IsModelSet);
// Model
var address = Assert.IsType<Address>(modelBindingResult.Model);
Assert.Equal("SomeStreet", address.Street);
// ModelState
Assert.True(modelState.IsValid);
var kvp = Assert.Single(modelState);
Assert.Equal("Street", kvp.Key);
var entry = kvp.Value;
Assert.NotNull(entry);
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
Assert.NotNull(entry.RawValue); // Value is set by test model binder, no need to validate it.
}
// Make sure the metadata is honored when a [ModelBinder] attribute is associated with an action parameter's
// type. This should behave identically to such an attribute on an action parameter. (Tests such as
// BindParameter_WithData_WithPrefix_GetsBound cover associating [ModelBinder] with an action parameter.)
//
// This is a regression test for aspnet/Mvc#4652
[Theory]
[MemberData(nameof(NullAndEmptyBindingInfo))]
public async Task BinderTypeOnParameterType_WithDataEmptyPrefixAndVersion20_GetsBound(
BindingInfo bindingInfo)
{
// Arrange
var testContext = ModelBindingTestHelper.GetTestContext(
// ParameterBinder will use ModelMetadata for typeof(Address), not Parameter1's ParameterInfo.
updateOptions: options => options.AllowValidatingTopLevelNodes = false);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
@ -162,9 +213,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Address),
};
var testContext = ModelBindingTestHelper.GetTestContext();
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -419,5 +467,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return Task.CompletedTask;
}
}
private class TestController
{
public void Action(Address address)
{
}
}
}
}
}

View File

@ -377,15 +377,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
testContext.MvcOptions.AllowEmptyInputInBodyModelBinding = true;
var modelState = testContext.ModelState;
var addressRequired = ValidationAttributeUtil.GetRequiredErrorMessage("Address");
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = true;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -422,14 +418,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty));
request.ContentType = "application/json";
});
testContext.MvcOptions.AllowEmptyInputInBodyModelBinding = true;
var httpContext = testContext.HttpContext;
var modelState = testContext.ModelState;
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = true;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -626,10 +618,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
},
options => options.AllowEmptyInputInBodyModelBinding = allowEmptyInputInBodyModelBindingSetting);
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -793,7 +783,16 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
.ForProperty<Person6>(nameof(Person6.Address))
.BindingDetails(binding => binding.BindingSource = BindingSource.Body);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter-name",
@ -801,15 +800,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Person6),
};
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
});
testContext.MetadataProvider = metadataProvider;
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -839,7 +829,16 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
.ForType<Address6>()
.BindingDetails(binding => binding.BindingSource = BindingSource.Body);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter-name",
@ -847,15 +846,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Address6),
};
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(inputText));
request.ContentType = "application/json";
});
testContext.MetadataProvider = metadataProvider;
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -890,4 +880,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
return result;
}
}
}
}

View File

@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Xunit;
@ -148,13 +147,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
request.ContentType = "application/json";
});
var optionsAccessor = testContext.GetService<IOptions<MvcOptions>>();
optionsAccessor.Value.AllowEmptyInputInBodyModelBinding = true;
testContext.MvcOptions.AllowEmptyInputInBodyModelBinding = true;
var modelState = testContext.ModelState;
var valueProvider = await CompositeValueProvider.CreateAsync(testContext);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(optionsAccessor.Value);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter);
@ -1607,7 +1604,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = Assert.IsType<Order8>(modelBindingResult.Model);
Assert.Equal("bill", model.Name);
Assert.Equal(default(KeyValuePair<string, int>), model.ProductId);
Assert.Equal(default, model.ProductId);
Assert.Single(modelState);
Assert.Equal(0, modelState.ErrorCount);
@ -1646,7 +1643,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
var model = Assert.IsType<Order8>(modelBindingResult.Model);
Assert.Null(model.Name);
Assert.Equal(default(KeyValuePair<string, int>), model.ProductId);
Assert.Equal(default, model.ProductId);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
@ -2728,4 +2725,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
});
}
}
}
}

View File

@ -67,12 +67,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{
// Arrange
var options = new MvcOptions();
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
var modelBinderProvider = new TypeModelBinderProvider();
// Adding a custom model binder for Type to ensure it doesn't get called
options.ModelBinderProviders.Insert(0, modelBinderProvider);
var setup = new MvcCoreMvcOptionsSetup(new TestHttpRequestStreamReaderFactory());
setup.Configure(options);
// Remove the ExcludeBindingMetadataProvider
@ -84,7 +84,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(options);
var metadataProvider = TestModelMetadataProvider.CreateProvider(options.ModelMetadataDetailsProviders);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.Form = new FormCollection(new Dictionary<string, StringValues>
{
{ "name", new[] { "Fred" } },
{ "type", new[] { "SomeType" } },
});
},
metadataProvider: metadataProvider,
mvcOptions: options);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
@ -92,15 +105,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(TypesBundle),
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.Form = new FormCollection(new Dictionary<string, StringValues>
{
{ "name", new[] { "Fred" } },
{ "type", new[] { "SomeType" } },
});
});
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -145,4 +149,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
}
}
}
}

View File

@ -196,18 +196,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public async Task GenericModelBinder_BindsCollection_ElementTypeUsesGreedyModelBinder_WithPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(binderProvider: new AddressBinderProvider());
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Address[])
};
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(
request => request.QueryString = new QueryString("?parameter.index=0"));
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(
testContext.MvcOptions,
new AddressBinderProvider());
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Address[])
};
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -638,4 +640,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.True(modelState.IsValid);
}
}
}
}

View File

@ -104,17 +104,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
() => $"Hurts when nothing is provided.");
}));
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.QueryString = new QueryString("?parameter.Value=10");
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter",
ParameterType = typeof(KeyValuePair<string, int>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?parameter.Value=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -188,18 +191,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
() => $"Hurts when nothing is provided.");
}));
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.QueryString = new QueryString("?parameter.Key=10");
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter",
ParameterType = typeof(KeyValuePair<string, int>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?parameter.Key=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -544,4 +549,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Equal("value2", entry.RawValue);
}
}
}
}

View File

@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{
public IModelMetadataProvider MetadataProvider { get; set; }
public MvcOptions MvcOptions { get; set; }
public T GetService<T>()
{
return (T)HttpContext.RequestServices.GetService(typeof(T));

View File

@ -26,17 +26,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public static ModelBindingTestContext GetTestContext(
Action<HttpRequest> updateRequest = null,
Action<MvcOptions> updateOptions = null,
ControllerActionDescriptor actionDescriptor = null)
ControllerActionDescriptor actionDescriptor = null,
IModelMetadataProvider metadataProvider = null,
MvcOptions mvcOptions = null)
{
var httpContext = GetHttpContext(updateRequest, updateOptions);
var httpContext = GetHttpContext(metadataProvider, updateRequest, updateOptions, mvcOptions);
var services = httpContext.RequestServices;
metadataProvider = metadataProvider ?? services.GetRequiredService<IModelMetadataProvider>();
var options = services.GetRequiredService<IOptions<MvcOptions>>();
var context = new ModelBindingTestContext()
var context = new ModelBindingTestContext
{
ActionDescriptor = actionDescriptor ?? new ControllerActionDescriptor(),
HttpContext = httpContext,
MetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(),
MetadataProvider = metadataProvider,
MvcOptions = options.Value,
RouteData = new RouteData(),
ValueProviderFactories = new List<IValueProviderFactory>(options.Value.ValueProviderFactories),
};
@ -80,10 +84,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
IModelBinderProvider binderProvider = null,
MvcOptions mvcOptions = null)
{
var services = GetServices();
var options = mvcOptions != null
? Options.Create(mvcOptions)
: services.GetRequiredService<IOptions<MvcOptions>>();
var services = GetServices(metadataProvider, mvcOptions: mvcOptions);
var options = services.GetRequiredService<IOptions<MvcOptions>>();
if (binderProvider != null)
{
@ -102,15 +104,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public static IModelBinderFactory GetModelBinderFactory(
IModelMetadataProvider metadataProvider,
IOptions<MvcOptions> options = null)
IServiceProvider services = null)
{
var services = GetServices();
if (options == null)
if (services == null)
{
options = services.GetRequiredService<IOptions<MvcOptions>>();
services = GetServices(metadataProvider);
}
var options = services.GetRequiredService<IOptions<MvcOptions>>();
return new ModelBinderFactory(metadataProvider, options, services);
}
@ -136,23 +138,52 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
}
private static HttpContext GetHttpContext(
IModelMetadataProvider metadataProvider,
Action<HttpRequest> updateRequest = null,
Action<MvcOptions> updateOptions = null)
Action<MvcOptions> updateOptions = null,
MvcOptions mvcOptions = null)
{
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpRequestLifetimeFeature>(new CancellableRequestLifetimeFeature());
updateRequest?.Invoke(httpContext.Request);
httpContext.RequestServices = GetServices(updateOptions);
httpContext.RequestServices = GetServices(metadataProvider, updateOptions, mvcOptions);
return httpContext;
}
public static IServiceProvider GetServices(Action<MvcOptions> updateOptions = null)
public static IServiceProvider GetServices(
IModelMetadataProvider metadataProvider,
Action<MvcOptions> updateOptions = null,
MvcOptions mvcOptions = null)
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddAuthorization();
serviceCollection.AddSingleton(new ApplicationPartManager());
if (metadataProvider != null)
{
serviceCollection.AddSingleton(metadataProvider);
}
else if (updateOptions != null || mvcOptions != null)
{
serviceCollection.AddSingleton(services =>
{
var optionsAccessor = services.GetRequiredService<IOptions<MvcOptions>>();
return TestModelMetadataProvider.CreateProvider(optionsAccessor.Value.ModelMetadataDetailsProviders);
});
}
else
{
serviceCollection.AddSingleton<IModelMetadataProvider>(services =>
{
return TestModelMetadataProvider.CreateDefaultProvider();
});
}
if (mvcOptions != null)
{
serviceCollection.AddSingleton(Options.Create(mvcOptions));
}
serviceCollection
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

View File

@ -1,23 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
{
public static class ParameterBinderExtensions
{
public static async Task<ModelBindingResult> BindModelAsync(
public static Task<ModelBindingResult> BindModelAsync(
this ParameterBinder parameterBinder,
ParameterDescriptor parameter,
ParameterDescriptor parameter,
ControllerContext context)
{
var valueProvider = await CompositeValueProvider.CreateAsync(context);
return await parameterBinder.BindModelAsync(context, valueProvider, parameter);
var optionsAccessor = context.HttpContext.RequestServices.GetService<IOptions<MvcOptions>>();
Assert.NotNull(optionsAccessor?.Value); // Guard
var modelMetadataProvider = context.HttpContext.RequestServices.GetService<IModelMetadataProvider>();
Assert.NotNull(modelMetadataProvider); // Guard
// Imitate a bit of ControllerBinderDelegateProvider and PageBinderFactory
ParameterInfo parameterInfo;
if (parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
parameterInfo = controllerParameterDescriptor.ParameterInfo;
}
else if (parameter is HandlerParameterDescriptor handlerParameterDescriptor)
{
parameterInfo = handlerParameterDescriptor.ParameterInfo;
}
else
{
parameterInfo = null;
}
ModelMetadata metadata;
if (optionsAccessor.Value.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameterInfo != null)
{
metadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo);
}
else
{
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
}
return parameterBinder.BindModelAsync(parameter, context, modelMetadataProvider, metadata);
}
public static async Task<ModelBindingResult> BindModelAsync(
this ParameterBinder parameterBinder,
ParameterDescriptor parameter,
@ -26,8 +63,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ModelMetadata modelMetadata)
{
var valueProvider = await CompositeValueProvider.CreateAsync(context);
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(modelMetadataProvider);
var modelBinderFactory = ModelBindingTestHelper.GetModelBinderFactory(
modelMetadataProvider,
context.HttpContext.RequestServices);
var modelBinder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{

View File

@ -205,7 +205,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
.ForProperty<Person>(nameof(Person.Service))
.BindingDetails(binding => binding.BindingSource = BindingSource.Services);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter-name",
@ -213,10 +215,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Person),
};
var testContext = ModelBindingTestHelper.GetTestContext();
testContext.MetadataProvider = metadataProvider;
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -245,7 +243,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
.ForType<JsonOutputFormatter>()
.BindingDetails(binding => binding.BindingSource = BindingSource.Services);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "parameter-name",
@ -253,10 +253,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(JsonOutputFormatter),
};
var testContext = ModelBindingTestHelper.GetTestContext();
testContext.MetadataProvider = metadataProvider;
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -268,4 +264,4 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Empty(modelState);
}
}
}
}

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;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -304,7 +303,16 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
binding.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(
(value) => $"Hmm, '{ value }' is not a valid value.");
});
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.QueryString = QueryString.Create("Parameter1", "abcd");
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
@ -312,13 +320,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = parameterType
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = QueryString.Create("Parameter1", "abcd");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
@ -405,8 +406,16 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
binding.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
value => $"Hurts when '{ value }' is provided.");
});
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider);
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
{
request.QueryString = QueryString.Create("Parameter1", string.Empty);
},
metadataProvider: metadataProvider);
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
@ -414,11 +423,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = parameterType
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = QueryString.Create("Parameter1", string.Empty);
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

View File

@ -1608,7 +1608,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
ParameterType = typeof(Order11)
};
MvcOptions testOptions = null;
var input = "{\"Zip\":\"47\"}";
var testContext = ModelBindingTestHelper.GetTestContext(
request =>
@ -1621,10 +1620,9 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
options =>
{
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Address)));
testOptions = options;
});
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testOptions);
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices);
var modelState = testContext.ModelState;
// Act

View File

@ -26,7 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var detailsProvider = new DefaultCompositeMetadataDetailsProvider(
Enumerable.Empty<IMetadataDetailsProvider>());
var key = ModelMetadataIdentity.ForType(typeof(DateTime));
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0], null, null));
#pragma warning disable CS0618 // Type or member is obsolete
var cache = new DefaultMetadataDetails(key, new ModelAttributes(new object[0]));
#pragma warning restore CS0618 // Type or member is obsolete
var provider = new EmptyModelMetadataProvider();
var metadata = new DefaultModelMetadata(provider, detailsProvider, cache);