Skip to content

Commit

Permalink
Add validation for MetadataType attribute - Issue #46678 (#51772)
Browse files Browse the repository at this point in the history
* MetadataType attribute class properties are now validated.

Fix #46678

* Clean up PR #51772

Co-authored-by: Brice Lambson <brice@bricelam.net>
  • Loading branch information
Xaeco and bricelam committed Jul 28, 2021
1 parent c0cad5f commit 5d42771
Show file tree
Hide file tree
Showing 3 changed files with 488 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ private TypeStoreItem GetTypeStoreItem([DynamicallyAccessedMembers(TypeStoreItem
{
if (!_typeStoreItems.TryGetValue(type, out TypeStoreItem? item))
{
// use CustomAttributeExtensions.GetCustomAttributes() to get inherited attributes as well as direct ones
var attributes = CustomAttributeExtensions.GetCustomAttributes(type, true);
var attributes = TypeDescriptor.GetAttributes(type).Cast<Attribute>();
item = new TypeStoreItem(type, attributes);
_typeStoreItems[type] = item;
}
Expand Down Expand Up @@ -170,7 +169,7 @@ internal StoreItem(IEnumerable<Attribute> attributes)
/// </summary>
private sealed class TypeStoreItem : StoreItem
{
internal const DynamicallyAccessedMemberTypes DynamicallyAccessedTypes = DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties;
internal const DynamicallyAccessedMemberTypes DynamicallyAccessedTypes = DynamicallyAccessedMemberTypes.All;

private readonly object _syncRoot = new object();
[DynamicallyAccessedMembers(DynamicallyAccessedTypes)]
Expand All @@ -183,6 +182,7 @@ internal TypeStoreItem([DynamicallyAccessedMembers(DynamicallyAccessedTypes)] Ty
_type = type;
}

[RequiresUnreferencedCode("The Types of _type's properties cannot be statically discovered.")]
internal PropertyStoreItem GetPropertyStoreItem(string propertyName)
{
if (!TryGetPropertyStoreItem(propertyName, out PropertyStoreItem? item))
Expand All @@ -194,6 +194,7 @@ internal PropertyStoreItem GetPropertyStoreItem(string 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))
Expand All @@ -215,23 +216,51 @@ internal bool TryGetPropertyStoreItem(string propertyName, [NotNullWhen(true)] o
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>();

// exclude index properties to match old TypeDescriptor functionality
var properties = _type.GetRuntimeProperties()
.Where(prop => IsPublic(prop) && !prop.GetIndexParameters().Any());
foreach (PropertyInfo property in properties)
var properties = TypeDescriptor.GetProperties(_type);
foreach (PropertyDescriptor property in properties)
{
// use CustomAttributeExtensions.GetCustomAttributes() to get inherited attributes as well as direct ones
var item = new PropertyStoreItem(property.PropertyType,
CustomAttributeExtensions.GetCustomAttributes(property, true));
var item = new PropertyStoreItem(property.PropertyType, GetExplicitAttributes(property).Cast<Attribute>());
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 AttributeCollection GetExplicitAttributes(PropertyDescriptor propertyDescriptor)
{
List<Attribute> attributes = new List<Attribute>(propertyDescriptor.Attributes.Cast<Attribute>());
IEnumerable<Attribute> typeAttributes = TypeDescriptor.GetAttributes(propertyDescriptor.PropertyType).Cast<Attribute>();
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;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,17 +514,16 @@ private static IEnumerable<ValidationError> GetObjectPropertyValidationErrors(ob
private static ICollection<KeyValuePair<ValidationContext, object?>> GetPropertyValues(object instance,
ValidationContext validationContext)
{
var properties = instance.GetType().GetRuntimeProperties()
.Where(p => ValidationAttributeStore.IsPublic(p) && !p.GetIndexParameters().Any());
var items = new List<KeyValuePair<ValidationContext, object?>>(properties.Count());
foreach (var property in properties)
var properties = TypeDescriptor.GetProperties(instance);
var items = new List<KeyValuePair<ValidationContext, object?>>(properties.Count);
foreach (PropertyDescriptor property in properties)
{
var context = CreateValidationContext(instance, validationContext);
context.MemberName = property.Name;

if (_store.GetPropertyValidationAttributes(context).Any())
{
items.Add(new KeyValuePair<ValidationContext, object?>(context, property.GetValue(instance, null)));
items.Add(new KeyValuePair<ValidationContext, object?>(context, property.GetValue(instance)));
}
}

Expand Down
Loading

0 comments on commit 5d42771

Please sign in to comment.