MVC 5 T4 Templates and View Model Property Attributes


Yesterday, I published a blog post demonstrating how to modify your T4 templates for your single project.  Today, I’m going to show you how to access an attribute from your Model in your T4 template.  Beginning with MVC 5, the T4 templates no longer use ModelProperty class to iterate over the properties in your Model.  ModelProperty has direct access to Attributes, and there are plenty of resources on the web on how to read the attributes in your T4 using that.  In MVC 5, however, ModelProperty has been replaced with PropertyMetaData, which does not have access to the attributes of your properties.  Further, I found it very difficult to find a solution for MVC5 on the web, until finally I came across an old MSDN Magazine article, which was only helpful because I could download the sample project and take a look inside.  The article itself is missing referenced images and code samples.  So, I’m taking what I learned, and am creating this, hopefully simple, post to demonstrate.

For our purposes today, we will be extending our project from yesterday to make the Biography property on the Person model a RichText field.  So, for a reminder, here is our Person model:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
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; }     
     }
}

To demonstrate that we can also use a custom attribute, let’s create one.  Create a class called RichTextAttribute in the project root directory:

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

Then, back to PersonModel, add the RichText attribute to the Biography property:

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

Now, copy the following global template files from

C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcView\

to a folder called

CodeTemplates\MvcView\

  • Create.cs.t4 (OR Create.vb.t4)
  • Imports.include.t4
  • ModelMetaDataFunctions.cs.include.t4 (OR ModelMetaDataFunctions.vb.t4)

Now, remember the PropertyMetaData, doesn’t have access to the attributes, so we need to create a new class with a function that does the work for us, so let’s create a class called T4Helpers, with a public function called IsRichText.

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;
         }
     }
}

We are using our custom RichTextAttribute here, but you could use any Attribute you would like.  What we are doing is returning true or false; indicating that the given property either does or does not have the attribute we are checking for.

Before we can use this function in our T4 template, we’ll need to Build our project and then add a reference to our assembly in the imports file.

Open your projects copy of Imports.include.t4 and add a reference to your assembly as line 4.  For me, it looks like this:

<#@ assembly name="C:\My Applications\Playground\CustomViewTemplate\CustomViewTemplate\bin\CustomViewTemplate.dll" #>

As you are typing the line of code above, if you are using tangible T4 Editor, you might see an inellisense option like so:

image

and you might be tempted to use it, but there is a bug that will prevent this form working.

Okay, now we can modify our T4 template.  Open Create.cs.t4 and look for the foreach loop that iterates over our list of properties.  You’ll find this around like 96:

<# foreach (PropertyMetadata property in ModelMetadata.Properties) {      if (property.Scaffold && !property.IsAutoGenerated && !property.IsReadOnly && !property.IsAssociation) {          // If the property is a primary key and Guid, then the Guid is generated in the controller. Hence, this property is not displayed on the view.          if (property.IsPrimaryKey && IsPropertyGuid(property)) {              continue;          } #>

To avoid unnecessary calls to our new method, we’ll only check for the RichText attribute against properties that we will actually be rendering, so after the inner If statement (the one that checks for primary key’s and Guid’s), we can add this line:

bool isRichText = CustomViewTemplate.T4Helpers.IsRichText(ViewDataTypeName, property.PropertyName);

Now, the tricky part is using our new variable and finding the correct place for it.  For our purposes, we will put it inside the nested if statement that is checking for a checkbox.  So, that entire if block would look like this with our new code added:

             if (isCheckbox) {
#>


<div class="checkbox">
<#                  PushIndent("    "); #>
                 @Html.EditorFor(model => model.<#= property.PropertyName #>)
<#              } else if (property.IsEnum && !property.IsEnumFlags) { #>
                 @Html.EnumDropDownListFor(model => model.<#= property.PropertyName #>, htmlAttributes: new { @class = "form-control" })
<#              } else if (isRichText) { #>
                 // TODO: Place RichTextBox HTML here
<# } else { #>
                 @Html.EditorFor(model =>; model.<#= property.PropertyName #>, new { htmlAttributes = new { @class = "form-control" } })
<#              }           } else { #>
                 @Html.EditorFor(model => model.<#= property.PropertyName #>) 
<#          } #>

Now, you can see that I just put some generic text instead of an @Html helper, because the implementation details of RichText aren’t important for the purposes of this tutorial.

To verify everything is working properly, we can save changes, go to our PersonController and add a Create action, Right-Click inside to Add View, and add a Create view.

Inside the generated Create.cshtml, you can scroll down and see that our Biography field has our placeholder text, instead of a @Html helper function.



<div class="form-group">
    @Html.LabelFor(model => model.Biography, htmlAttributes: new { @class = "control-label col-md-2" })


<div class="col-md-10">
        // TODO: Place RichTextBox HTML here
        @Html.ValidationMessageFor(model => model.Biography, "", new { @class = "text-danger" })
    </div>


</div>


If you’ve found this tutorial helpful, please leave a comment below!

Advertisements

4 thoughts on “MVC 5 T4 Templates and View Model Property Attributes

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s