| File: System\ComponentModel\DataAnnotations\ValidationAttributeStore.cs | Web Access |
| Project: src\runtime\src\libraries\System.ComponentModel.Annotations\src\System.ComponentModel.Annotations.csproj (System.ComponentModel.Annotations) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; namespace System.ComponentModel.DataAnnotations { /// <summary> /// Cache of <see cref="ValidationAttribute" />s /// </summary> /// <remarks> /// 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. /// </remarks> internal sealed class ValidationAttributeStore { private readonly ConcurrentDictionary<Type, TypeStoreItem> _typeStoreItems = new(); /// <summary> /// Gets the singleton <see cref="ValidationAttributeStore" /> /// </summary> internal static ValidationAttributeStore Instance { get; } = new ValidationAttributeStore(); /// <summary> /// Retrieves the type level validation attributes for the given type. /// </summary> /// <param name="validationContext">The context that describes the type. It cannot be null.</param> /// <returns>The collection of validation attributes. It could be empty.</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal IEnumerable<ValidationAttribute> GetTypeValidationAttributes(ValidationContext validationContext) { EnsureValidationContext(validationContext); var item = GetTypeStoreItem(validationContext.ObjectType); return item.ValidationAttributes; } /// <summary> /// Retrieves the <see cref="DisplayAttribute" /> associated with the given type. It may be null. /// </summary> /// <param name="validationContext">The context that describes the type. It cannot be null.</param> /// <returns>The display attribute instance, if present.</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal DisplayAttribute? GetTypeDisplayAttribute(ValidationContext validationContext) { EnsureValidationContext(validationContext); var item = GetTypeStoreItem(validationContext.ObjectType); return item.DisplayAttribute; } /// <summary> /// Retrieves the set of validation attributes for the property /// </summary> /// <param name="validationContext">The context that describes the property. It cannot be null.</param> /// <returns>The collection of validation attributes. It could be empty.</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal IEnumerable<ValidationAttribute> GetPropertyValidationAttributes(ValidationContext validationContext) { EnsureValidationContext(validationContext); var typeItem = GetTypeStoreItem(validationContext.ObjectType); var item = typeItem.GetPropertyStoreItem(validationContext.MemberName!); return item.ValidationAttributes; } /// <summary> /// Retrieves the <see cref="DisplayAttribute" /> associated with the given property /// </summary> /// <param name="validationContext">The context that describes the property. It cannot be null.</param> /// <returns>The display attribute instance, if present.</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal DisplayAttribute? GetPropertyDisplayAttribute(ValidationContext validationContext) { EnsureValidationContext(validationContext); var typeItem = GetTypeStoreItem(validationContext.ObjectType); var item = typeItem.GetPropertyStoreItem(validationContext.MemberName!); return item.DisplayAttribute; } /// <summary> /// Retrieves the Type of the given property. /// </summary> /// <param name="validationContext">The context that describes the property. It cannot be null.</param> /// <returns>The type of the specified property</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal Type GetPropertyType(ValidationContext validationContext) { EnsureValidationContext(validationContext); var typeItem = GetTypeStoreItem(validationContext.ObjectType); var item = typeItem.GetPropertyStoreItem(validationContext.MemberName!); return item.PropertyType; } /// <summary> /// Determines whether or not a given <see cref="ValidationContext" />'s /// <see cref="ValidationContext.MemberName" /> references a property on /// the <see cref="ValidationContext.ObjectType" />. /// </summary> /// <param name="validationContext">The <see cref="ValidationContext" /> to check.</param> /// <returns><c>true</c> when the <paramref name="validationContext" /> represents a property, <c>false</c> otherwise.</returns> [RequiresUnreferencedCode("The Type of validationContext.ObjectType cannot be statically discovered.")] internal bool IsPropertyContext(ValidationContext validationContext) { EnsureValidationContext(validationContext); var typeItem = GetTypeStoreItem(validationContext.ObjectType); return typeItem.TryGetPropertyStoreItem(validationContext.MemberName!, out _); } /// <summary> /// Retrieves or creates the store item for the given type /// </summary> /// <param name="type">The type whose store item is needed. It cannot be null</param> /// <returns>The type store item. It will not be null.</returns> private TypeStoreItem GetTypeStoreItem([DynamicallyAccessedMembers(TypeStoreItem.DynamicallyAccessedTypes)] Type type) { Debug.Assert(type != null); return _typeStoreItems.GetOrAdd(type, AddTypeStoreItem); [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", Justification = "The parameter in the parent method has already been marked DynamicallyAccessedMemberTypes.All.")] static TypeStoreItem AddTypeStoreItem(Type type) { AttributeCollection attributes = TypeDescriptor.GetAttributes(type); return new TypeStoreItem(type, attributes); } } /// <summary> /// Throws an ArgumentException of the validation context is null /// </summary> /// <param name="validationContext">The context to check</param> private static void EnsureValidationContext(ValidationContext validationContext) { ArgumentNullException.ThrowIfNull(validationContext); } internal static bool IsPublic(PropertyInfo p) => (p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic); /// <summary> /// Private abstract class for all store items /// </summary> private abstract class StoreItem { internal StoreItem(AttributeCollection attributes) { ValidationAttributes = attributes.OfType<ValidationAttribute>(); DisplayAttribute = attributes.OfType<DisplayAttribute>().SingleOrDefault(); } internal IEnumerable<ValidationAttribute> ValidationAttributes { get; } internal DisplayAttribute? DisplayAttribute { get; } } /// <summary> /// Private class to store data associated with a type /// </summary> private sealed class TypeStoreItem : StoreItem { internal const DynamicallyAccessedMemberTypes DynamicallyAccessedTypes = AssociatedMetadataTypeTypeDescriptionProvider.AllMembersAndInterfaces; private readonly object _syncRoot = new object(); [DynamicallyAccessedMembers(DynamicallyAccessedTypes)] private readonly Type _type; private Dictionary<string, PropertyStoreItem>? _propertyStoreItems; internal TypeStoreItem([DynamicallyAccessedMembers(DynamicallyAccessedTypes)] Type type, AttributeCollection attributes) : base(attributes) { _type = type; } [RequiresUnreferencedCode("The Types of _type's properties cannot be statically discovered.")] internal PropertyStoreItem GetPropertyStoreItem(string propertyName) { if (!TryGetPropertyStoreItem(propertyName, out PropertyStoreItem? item)) { throw new ArgumentException(SR.Format(SR.AttributeStore_Unknown_Property, _type.Name, propertyName), nameof(propertyName)); } return item; } [RequiresUnreferencedCode("The Types of _type's properties cannot be statically discovered.")] internal bool TryGetPropertyStoreItem(string propertyName, [NotNullWhen(true)] out PropertyStoreItem? item) { if (string.IsNullOrEmpty(propertyName)) { throw new ArgumentNullException(nameof(propertyName)); } if (_propertyStoreItems == null) { lock (_syncRoot) { _propertyStoreItems ??= CreatePropertyStoreItems(); } } return _propertyStoreItems.TryGetValue(propertyName, out item); } [RequiresUnreferencedCode("The Types of _type's properties cannot be statically discovered.")] private Dictionary<string, PropertyStoreItem> CreatePropertyStoreItems() { var propertyStoreItems = new Dictionary<string, PropertyStoreItem>(); var properties = TypeDescriptor.GetProperties(_type); foreach (PropertyDescriptor property in properties) { var item = new PropertyStoreItem(property.PropertyType, GetExplicitAttributes(property)); propertyStoreItems[property.Name] = item; } return propertyStoreItems; } /// <summary> /// Method to extract only the explicitly specified attributes from a <see cref="PropertyDescriptor"/> /// </summary> /// <remarks> /// Normal TypeDescriptor semantics are to inherit the attributes of a property's type. This method /// exists to suppress those inherited attributes. /// </remarks> /// <param name="propertyDescriptor">The property descriptor whose attributes are needed.</param> /// <returns>A new <see cref="AttributeCollection"/> stripped of any attributes from the property's type.</returns> [RequiresUnreferencedCode("The Type of propertyDescriptor.PropertyType cannot be statically discovered.")] private static AttributeCollection GetExplicitAttributes(PropertyDescriptor propertyDescriptor) { AttributeCollection propertyDescriptorAttributes = propertyDescriptor.Attributes; List<Attribute> attributes = new List<Attribute>(propertyDescriptorAttributes.Count); foreach (Attribute attribute in propertyDescriptorAttributes) { attributes.Add(attribute); } AttributeCollection typeAttributes = TypeDescriptor.GetAttributes(propertyDescriptor.PropertyType); 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()) : propertyDescriptorAttributes; } } /// <summary> /// Private class to store data associated with a property /// </summary> private sealed class PropertyStoreItem : StoreItem { internal PropertyStoreItem(Type propertyType, AttributeCollection attributes) : base(attributes) { Debug.Assert(propertyType != null); PropertyType = propertyType; } internal Type PropertyType { get; } } } }