Using an Associated Metadata Class outside Dynamic Data

A while back, I blogged about how ASP.NET Dynamic Data apps can uses an Associated Metadata class (aka a ‘buddy’ class) to add metadata attributed to properties defined in a generated class.  It’s a mostly ugly thing that was made necessary by limitations of the C# and VB.NET languages: they don’t let you add attributes to properties defined in another partial class.

What I didn’t mention there is that this ‘buddy’ class mechanism is actually not specific to Dynamic Data apps, and can in fact be used anywhere.  Since I’ve recently heard of several cases of users trying to do something similar, I’ll describe how it’s done.  If you’re familiar with TypeDescriptionProviders (which have been around since ancient times), this will look very trivial.

I will illustrate this in a very simple console app to keep all other distractions out of the picture (the full sample is attached to the post).  So the general scenario is that we have a generated class somewhere, e.g.

 // Assume that this is generated code that should never be hand modified.
// Hence metadata attributes can't be added directly here

public partial class Product {
    public string Name { get; set; }

    public int UnitsInStock { get; set; }
}

Instead, the buddy provider let’s you write:

 [MetadataType(typeof(Product_Metadata))]
public partial class Product {
}

class Product_Metadata {
    [DisplayName("The Units In Stock")]
    public object UnitsInStock { get; set; }
}

This works in Dynamic Data, but if we’re in some other context, no one will find our Product_Metadata ‘buddy’.  In our to hook it up ourselves, we just need to make one call!

 TypeDescriptor.AddProvider(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Product)),
    typeof(Product));

Pretty trivial stuff: we instantiate a TypeDescriptionProvider (with a somewhat scary name, I’ll give you that) and we register it by calling TypeDescriptor.AddProvider.

Once we do that, the attributes on the ‘buddy’ class magically show up as if they were defined on the real class:

 // Get the property descriptor for UnitsInStock
PropertyDescriptor propDesc = TypeDescriptor.GetProperties(
    typeof(Product)).Find("UnitsInStock", true);

// Get the display name attribute, which is not actually on the property,
// but on its counterpart in the 'buddy' class
var displayName = propDesc.Attributes.OfType<DisplayNameAttribute>().First();

And that’s pretty much it.  One important thing to notice here is that we are not using the standard reflection API (which would look like typeof(Product).GetCustomAttributes()), but we are instead using the TypeDescriptor API from the System.ComponentModel model namespace.

So to summarize, you can easily use this ‘buddy’ class mechanism anytime you deal with generated code that you need to annotate with attributes.  And even though it’s not pretty to have to use that extra class, it’s comes very handy when there is no alternative.  Maybe the day will come when C# will support doing this more cleanly, but in the meantime that’ll have to do!

SimpleBuddyTypeSample.zip