Search code examples
visual-studio-2013data-annotationst4asp.net-mvc-scaffolding

Getting Custom Attributes in VS2013 T4 Scaffolding Templates


I'm in the process of porting my previous MVC4.5 T4 Scaffolding templates to be used in VS2013. All is going well, luckily the logic behind it has not changed much, but lots of namespaces, objects and properties were renamed just like I expected.

However, the tricky bit is PropertyInfo. It seems that it's no longer possible to use PropertyInfo, since the new ModelMetadata object only contains PropertyMetadata. Since PropertyMetadata has no GetCustomAttributes() method or similar, I'm stuck on upgrading the following snippet:

<#+
string SearchableBy(PropertyInfo property) {
    foreach (object attribute in property.GetCustomAttributes(true))
    {
        var searchable = attribute as SearchableAttribute;
        if (searchable != null)
        {
            return searchable.By == "" ? GetValueExpressionSuffix(property) :
                                         searchable.By;
        }
    }
    return null;
}
#>
  • Is it possible to get a PropertyInfo object in a T4 Controller / View Scaffolder somehow?
  • If not, what is the new / correct way of accessing Custom Annotations? ModelMetadata seems to be useless regarding this

PS:
This question can be considered as a sub-question of my previous one
If you are interested in how Custom Annotations can be accessed in VS2012, see this one


Solution

  • I have been wrestling with this particular issue as well. I have found a workaround to access the attributes collection of the model object's properties via the EnvDTE service available to the T4 template. However, it is a bit tortuous and there are some constraints.

    After creating an instance of the EnvDTE service, you will need to reference the project which contains your EF DBContext (assuming your MVC/WebAPI is in a separate project within the same solution). If the DBContext is in a different solution and not available via the EnvDTE service, then I'm not sure you can achieve your goal without referencing your EF assembly and using reflection; but that makes the T4 template less portable.

    Below is a sketch of the code. In my actual implementation I have a routine which locates the project containing the DBContext class. Below I'm referencing the EF project by name instead.

    var env = (DTE)((IServiceProvider)this.Host).GetService(typeof(EnvDTE.DTE));
    var project = dte.Solution.Projects.OfType<Project>().Where(p => p.Name == "your ef project name").FirstOrDefault();
    
    CodeType codeType = project.CodeModel.CodeTypeFromFullName("your ef namespace.class");
    CodeClass cc = (CodeClass)codeType;
    
    List<CodeProperty> cps = cc.Members.OfType<CodeProperty>().ToList();
    
    WriteLine(codeType.FullName);
    WriteLine("======================");
    
    foreach (CodeProperty cp in cps)
    {
        WriteLine(cp.Name);
        foreach (CodeAttribute ca in cp.Attributes.OfType<CodeAttribute>())
        {
            WriteLine("-" + ca.Name);
        }
    }
    

    If you find the attibutes collection of the property is empty it is because the EF model is not in the EnvDTE.Project you are referencing. So you definitely need to access the CodeClass object through the project where the EF models reside.

    Again, this is a rough sketch... I'm sure you will need to tweak this a bit to get the desired result. I'm sure there has to be a better way to do this, but aside from creating a custom scaffolder which extends the ModelMetadata class with the attribute values I'm not sure what the better solution is.

    Hope this makes sense.