using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Resources;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
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 =
#if SILVERLIGHT
type.GetCustomAttributes(true).Cast();
#else
TypeDescriptor.GetAttributes(type).Cast();
#endif
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();
#if SILVERLIGHT
PropertyInfo[] properties = this._type.GetProperties();
foreach (PropertyInfo property in properties) {
PropertyStoreItem item = new PropertyStoreItem(property.PropertyType, property.GetCustomAttributes(true).Cast());
propertyStoreItems[property.Name] = item;
}
#else
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(this._type);
foreach (PropertyDescriptor property in properties) {
PropertyStoreItem item = new PropertyStoreItem(property.PropertyType, GetExplicitAttributes(property).Cast());
propertyStoreItems[property.Name] = item;
}
#endif // SILVERLIGHT
return propertyStoreItems;
}
#if !SILVERLIGHT
///
/// Method to extract only the explicitly specified attributes from a
///
///
/// Normal TypeDescriptor semantics are to inherit the attributes of a property's type. This method
/// exists to suppress those inherited attributes.
///
/// The property descriptor whose attributes are needed.
/// A new stripped of any attributes from the property's type.
public static AttributeCollection GetExplicitAttributes(PropertyDescriptor propertyDescriptor) {
List attributes = new List(propertyDescriptor.Attributes.Cast());
IEnumerable typeAttributes = TypeDescriptor.GetAttributes(propertyDescriptor.PropertyType).Cast();
bool removedAttribute = false;
foreach (Attribute attr in typeAttributes) {
for (int i = attributes.Count - 1; i >= 0; --i) {
// We must use ReferenceEquals since attributes could Match if they are the same.
// Only ReferenceEquals will catch actual duplications.
if (object.ReferenceEquals(attr, attributes[i])) {
attributes.RemoveAt(i);
removedAttribute = true;
}
}
}
return removedAttribute ? new AttributeCollection(attributes.ToArray()) : propertyDescriptor.Attributes;
}
#endif // !SILVERLIGHT
}
///
/// 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;
}
}
}
}
}