Added ModelMetadataType Attribute and logic to get attributes from ModelMetadata class
This commit is contained in:
parent
6641997836
commit
6a824a4394
|
|
@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
private TModelMetadata CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
private TModelMetadata CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
|
||||||
{
|
{
|
||||||
var attributes = type.GetTypeInfo().GetCustomAttributes();
|
var attributes = ModelAttributes.GetAttributesForType(type);
|
||||||
if (associatedAttributes != null)
|
if (associatedAttributes != null)
|
||||||
{
|
{
|
||||||
attributes = attributes.Concat(associatedAttributes);
|
attributes = attributes.Concat(associatedAttributes);
|
||||||
|
|
@ -169,10 +169,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper)
|
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper)
|
||||||
{
|
{
|
||||||
var property = helper.Property;
|
var property = helper.Property;
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(containerType, property);
|
||||||
|
|
||||||
return new PropertyInformation
|
return new PropertyInformation
|
||||||
{
|
{
|
||||||
PropertyHelper = helper,
|
PropertyHelper = helper,
|
||||||
Prototype = CreateMetadataPrototype(ModelAttributes.GetAttributesForProperty(property),
|
Prototype = CreateMetadataPrototype(attributes,
|
||||||
containerType,
|
containerType,
|
||||||
property.PropertyType,
|
property.PropertyType,
|
||||||
property.Name),
|
property.Name),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -32,17 +33,57 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the attributes for the given <paramref name="property"/>.
|
/// Gets the attributes for the given <paramref name="property"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="property">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
|
/// <param name="type">The <see cref="Type"/> in which caller found <paramref name="property"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="property">A <see cref="PropertyInfo"/> for which attributes need to be resolved.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
|
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
|
||||||
/// <paramref name="property"/> before the attributes on the <paramref name="property"/> type.</returns>
|
/// <paramref name="property"/> before the attributes on the <paramref name="property"/> type.</returns>
|
||||||
public static IEnumerable<object> GetAttributesForProperty(PropertyInfo property)
|
public static IEnumerable<object> GetAttributesForProperty([NotNull] Type type, [NotNull] PropertyInfo property)
|
||||||
{
|
{
|
||||||
// Return the property attributes first.
|
// Return the property attributes first.
|
||||||
var propertyAttributes = property.GetCustomAttributes();
|
var propertyAttributes = property.GetCustomAttributes();
|
||||||
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes();
|
var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes();
|
||||||
|
|
||||||
return propertyAttributes.Concat(typeAttributes);
|
propertyAttributes = propertyAttributes.Concat(typeAttributes);
|
||||||
|
|
||||||
|
var modelMedatadataType = type.GetTypeInfo().GetCustomAttribute<ModelMetadataTypeAttribute>();
|
||||||
|
if (modelMedatadataType != null)
|
||||||
|
{
|
||||||
|
var modelMedatadataProperty = modelMedatadataType.MetadataType.GetRuntimeProperty(property.Name);
|
||||||
|
if (modelMedatadataProperty != null)
|
||||||
|
{
|
||||||
|
var modelMedatadataAttributes = modelMedatadataProperty.GetCustomAttributes();
|
||||||
|
propertyAttributes = propertyAttributes.Concat(modelMedatadataAttributes);
|
||||||
|
|
||||||
|
var modelMetadataTypeAttributes =
|
||||||
|
modelMedatadataProperty.PropertyType.GetTypeInfo().GetCustomAttributes();
|
||||||
|
propertyAttributes = propertyAttributes.Concat(modelMetadataTypeAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the attributes for the given <paramref name="type"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The <see cref="Type"/> for which attributes need to be resolved.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
|
||||||
|
/// <paramref name="type"/>.</returns>
|
||||||
|
public static IEnumerable<object> GetAttributesForType([NotNull] Type type)
|
||||||
|
{
|
||||||
|
var attributes = type.GetTypeInfo().GetCustomAttributes();
|
||||||
|
|
||||||
|
var modelMedatadataType = type.GetTypeInfo().GetCustomAttribute<ModelMetadataTypeAttribute>();
|
||||||
|
if (modelMedatadataType != null)
|
||||||
|
{
|
||||||
|
var modelMedatadataAttributes = modelMedatadataType.MetadataType.GetTypeInfo().GetCustomAttributes();
|
||||||
|
attributes = attributes.Concat(modelMedatadataAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,4 @@
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
<ProjectExtensions>
|
|
||||||
<VisualStudio>
|
|
||||||
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
|
|
||||||
</VisualStudio>
|
|
||||||
</ProjectExtensions>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This attribute specifies the metadata class to associate with a data model class.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class ModelMetadataTypeAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModelMetadataTypeAttribute" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of metadata class that is associated with a data model class.</param>
|
||||||
|
public ModelMetadataTypeAttribute([NotNull] Type type)
|
||||||
|
{
|
||||||
|
MetadataType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of metadata class that is associated with a data model class.
|
||||||
|
/// </summary>
|
||||||
|
public Type MetadataType { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ModelBinding
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
@ -20,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
protected abstract IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
||||||
IEnumerable<Attribute> attributes);
|
IEnumerable<object> attributes);
|
||||||
|
|
||||||
private IEnumerable<IModelValidator> GetValidatorsForProperty(ModelMetadata metadata)
|
private IEnumerable<IModelValidator> GetValidatorsForProperty(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
|
|
@ -38,15 +39,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
"metadata");
|
"metadata");
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributes = property.GetCustomAttributes();
|
var attributes = ModelAttributes.GetAttributesForProperty(metadata.ContainerType, property);
|
||||||
return GetValidators(metadata, attributes);
|
return GetValidators(metadata, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IModelValidator> GetValidatorsForType(ModelMetadata metadata)
|
private IEnumerable<IModelValidator> GetValidatorsForType(ModelMetadata metadata)
|
||||||
{
|
{
|
||||||
var attributes = metadata.ModelType
|
var attributes = ModelAttributes.GetAttributesForType(metadata.ModelType);
|
||||||
.GetTypeInfo()
|
|
||||||
.GetCustomAttributes();
|
|
||||||
return GetValidators(metadata, attributes);
|
return GetValidators(metadata, attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
||||||
IEnumerable<Attribute> attributes)
|
IEnumerable<object> attributes)
|
||||||
{
|
{
|
||||||
var results = new List<IModelValidator>();
|
var results = new List<IModelValidator>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public class DataMemberModelValidatorProvider : AssociatedValidatorProvider
|
public class DataMemberModelValidatorProvider : AssociatedValidatorProvider
|
||||||
{
|
{
|
||||||
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
|
||||||
IEnumerable<Attribute> attributes)
|
IEnumerable<object> attributes)
|
||||||
{
|
{
|
||||||
// Types cannot be required; only properties can
|
// Types cannot be required; only properties can
|
||||||
if (metadata.ContainerType == null || string.IsNullOrEmpty(metadata.PropertyName))
|
if (metadata.ContainerType == null || string.IsNullOrEmpty(metadata.PropertyName))
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return Enumerable.Empty<IModelValidator>();
|
return Enumerable.Empty<IModelValidator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsRequiredDataMember(Type containerType, IEnumerable<Attribute> attributes)
|
internal static bool IsRequiredDataMember(Type containerType, IEnumerable<object> attributes)
|
||||||
{
|
{
|
||||||
var dataMemberAttribute = attributes.OfType<DataMemberAttribute>()
|
var dataMemberAttribute = attributes.OfType<DataMemberAttribute>()
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class ModelMetadataAttributesTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForBaseProperty_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(BaseViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "BaseProperty");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<RequiredAttribute>());
|
||||||
|
Assert.Single(attributes.OfType<StringLengthAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForTestProperty_ModelOverridesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(BaseViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "TestProperty");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var rangeAttribute = attributes.OfType<RangeAttribute>().FirstOrDefault();
|
||||||
|
Assert.NotNull(rangeAttribute);
|
||||||
|
Assert.Equal(0, (int)rangeAttribute.Minimum);
|
||||||
|
Assert.Equal(10, (int)rangeAttribute.Maximum);
|
||||||
|
Assert.Single(attributes.OfType<FromHeaderAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForBasePropertyFromDerivedModel_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(DerivedViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "BaseProperty");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<RequiredAttribute>());
|
||||||
|
Assert.Single(attributes.OfType<StringLengthAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForTestPropertyFromDerived_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(DerivedViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "TestProperty");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<RequiredAttribute>());
|
||||||
|
Assert.Single(attributes.OfType<StringLengthAttribute>());
|
||||||
|
Assert.DoesNotContain(typeof(RangeAttribute), attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForVirtualPropertyFromDerived_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(DerivedViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "VirtualProperty");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<RequiredAttribute>());
|
||||||
|
Assert.Single(attributes.OfType<RangeAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFromServiceAttributeFromBase_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var modelType = typeof(DerivedViewModel);
|
||||||
|
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "Calculator");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<RequiredAttribute>());
|
||||||
|
Assert.Single(attributes.OfType<FromServicesAttribute>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetAttributesForType_IncludesMetadataAttributes()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var attributes = ModelAttributes.GetAttributesForType(typeof(BaseViewModel));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(attributes.OfType<ClassValidator>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper classes
|
||||||
|
|
||||||
|
[ClassValidator]
|
||||||
|
private class BaseModel
|
||||||
|
{
|
||||||
|
[StringLength(10)]
|
||||||
|
public string BaseProperty { get; set; }
|
||||||
|
|
||||||
|
[Range(10,100)]
|
||||||
|
[FromHeader]
|
||||||
|
public string TestProperty { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public virtual int VirtualProperty { get; set; }
|
||||||
|
|
||||||
|
[FromServices]
|
||||||
|
public ICalculator Calculator { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DerivedModel : BaseModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string DerivedProperty { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public new string TestProperty { get; set; }
|
||||||
|
|
||||||
|
[Range(10,100)]
|
||||||
|
public override int VirtualProperty { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[ModelMetadataType(typeof(BaseModel))]
|
||||||
|
private class BaseViewModel
|
||||||
|
{
|
||||||
|
[Range(0,10)]
|
||||||
|
public string TestProperty { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string BaseProperty { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public ICalculator Calculator { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ModelMetadataType(typeof(DerivedModel))]
|
||||||
|
private class DerivedViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
[StringLength(2)]
|
||||||
|
public new string TestProperty { get; set; }
|
||||||
|
|
||||||
|
public int VirtualProperty { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICalculator
|
||||||
|
{
|
||||||
|
int Operation(char @operator, int left, int right);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassValidator : ValidationAttribute
|
||||||
|
{
|
||||||
|
public override Boolean IsValid(Object value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -91,12 +91,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
public abstract class TestableAssociatedValidatorProvider : AssociatedValidatorProvider
|
public abstract class TestableAssociatedValidatorProvider : AssociatedValidatorProvider
|
||||||
{
|
{
|
||||||
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes)
|
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata, IEnumerable<object> attributes)
|
||||||
{
|
{
|
||||||
return AbstractGetValidators(metadata, attributes);
|
return AbstractGetValidators(metadata, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract IEnumerable<IModelValidator> AbstractGetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes);
|
public abstract IEnumerable<IModelValidator> AbstractGetValidators(ModelMetadata metadata, IEnumerable<object> attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue