Permalink
Browse files

More Validation Performance Improvements

  • Loading branch information...
youssefm
youssefm committed Apr 9, 2012
1 parent 6bda940 commit 730c683da2458430d36e3e360aba68932ba69fa4
Showing with 259 additions and 170 deletions.
  1. +149 −29 src/System.Web.Http/Metadata/Providers/AssociatedMetadataProvider.cs
  2. +0 −70 src/System.Web.Http/Metadata/Providers/CachedAssociatedMetadataProvider.cs
  3. +3 −2 src/System.Web.Http/Metadata/Providers/CachedDataAnnotationsMetadataAttributes.cs
  4. +2 −2 src/System.Web.Http/Metadata/Providers/CachedDataAnnotationsModelMetadata.cs
  5. +1 −1 src/System.Web.Http/Metadata/Providers/CachedModelMetadata.cs
  6. +1 −1 ...oviders/{CachedDataAnnotationsModelMetadataProvider.cs → DataAnnotationsModelMetadataProvider.cs}
  7. +8 −3 src/System.Web.Http/Metadata/Providers/EmptyMetadataProvider.cs
  8. +1 −1 src/System.Web.Http/Services/DefaultServices.cs
  9. +2 −3 src/System.Web.Http/System.Web.Http.csproj
  10. +10 −3 src/System.Web.Http/Validation/DefaultBodyModelValidator.cs
  11. +1 −1 test/System.Web.Http.Test/Metadata/ModelMetadataTest.cs
  12. +42 −30 test/System.Web.Http.Test/Metadata/Providers/AssociatedMetadataProviderTest.cs
  13. +7 −7 ...{CachedDataAnnotationsModelMetadataProviderTest.cs → DataAnnotationsModelMetadataProviderTest.cs}
  14. +4 −4 test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs
  15. +1 −1 test/System.Web.Http.Test/Services/DefaultServicesTests.cs
  16. +2 −4 test/System.Web.Http.Test/System.Web.Http.Test.csproj
  17. +19 −2 test/System.Web.Http.Test/Validation/DefaultBodyModelValidatorTest.cs
  18. +1 −1 test/System.Web.Http.Test/Validation/ModelValidationNodeTest.cs
  19. +1 −1 test/System.Web.Http.Test/Validation/ModelValidatorTest.cs
  20. +1 −1 test/System.Web.Http.Test/Validation/Providers/AssociatedValidatorProviderTest.cs
  21. +1 −1 test/System.Web.Http.Test/Validation/Providers/DataAnnotationsModelValidatorProviderTest.cs
  22. +1 −1 test/System.Web.Http.Test/Validation/Providers/DataMemberModelValidatorProviderTest.cs
  23. +1 −1 test/System.Web.Http.Test/Validation/Validators/DataAnnotationsModelValidatorTest.cs
@@ -1,22 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
+using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Linq;
+using System.Diagnostics.Contracts;
+using System.Reflection;
+using System.Reflection.Emit;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.Metadata.Providers
{
- // This class provides a good implementation of ModelMetadataProvider for people who will be
- // using traditional classes with properties. It uses the buddy class support from
- // DataAnnotations, and consolidates the three operations down to a single override
- // for reading the attribute values and creating the metadata class.
- public abstract class AssociatedMetadataProvider : ModelMetadataProvider
+ public abstract class AssociatedMetadataProvider<TModelMetadata> : ModelMetadataProvider
+ where TModelMetadata : ModelMetadata
{
- protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName);
+ private ConcurrentDictionary<Type, TypeInformation> _typeInfoCache = new ConcurrentDictionary<Type, TypeInformation>();
- public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
+ public sealed override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
{
if (containerType == null)
{
@@ -28,14 +29,21 @@ public override IEnumerable<ModelMetadata> GetMetadataForProperties(object conta
private IEnumerable<ModelMetadata> GetMetadataForPropertiesImpl(object container, Type containerType)
{
- foreach (PropertyDescriptor property in GetProperties(containerType))
+ TypeInformation typeInfo = GetTypeInformation(containerType);
+ foreach (KeyValuePair<string, PropertyInformation> kvp in typeInfo.Properties)
{
- Func<object> modelAccessor = container == null ? null : GetPropertyValueAccessor(container, property);
- yield return GetMetadataForProperty(modelAccessor, containerType, property);
+ PropertyInformation propertyInfo = kvp.Value;
+ Func<object> modelAccessor = null;
+ if (container != null)
+ {
+ Func<object, object> propertyGetter = propertyInfo.ValueAccessor;
+ modelAccessor = () => propertyGetter(container);
+ }
+ yield return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
}
}
- public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
+ public sealed override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
if (containerType == null)
{
@@ -46,45 +54,157 @@ public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor,
throw Error.ArgumentNullOrEmpty("propertyName");
}
- PropertyDescriptor property = GetProperties(containerType).Find(propertyName, true);
- if (property == null)
+ TypeInformation typeInfo = GetTypeInformation(containerType);
+ PropertyInformation propertyInfo;
+ if (!typeInfo.Properties.TryGetValue(propertyName, out propertyInfo))
{
throw Error.Argument("propertyName", SRResources.Common_PropertyNotFound, containerType, propertyName);
}
- return GetMetadataForProperty(modelAccessor, containerType, property);
+ return CreateMetadataFromPrototype(propertyInfo.Prototype, modelAccessor);
}
- protected virtual ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor)
+ public sealed override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
{
- IEnumerable<Attribute> attributes = propertyDescriptor.Attributes.Cast<Attribute>();
- return CreateMetadata(attributes, containerType, modelAccessor, propertyDescriptor.PropertyType, propertyDescriptor.Name);
+ if (modelType == null)
+ {
+ throw Error.ArgumentNull("modelType");
+ }
+
+ TModelMetadata prototype = GetTypeInformation(modelType).Prototype;
+ return CreateMetadataFromPrototype(prototype, modelAccessor);
}
- public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
+ // Override for creating the prototype metadata (without the accessor)
+ protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName);
+
+ // Override for applying the prototype + modelAccess to yield the final metadata
+ protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype, Func<object> modelAccessor);
+
+ private TypeInformation GetTypeInformation(Type type)
{
- if (modelType == null)
+ // This retrieval is implemented as a TryGetValue/TryAdd instead of a GetOrAdd to avoid the performance cost of creating instance delegates
+ TypeInformation typeInfo;
+ if (!_typeInfoCache.TryGetValue(type, out typeInfo))
{
- throw Error.ArgumentNull("modelType");
+ typeInfo = CreateTypeInformation(type);
+ _typeInfoCache.TryAdd(type, typeInfo);
}
+ return typeInfo;
+ }
- IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();
- return CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
+ private TypeInformation CreateTypeInformation(Type type)
+ {
+ TypeInformation info = new TypeInformation();
+ ICustomTypeDescriptor typeDescriptor = TypeDescriptorHelper.Get(type);
+ info.TypeDescriptor = typeDescriptor;
+ info.Prototype = CreateMetadataPrototype(AsAttributes(typeDescriptor.GetAttributes()), containerType: null, modelType: type, propertyName: null);
+
+ Dictionary<string, PropertyInformation> properties = new Dictionary<string, PropertyInformation>();
+ foreach (PropertyDescriptor property in typeDescriptor.GetProperties())
+ {
+ properties.Add(property.Name, CreatePropertyInformation(type, property));
+ }
+ info.Properties = properties;
+
+ return info;
+ }
+
+ private PropertyInformation CreatePropertyInformation(Type containerType, PropertyDescriptor property)
+ {
+ PropertyInformation info = new PropertyInformation();
+ info.ValueAccessor = CreatePropertyValueAccessor(property);
+ info.Prototype = CreateMetadataPrototype(AsAttributes(property.Attributes), containerType, property.PropertyType, property.Name);
+ return info;
}
- private static Func<object> GetPropertyValueAccessor(object container, PropertyDescriptor property)
+ // Optimization: yield provides much better performance than the LINQ .Cast<Attribute>() in this case
+ private static IEnumerable<Attribute> AsAttributes(IEnumerable attributes)
{
- return () => property.GetValue(container);
+ foreach (object attribute in attributes)
+ {
+ yield return attribute as Attribute;
+ }
+ }
+
+ private static Func<object, object> CreatePropertyValueAccessor(PropertyDescriptor property)
+ {
+ Type declaringType = property.ComponentType;
+ if (declaringType.IsVisible)
+ {
+ string propertyName = property.Name;
+ PropertyInfo propertyInfo = declaringType.GetProperty(propertyName, property.PropertyType);
+
+ if (propertyInfo != null && propertyInfo.CanRead)
+ {
+ MethodInfo getMethodInfo = propertyInfo.GetGetMethod();
+ if (getMethodInfo != null)
+ {
+ return CreateDynamicValueAccessor(getMethodInfo, declaringType, propertyName);
+ }
+ }
+ }
+
+ // If either the type isn't public or we can't find a public getter, use the slow Reflection path
+ return container => property.GetValue(container);
+ }
+
+ // Uses Lightweight Code Gen to generate a tiny delegate that gets the property value
+ // This is an optimization to avoid having to go through the much slower System.Reflection APIs
+ // e.g. generates (object o) => (Person)o.Id
+ private static Func<object, object> CreateDynamicValueAccessor(MethodInfo getMethodInfo, Type declaringType, string propertyName)
+ {
+ Contract.Assert(getMethodInfo != null && getMethodInfo.IsPublic && !getMethodInfo.IsStatic);
+
+ Type propertyType = getMethodInfo.ReturnType;
+ DynamicMethod dynamicMethod = new DynamicMethod("Get" + propertyName + "From" + declaringType.Name, typeof(object), new Type[] { typeof(object) });
+ ILGenerator ilg = dynamicMethod.GetILGenerator();
+
+ // Load the container onto the stack, convert from object => declaring type for the property
+ ilg.Emit(OpCodes.Ldarg_0);
+ if (declaringType.IsValueType)
+ {
+ ilg.Emit(OpCodes.Unbox, declaringType);
+ }
+ else
+ {
+ ilg.Emit(OpCodes.Castclass, declaringType);
+ }
+
+ // if declaring type is value type, we use Call : structs don't have inheritance
+ // if get method is sealed or isn't virtual, we use Call : it can't be overridden
+ if (declaringType.IsValueType || !getMethodInfo.IsVirtual || getMethodInfo.IsFinal)
+ {
+ ilg.Emit(OpCodes.Call, getMethodInfo);
+ }
+ else
+ {
+ ilg.Emit(OpCodes.Callvirt, getMethodInfo);
+ }
+
+ // Box if the property type is a value type, so it can be returned as an object
+ if (propertyType.IsValueType)
+ {
+ ilg.Emit(OpCodes.Box, propertyType);
+ }
+
+ // Return property value
+ ilg.Emit(OpCodes.Ret);
+
+ return (Func<object, object>)dynamicMethod.CreateDelegate(typeof(Func<object, object>));
}
- protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type)
+ private class TypeInformation
{
- return TypeDescriptorHelper.Get(type);
+ public ICustomTypeDescriptor TypeDescriptor { get; set; }
+ public TModelMetadata Prototype { get; set; }
+ public Dictionary<string, PropertyInformation> Properties { get; set; }
}
- protected virtual PropertyDescriptorCollection GetProperties(Type type)
+ private class PropertyInformation
{
- return GetTypeDescriptor(type).GetProperties();
+ public Func<object, object> ValueAccessor { get; set; }
+ public TModelMetadata Prototype { get; set; }
}
}
}
@@ -1,70 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
-
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Runtime.Caching;
-
-namespace System.Web.Http.Metadata.Providers
-{
- public abstract class CachedAssociatedMetadataProvider<TModelMetadata> : AssociatedMetadataProvider
- where TModelMetadata : ModelMetadata
- {
- private ConcurrentDictionary<Type, ICustomTypeDescriptor> _typeDescriptorCache = new ConcurrentDictionary<Type, ICustomTypeDescriptor>();
- private ConcurrentDictionary<Type, PropertyDescriptorCollection> _propertyCache = new ConcurrentDictionary<Type, PropertyDescriptorCollection>();
- // Keyed on (Type, propertyName) tuple
- private ConcurrentDictionary<Tuple<Type, string>, ModelMetadata> _prototypeCache = new ConcurrentDictionary<Tuple<Type, string>, ModelMetadata>();
-
- protected sealed override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
- {
- // If metadata is being created for a property then containerType != null && propertyName != null
- // If metadata is being created for a type then containerType == null && propertyName == null, so we have to use modelType for the cache key.
- Type typeForCache = containerType ?? modelType;
- TModelMetadata prototype = _prototypeCache.GetOrAdd(
- Tuple.Create(typeForCache, propertyName),
- key => CreateMetadataPrototype(attributes, containerType, modelType, propertyName)) as TModelMetadata;
-
- return CreateMetadataFromPrototype(prototype, modelAccessor);
- }
-
- // New override for creating the prototype metadata (without the accessor)
- protected abstract TModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName);
-
- // New override for applying the prototype + modelAccess to yield the final metadata
- protected abstract TModelMetadata CreateMetadataFromPrototype(TModelMetadata prototype, Func<object> modelAccessor);
-
- public sealed override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
- {
- return base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
- }
-
- protected sealed override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor)
- {
- return base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
- }
-
- public sealed override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
- {
- return base.GetMetadataForProperties(container, containerType);
- }
-
- public sealed override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType)
- {
- return base.GetMetadataForType(modelAccessor, modelType);
- }
-
- protected sealed override ICustomTypeDescriptor GetTypeDescriptor(Type type)
- {
- return _typeDescriptorCache.GetOrAdd(
- type,
- key => base.GetTypeDescriptor(type));
- }
-
- protected sealed override PropertyDescriptorCollection GetProperties(Type type)
- {
- return _propertyCache.GetOrAdd(
- type,
- key => base.GetProperties(type));
- }
- }
-}
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
+using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
@@ -10,7 +11,7 @@ namespace System.Web.Http.Metadata.Providers
// REVIEW: No access to HiddenInputAttribute
public class CachedDataAnnotationsMetadataAttributes
{
- public CachedDataAnnotationsMetadataAttributes(Attribute[] attributes)
+ public CachedDataAnnotationsMetadataAttributes(IEnumerable<Attribute> attributes)
{
CacheAttributes(attributes);
}
@@ -28,7 +29,7 @@ public CachedDataAnnotationsMetadataAttributes(Attribute[] attributes)
// [SecuritySafeCritical] because it uses several DataAnnotations attribute types
[SecuritySafeCritical]
- private void CacheAttributes(Attribute[] attributes)
+ private void CacheAttributes(IEnumerable<Attribute> attributes)
{
Display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
DisplayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
@@ -18,8 +18,8 @@ public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadata pro
{
}
- public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
- : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes.ToArray()))
+ public CachedDataAnnotationsModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes)
+ : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes))
{
}
@@ -28,7 +28,7 @@ protected CachedModelMetadata(CachedModelMetadata<TPrototypeCache> prototype, Fu
}
// Constructor for creating the prototype instances of the metadata class
- protected CachedModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, TPrototypeCache prototypeCache)
+ protected CachedModelMetadata(DataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, TPrototypeCache prototypeCache)
: base(provider, containerType, null /* modelAccessor */, modelType, propertyName)
{
PrototypeCache = prototypeCache;
@@ -4,7 +4,7 @@
namespace System.Web.Http.Metadata.Providers
{
- public class CachedDataAnnotationsModelMetadataProvider : CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
+ public class DataAnnotationsModelMetadataProvider : AssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>
{
protected override CachedDataAnnotationsModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
{
@@ -4,11 +4,16 @@
namespace System.Web.Http.Metadata.Providers
{
- public class EmptyModelMetadataProvider : AssociatedMetadataProvider
+ public class EmptyModelMetadataProvider : AssociatedMetadataProvider<ModelMetadata>
{
- protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ protected override ModelMetadata CreateMetadataPrototype(IEnumerable<Attribute> attributes, Type containerType, Type modelType, string propertyName)
{
- return new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
+ return new ModelMetadata(this, containerType, null, modelType, propertyName);
+ }
+
+ protected override ModelMetadata CreateMetadataFromPrototype(ModelMetadata prototype, Func<object> modelAccessor)
+ {
+ return new ModelMetadata(this, prototype.ContainerType, modelAccessor, prototype.ModelType, prototype.PropertyName);
}
}
}
Oops, something went wrong.

0 comments on commit 730c683

Please sign in to comment.