Entity Framework, Template T4 and data annotations

[French version of this post can be found here]

For the new version of my personal website using ASP.Net MVC, I decided to implement the repository pattern with Entity Framework  4.0, helped by the use of T4 templates. Let’s skip the explanation about how to declare the repository interfaces and services to use this pattern. What we really want to do now, is to be able to use data annotations, these data contracts which allow to define our validation rules within our model. But how to do that the “easyiest” way?

The main interest of using T4 templates in my case, is to automatically generate data annotations (= attributes) in order to take benefit of the ASP.Net MVC validation mechanism. This way, the validation is centralized and every view using my objects will be able to use the same rules of validation.

The task is a little more complicated that it seems. Indeed, you have :

  • to analyse SSDL (Storage Schema Definition Langage) to read metadata and fields from each entity
  • to dynamically generate attributes
  • to be able to update your model layer without loosing manual modifications (for instance, if your add a regex attribute to your class, the next generation will overwrite it)

 

The first difficulty is to create attributes automatically without any action from the developper. It gives limits to the feasibility and some attributes like Range or regular expressions will not be available (since they cannot be inferred from the SSDL).

First we have to download the ADO.NET C# POCO Entity Generator template to generate our POCO classes. This template will help us to regenerate our classes each time our CSDL (Conceptual Schema Definition Langage) changes. Sadly, the behavior of T4 template is to rewrite all files and our classes are detroyed and recreated, deleting all manual modifications.

It is then necessary to place these attributes in a different file from each class but then, how to link the attributes to the members of our classes? Partial classes? Impossible, since we would have to redefine the member and a compilation error would occur. The solution is to use metadata, a mechanism allowing to define a class, containing all metadata for another class.

Here is a simple example :

 [MetadataType(typeof(ArticleMetaData))]
public partial class Article
{
    string Author{ get; set; }
    string Title { get; set; }
}

public class ArticleMetaData
{
   [Required]
   [StringLength(255)]
   private string Author;
}

We have our POCO class on which we specify, with the MetaDataType attribute, the class which will contain the metadata. The second one contains only members on which we have to add annotations and for each member, we can add differents attributes.

For our issue of regeneration, it is necessary to generate these metadata classes in different files than our POCO classes and to be more precise, it will be necessary to have a specific T4 template to generate POCO classes and a second one to generate metadata classes. This way, we will be able to regenerate POCO classes while keeping our previously created metadata classes. Of course, one day, you will may have to regenerate metadata classes too but then, you will have to create the new classes manually or by “processing” the template. But it is the best way to give your project a quick start!

The first task consists of editing the default T4 template (the one which generates POCO Classes) and to add (line 42 :), just before the class declaration), the following line :

 [MetadataType(typeof(<#=code.Escape(entity)#>MetaData))]
  

The second step is to create a secondary template, copy of the first one (without the line previously added) within you will have to remove all code to generate navigation properties and association fixups (you can use #region to detect what to remove and what to keep). In the WriteHeader method, add the next line :

 using System.ComponentModel.DataAnnotations;
  

Then, line 52, just before the creation of properties, add the following code block which allows to add specific atttributes (Required and StringLength) when necessary :

 <# // begin max length attribute
if (code.Escape(edmProperty.TypeUsage) == "string")
{
  int maxLength = 0;
  if (edmProperty.TypeUsage.Facets["MaxLength"].Value != null && Int32.TryParse(
          edmProperty.TypeUsage.Facets["MaxLength"].Value.ToString(),
          out maxLength))
  {
#>    
    [StringLength(<#=code.CreateLiteral(maxLength)#>, 
       ErrorMessage="This field cannot be longer than 
            <#=code.CreateLiteral(maxLength)#> characters")]
<#
   }
}
    // begin required attribute
  if (edmProperty.TypeUsage.Facets["Nullable"].Value.ToString() =="False")
  {
#>
    [Required]
<#
    }
#>

and voila! For each entity, one metadata class will be created and each time that the property must be “not null” or that its length si defined in the database then the corresponding attribute is added. To finish, you just have to add complementary attributes (range, regex, etc.)

Here is the full T4 template