using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Resources; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; namespace System.ComponentModel.DataAnnotations { /// /// Cache of s /// /// /// This internal class serves as a cache of validation attributes and [Display] attributes. /// It exists both to help performance as well as to abstract away the differences between /// Reflection and TypeDescriptor. /// internal class ValidationAttributeStore { private static ValidationAttributeStore _singleton = new ValidationAttributeStore(); private Dictionary _typeStoreItems = new Dictionary(); /// /// Gets the singleton /// internal static ValidationAttributeStore Instance { get { return _singleton; } } /// /// Retrieves the type level validation attributes for the given type. /// /// The context that describes the type. It cannot be null. /// The collection of validation attributes. It could be empty. internal IEnumerable GetTypeValidationAttributes(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem item = this.GetTypeStoreItem(validationContext.ObjectType); return item.ValidationAttributes; } /// /// Retrieves the associated with the given type. It may be null. /// /// The context that describes the type. It cannot be null. /// The display attribute instance, if present. internal DisplayAttribute GetTypeDisplayAttribute(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem item = this.GetTypeStoreItem(validationContext.ObjectType); return item.DisplayAttribute; } /// /// Retrieves the set of validation attributes for the property /// /// The context that describes the property. It cannot be null. /// The collection of validation attributes. It could be empty. internal IEnumerable GetPropertyValidationAttributes(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem typeItem = this.GetTypeStoreItem(validationContext.ObjectType); PropertyStoreItem item = typeItem.GetPropertyStoreItem(validationContext.MemberName); return item.ValidationAttributes; } /// /// Retrieves the associated with the given property /// /// The context that describes the property. It cannot be null. /// The display attribute instance, if present. internal DisplayAttribute GetPropertyDisplayAttribute(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem typeItem = this.GetTypeStoreItem(validationContext.ObjectType); PropertyStoreItem item = typeItem.GetPropertyStoreItem(validationContext.MemberName); return item.DisplayAttribute; } /// /// Retrieves the Type of the given property. /// /// The context that describes the property. It cannot be null. /// The type of the specified property internal Type GetPropertyType(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem typeItem = this.GetTypeStoreItem(validationContext.ObjectType); PropertyStoreItem item = typeItem.GetPropertyStoreItem(validationContext.MemberName); return item.PropertyType; } /// /// Determines whether or not a given 's /// references a property on /// the . /// /// The to check. /// true when the represents a property, false otherwise. internal bool IsPropertyContext(ValidationContext validationContext) { EnsureValidationContext(validationContext); TypeStoreItem typeItem = this.GetTypeStoreItem(validationContext.ObjectType); PropertyStoreItem item = null; return typeItem.TryGetPropertyStoreItem(validationContext.MemberName, out item); } /// /// Retrieves or creates the store item for the given type /// /// The type whose store item is needed. It cannot be null /// The type store item. It will not be null. [SuppressMessage("Microsoft.Usage", "CA2301:EmbeddableTypesInContainersRule", MessageId = "_typeStoreItems", Justification = "This is used for caching the attributes for a type which is fine.")] private TypeStoreItem GetTypeStoreItem(Type type) { if (type == null) { throw new ArgumentNullException("type"); } lock (this._typeStoreItems) { TypeStoreItem item = null; if (!this._typeStoreItems.TryGetValue(type, out item)) { IEnumerable attributes = type.GetCustomAttributes(true).Cast(); item = new TypeStoreItem(type, attributes); this._typeStoreItems[type] = item; } return item; } } /// /// Throws an ArgumentException of the validation context is null /// /// The context to check private static void EnsureValidationContext(ValidationContext validationContext) { if (validationContext == null) { throw new ArgumentNullException("validationContext"); } } /// /// Private abstract class for all store items /// private abstract class StoreItem { private static IEnumerable _emptyValidationAttributeEnumerable = new ValidationAttribute[0]; private IEnumerable _validationAttributes; internal StoreItem(IEnumerable attributes) { this._validationAttributes = attributes.OfType(); this.DisplayAttribute = attributes.OfType().SingleOrDefault(); } internal IEnumerable ValidationAttributes { get { return this._validationAttributes; } } internal DisplayAttribute DisplayAttribute { get; set; } } /// /// Private class to store data associated with a type /// private class TypeStoreItem : StoreItem { private object _syncRoot = new object(); private Type _type; private Dictionary _propertyStoreItems; internal TypeStoreItem(Type type, IEnumerable attributes) : base(attributes) { this._type = type; } internal PropertyStoreItem GetPropertyStoreItem(string propertyName) { PropertyStoreItem item = null; if (!this.TryGetPropertyStoreItem(propertyName, out item)) { throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, DataAnnotationsResources.AttributeStore_Unknown_Property, this._type.Name, propertyName), "propertyName"); } return item; } internal bool TryGetPropertyStoreItem(string propertyName, out PropertyStoreItem item) { if (string.IsNullOrEmpty(propertyName)) { throw new ArgumentNullException("propertyName"); } if (this._propertyStoreItems == null) { lock (this._syncRoot) { if (this._propertyStoreItems == null) { this._propertyStoreItems = this.CreatePropertyStoreItems(); } } } if (!this._propertyStoreItems.TryGetValue(propertyName, out item)) { return false; } return true; } private Dictionary CreatePropertyStoreItems() { Dictionary propertyStoreItems = new Dictionary(); PropertyInfo[] properties = this._type.GetProperties(); foreach (PropertyInfo property in properties) { PropertyStoreItem item = new PropertyStoreItem(property.PropertyType, property.GetCustomAttributes(true).Cast()); propertyStoreItems[property.Name] = item; } return propertyStoreItems; } } /// /// Private class to store data associated with a property /// private class PropertyStoreItem : StoreItem { private Type _propertyType; internal PropertyStoreItem(Type propertyType, IEnumerable attributes) : base(attributes) { this._propertyType = propertyType; } internal Type PropertyType { get { return this._propertyType; } } } } }