Question

Can you create custom data annotations for the model that can be read inside the T4 template for the View like property.Scaffold is read? I would like to add data annotation parameters like Scaffold based on which I would build the view.

Thank you

Was it helpful?

Solution 2

So, this is how you do it. Follow this tutorial on how to create a custom attribute http://origin1tech.wordpress.com/2011/07/20/mvc-data-annotations-and-custom-attributes/

To read this attribute values in the T4 scaffolding templates, first add the template files as described here http://www.hanselman.com/blog/ModifyingTheDefaultCodeGenerationscaffoldingTemplatesInASPNETMVC.aspx

Then, for example, open List.tt from the AddView folder. This template creates the Index view.

Go to the end of the template file and find the definition for class ModelProperty. Add your property value to it ( public string MyAttributeValue { get; set; }

Now go a bit down in the List.tt and find bool Scaffold(PropertyInfo property) method. You will need to add your own attribute property reader. This method, for the above mentioned tutorial, would be:

string OptionalAttributesValueReader(PropertyInfo property){
    foreach (object attribute in property.GetCustomAttributes(true)) {
        var attr = attribute as OptionalAttributes ;
        if (attr != null) {
                return attr.style;
        }
    }
    return String.Empty;
}

Then find the method List GetEligibleProperties(Type type) at the bottom of the file. Add your reader to it like this:

            ...
            IsForeignKey = IsForeignKey(prop),
            IsReadOnly = prop.GetSetMethod() == null,
            Scaffold = Scaffold(prop),
            MyAttributeValue =  OptionalAttributesValueReader(prop)

When you want to use and read this attribute you can do it like the Scaffold property is used in the List.tt

      List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
      foreach (ModelProperty property in properties) {
          if (property.MyAttributeValue != String.Empty) {
              //read the value
              <#= property.MyAttributeValue #>  
           }
       }

Since these classes are defined in my project, I had to add my project dll and namespace to the top of the List.tt:

     <#@ assembly name="C:\myProjectPath\bin\myMVCproject.dll" #>
     <#@ import namespace="myMVCproject.CustomAttributes" #>

If your model changes and you need to find these new changes in the scaffolding, you need to rebuild your project.

Hope anyone looking for the solution will find this useful. Ask if there is anything unclear.

OTHER TIPS

I wrote a blog post on the solution I came up with for MVC5. I'm posting it here for anyone who comes along: https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/

Edit: In your entities, decorate property with custom Attribute

namespace CustomViewTemplate.Models
{     
     [Table("Person")]
     public class Person
     {
         [Key]
         public int PersonId { get; set;}

         [MaxLength(5)]
         public string Salutation { get; set; }

         [MaxLength(50)]
         public string FirstName { get; set; }

         [MaxLength(50)]
         public string LastName { get; set; }

         [MaxLength(50)]
         public string Title { get; set; }

         [DataType(DataType.EmailAddress)]
         [MaxLength(254)]
         public string EmailAddress { get; set; }

         [DataType(DataType.MultilineText)]
         public string Biography { get; set; }     
     }
}

With this Custom Attribute

namespace CustomViewTemplate
{
     [AttributeUsage(AttributeTargets.Property)]
     public class RichTextAttribute : Attribute
     {
         public RichTextAttribute() { }
     }
}

Then create a T4Helper that we'll reference in our template

using System; 

namespace CustomViewTemplate
{
     public static class T4Helpers
     {
         public static bool IsRichText(string viewDataTypeName, string propertyName)
         {
             bool isRichText = false;
             Attribute richText = null;
             Type typeModel = Type.GetType(viewDataTypeName);

             if (typeModel != null)
             {
                 richText = (RichTextAttribute)Attribute.GetCustomAttribute(typeModel.GetProperty(propertyName), typeof(RichTextAttribute));
                 return richText != null;
             }

             return isRichText;
         }
     }
}

This is how I did it in MVC 5. I did this a long time ago and I may be forgetting stuff, I'm just copy/pasting what I see in my modified templates.

I needed a way to set the order of properties in (for example) the create/edit views or in the list view table. So I created a custom attribute OrderAttribute with an integer property Order.

To access this attribute in the T4 templates I modified the file ModelMetadataFunctions.cs.include.t4. At the top I added one method that retrieves the Order value set in the attribute from a PropertyMetadata object, and another method to simply order a list of PropertyMetadata items by that order:

List<PropertyMetadata> GetOrderedProperties(List<PropertyMetadata> properties, Type modelType) {
    return properties.OrderBy<PropertyMetadata, int>(p => GetPropertyOrder(modelType, p)).ToList();
}

int GetPropertyOrder(Type type, PropertyMetadata property) {
    var info = type.GetProperty(property.PropertyName);
    if (info != null)
    {
        var attr = info.GetCustomAttribute<OrderAttribute>();
        if (attr != null) 
        {
            return attr.Order;
        }
    }
    return int.MaxValue;
}

Finally, in the List template for example, I have added a part where I call the GetOrderedProperties method:

var typeName = Assembly.CreateQualifiedName("AcCtc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ViewDataTypeName);
var modelType = Type.GetType(typeName);

var properties = ModelMetadata.Properties.Where(p => p.Scaffold && !p.IsPrimaryKey && !p.IsForeignKey && !(p.IsAssociation && GetRelatedModelMetadata(p) == null)).ToList();
properties = GetOrderedProperties(properties, modelType);

foreach (var property in properties)
{
//...
}

Unfortunately I needed the name of the project to be able to create a Type object which I needed to get the attributes from. Not ideal, perhaps you can get it some other way but I couldn't manage it without this string including all the version stuff.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top