Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC2 ModelMetadataProviders: What's the difference between overriding CreateMetadata() and GetMetadataForProperty()?

I'm statring to explore the framework's extension points, starting with MetadataProviders. I've currently implemented populating ModelMetadata.IsRequired property using RequiredAttribute succesfully, but I can't seem to find the difference between overriding CreateMetadata() or GetMetadataForProperty(), since both options seem to work.

In general, the examples I've seen override CreateMetadata().

  • What are the pro and cons of using either options?
  • Are there any scenarios where one of these are the preferred options?

As an extra: are there any good resources (blogs, books) to learn from this extension point?

like image 871
CGK Avatar asked Feb 09 '11 17:02

CGK


1 Answers

The GetMetadataForProperty() is declared on the class ModelMetadataProvider.

AssociatedMetadataProvider derives from ModelMetadataProvider. CreateMetadata() is declared on AssociatedMetadataProvider. The DataAnnotationsMetadataProvider that is overridden in the link you provide is derived from AssociatedMetadataProvider.

The MVC framework makes calls to ModelMetadataProvider's GetMetadataForProperty() method.

The reason overriding CreateMetadata() is working for you is because the AssociatedModelMetadataProvider's default implementation of GetMetadataForProperty() makes a call to CreateMetadata(). It looks like this:

public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
   if (containerType == null)
   {
       throw new ArgumentNullException("containerType");
   }
   if (string.IsNullOrEmpty(propertyName))
   {
       throw new ArgumentException(MvcResources.Common_NullOrEmpty, "propertyName");
   }
   PropertyDescriptor propertyDescriptor = this.GetTypeDescriptor(containerType).GetProperties().Find(propertyName, true);
   if (propertyDescriptor == null)
   {
       throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyNotFound, new object[] { containerType.FullName, propertyName   }));
   }
return this.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);

}

protected virtual ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, PropertyDescriptor propertyDescriptor) 
{
   IEnumerable<Attribute> attributes = this.FilterAttributes(containerType, propertyDescriptor, propertyDescriptor.Attributes.Cast<Attribute>());
   return this.CreateMetadata(attributes, containerType, modelAccessor, propertyDescriptor.PropertyType, propertyDescriptor.Name);
}

If you are subclassing the AssociatedMetadataProvider as you are in the link you provided, then your preferred extensibility point is the CreateMetadata method, because the AssociatedMetadataProvider.GetMetadataForProperty() method pre-validates the contract of your CreateMetadata() method. That way, you know that if there is an error in your CreateMetadata() method, you already know that the source of the error is in your method and not in the arguments that were passed to it.

Also, here is the source of the FilterAttributes() method, in case you were wondering:

protected virtual IEnumerable<Attribute> FilterAttributes(Type containerType, PropertyDescriptor propertyDescriptor, IEnumerable<Attribute> attributes)
{
if (!typeof(ViewPage).IsAssignableFrom(containerType) && !typeof(ViewUserControl).IsAssignableFrom(containerType))
    {
        return attributes;
    }
    return attributes.Where<Attribute>(delegate (Attribute a) {
        return !(a is ReadOnlyAttribute);
    });
}
like image 89
smartcaveman Avatar answered Oct 21 '22 14:10

smartcaveman