Added ModelMetadataType Attribute and logic to get attributes from ModelMetadata class

This commit is contained in:
Kirthi Krishnamraju 2015-01-06 23:02:26 -08:00
parent 6641997836
commit 6a824a4394
9 changed files with 272 additions and 20 deletions

View File

@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private TModelMetadata CreateTypeInformation(Type type, IEnumerable<Attribute> associatedAttributes)
{
var attributes = type.GetTypeInfo().GetCustomAttributes();
var attributes = ModelAttributes.GetAttributesForType(type);
if (associatedAttributes != null)
{
attributes = attributes.Concat(associatedAttributes);
@ -169,10 +169,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private PropertyInformation CreatePropertyInformation(Type containerType, PropertyHelper helper)
{
var property = helper.Property;
var attributes = ModelAttributes.GetAttributesForProperty(containerType, property);
return new PropertyInformation
{
PropertyHelper = helper,
Prototype = CreateMetadataPrototype(ModelAttributes.GetAttributesForProperty(property),
Prototype = CreateMetadataPrototype(attributes,
containerType,
property.PropertyType,
property.Name),

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -32,17 +33,57 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Gets the attributes for the given <paramref name="property"/>.
/// </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>
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
/// <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.
var propertyAttributes = property.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;
}
}
}

View File

@ -14,9 +14,4 @@
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<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>

View File

@ -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; }
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -20,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
protected abstract IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes);
IEnumerable<object> attributes);
private IEnumerable<IModelValidator> GetValidatorsForProperty(ModelMetadata metadata)
{
@ -38,15 +39,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
"metadata");
}
var attributes = property.GetCustomAttributes();
var attributes = ModelAttributes.GetAttributesForProperty(metadata.ContainerType, property);
return GetValidators(metadata, attributes);
}
private IEnumerable<IModelValidator> GetValidatorsForType(ModelMetadata metadata)
{
var attributes = metadata.ModelType
.GetTypeInfo()
.GetCustomAttributes();
var attributes = ModelAttributes.GetAttributesForType(metadata.ModelType);
return GetValidators(metadata, attributes);
}
}

View File

@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes)
IEnumerable<object> attributes)
{
var results = new List<IModelValidator>();

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public class DataMemberModelValidatorProvider : AssociatedValidatorProvider
{
protected override IEnumerable<IModelValidator> GetValidators(ModelMetadata metadata,
IEnumerable<Attribute> attributes)
IEnumerable<object> attributes)
{
// Types cannot be required; only properties can
if (metadata.ContainerType == null || string.IsNullOrEmpty(metadata.PropertyName))
@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
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>()
.FirstOrDefault();

View File

@ -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;
}
}
}
}

View File

@ -91,12 +91,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
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);
}
public abstract IEnumerable<IModelValidator> AbstractGetValidators(ModelMetadata metadata, IEnumerable<Attribute> attributes);
public abstract IEnumerable<IModelValidator> AbstractGetValidators(ModelMetadata metadata, IEnumerable<object> attributes);
}
}
}