Saturday, 6 August 2011

Automatic DisplayAttribute using PostSharp

The Problem


I worked with a MVC 3 web application the other day and was asked to translate the properties to Norwegian. The MVC 3 views are written in Razor view engine and the models are set to DataContract classes. These classes consists of multiple properties and each property needs to have a DisplayAttribute where the Name property is set to the text to display. An overload of the DisplayAttribute is to provide the type of a Resource file (public) followed by the Name attribute, which will in this case be the Resource key inside the Resource file.

After translating large DataContract classes with many DataMember properties, I thought this was a very tedious process. Another challenge was that I got runtime
exceptions when opening the MVC 3 view in a browser just to see an exception being
thrown at me because the resource file did not have a matching resource key which
should be tied to a property by given Name in the DisplayAttribute.

Attributes being difficult


The source of this problem is actually that Attributes in C# cannot define the Resource key directly, but by a string literals. The reason for this is limitations in the types supported in the parameters of a attribute. The ResourceType of the DisplayAttribute is set to the Resource file Type (the resx file is actually a class, inspect the designer.cs file to see its structure). The Name attribute is as previously mentioned a string literal, hardly very refactoring friendly.

Finding a solution


In another project at my work we have used PostSharp to aid in such repetitive tasks, where each property or class needs to be tagged with attributes in a certain pattern. I created a test project and implemented a PostSharp attribute / aspect called AutoDisplayAttribute. This attribute is set on the class level and will decorate all properties with a required display attribute and a resource key set to the property name. The client of this aspect can enforce lookup in the Resource File which means an error will be raised if there are no matching Resource key. The default behavior of this aspect is to avoid setting the DisplayAttribute if no matches are found in the Resource File specified (forceLookup).


Download ILSpy and SharpCrafter's PostSharp


I also used ILSpy to inspect the results in the DLL file of my test project, and the properties were all decorated with my desired DisplayAttribute. ILSpy is free software and is similar to Reflector. The tool is very useful to inspect metadata for DLL files and disassemble them to reveal the source code.

ILSpy

If you have not used PostSharp earlier, you need to download the setup from SharpCrafters. The version I used is PostSharp version 2.1 CTP 3. The download page should be available here:

PostSharp 2.1 Community Edition CTP 3

The PostSharp.dll file must be added in the References to your test project to test out the source code I will present next.

The source code for the AutoDisplayAttribute



The source code for the AutoDisplayAttribute follows below.



// We set up multicast inheritance so the aspect is automatically added to children types.
[MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)]
[Serializable]
public sealed class AutoDisplayAttribute : TypeLevelAspect, IAspectProvider
{

private Type _resourceType;
private bool _forceLookUp;

public AutoDisplayAttribute(Type resourceType, bool forceLookup = false) : base()
{
_resourceType = resourceType;
_forceLookUp = forceLookup;
}

// This method is called at build time and should just provide other aspects.
public IEnumerable ProvideAspects(object targetElement)
{
Type targetType = (Type)targetElement;
var targetTypeProperties = GetTargetTypeProperties(targetType).Distinct();

// Add a Display attribute to every relevant property.
foreach (PropertyInfo property in targetTypeProperties)
{
if (!property.IsDefined(typeof(NotAutoDisplayAttribute), false) &&
!property.IsDefined(typeof(DisplayAttribute), false))
{
if ((!_forceLookUp) && (_resourceType.GetProperty(property.Name) == null))
{
continue;
}
CustomAttributeIntroductionAspect introduceDisplayAspect =
CreateDisplayAttributeIntroductionAspect();
InitDisplayAttributeForProperty(introduceDisplayAspect, property);
yield return new AspectInstance(property, introduceDisplayAspect);
}
}
}

private void InitDisplayAttributeForProperty(CustomAttributeIntroductionAspect introduceDisplayAspect, PropertyInfo property)
{
introduceDisplayAspect.CustomAttribute.NamedArguments.Add("ResourceType", _resourceType);
introduceDisplayAspect.CustomAttribute.NamedArguments.Add("Name", property.Name);
}

private static PropertyInfo[] GetTargetTypeProperties(Type targetType)
{
return targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
}

private static CustomAttributeIntroductionAspect CreateDisplayAttributeIntroductionAspect()
{
CustomAttributeIntroductionAspect introduceDisplayAspect =
new CustomAttributeIntroductionAspect(
new ObjectConstruction(typeof(DisplayAttribute).GetConstructor(Type.EmptyTypes)));
return introduceDisplayAspect;
}
}

[AttributeUsage(AttributeTargets.Property)]
public sealed class NotAutoDisplayAttribute : Attribute
{

}



AutoDisplayAttribute code explained


The class above generates the DisplayAttribute for properties inside the
class the AutoDisplayAttribute is applied on, which is the Attribute which is used to lookup the string to display for a given property.


The TypeLevelAspect is the parent class for AutoDisplayAttribute and the class also
implements the IAspectProvider interface. The interface consists of the public method ProvideAspects which will return an IEnumerable of AspectInstance. All these supportive classes and interface is part of the PostSharp API which is used in the code to provide the "magic" post-compilation code executed right during/after the default compilation process.

Talk and embrace


I like this AutoDisplayAttribute a lot. The aspect will definately be a time saver. If you set forceLookup to true on all classes that should have translated properties (usually data contracts), excluding the properties tagged with NotAutoDisplayAttribute, you will get compilation errors when the Resource lookup defined in a Display(Name="MyProperty", ResourceType=typeof(MyResourceType)], which is a huge benefit compared to having run time exceptions thrown at you when the display lookup fails. To fix such an exception, add the resource key and associated resource value in the resource file and build again. If the build went fine, the display attributes are set up. When you refactor a property (i.e. rename), the AutoDisplayAttribute will indirectly require that the resource key is also refactored. To avoid this, it is possible to rather stick to adding the DisplayAttribute manually, but try to keep up this when having DataContract classes with many properties that must be translated and displayed using a resource file. Instead, when having AutoDisplayAttribute on all data contracts and forcing lookup, the compilation errors will guide you to keep the lookup process in a "type-safe" manner, requiring that for each property name there must be a resource key with the same case-sensitive name. In addition, this attribute / aspect is a huge time saver and when the data contracts are tagged with this attribute, it merely consists of adding the [AutoDisplay(typeof(MyResource), forceLookup:true)] tag at the class (instead of lots of [Display(..)] attributes polluting your data contract..

Use-case of this attribute


A minimalistic example follows.

Consider this data contract class. Note the AutoDisplayAttribute at the top of the class.


[AutoDisplay(typeof(Resource1), forceLookup:true)]
public class TestModel
{

public DateTime? BirthDate { get; set; }

public string FirstName { get; set; }

}


Lets follow up with a simple razor view sample



@Html.LabelFor(model => model.BirthDate)
@Html.TextBoxFor(model => model.BirthDate, new { id = "dtpBirthDate" })

@Html.LabelFor(model => model.FirstName)
@Html.TextBoxFor(model => model.FirstName, new { id = "tbFirstName" })


Of course, this attribute is very general and can be used in other frameworks
than MVC 3. When running the application, the labels should now use the value from the Resource file by given resource key.

Please note that the forceLookup is set default to false. To enforce lookup and letting the compiler aiding you that the resource file do indeed have the resource key, set the forceLookup to true explicitly.

In the end, a screen shot from ILSpy to show the magic revealed.



Download sample solution here:

Download sample MVC 3 solution with AutoDisplayAttribute implementation


Regards, Tore Aurstad

3 comments:

  1. Please give feedback if you found this AutoDisplayAttribute helpful. The code is provided as-is with no guarantee. You can reuse the code with no restriction.

    ReplyDelete
  2. The method ProvideAspects returns IEnumerable - must be the HTML formatting not rendering the code above correct.

    Correction: ProvideAspects in code above, return type IEnumerable (this is an iterator).

    ReplyDelete
  3. Correction again, ProvideAspects method above returns IEnumerable of type AspectInstance. (The comments in Google Blogger also does not support Generics syntax expressions very good...)

    ReplyDelete