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

MVC 5 Custom View Templates

The out-of-the-box template that comes with Visual Studio has improved a great deal since I first started building ASP.Net Web Pages several years ago. But, as nice as the new template is, it’s still nice to be able to adjust the T4 templates that are used to generate the Views. I’ve known for a long time that I could modify these templates globally, by modifying them directly in the Visual Studio folder, but, it wasn’t until earlier this week that I learned you can customize them for just your single project. This post will demonstrate how to do that. In my next post, I’ll demonstrate how to access the attributes on your Model’s properties, from inside the T4 templates.

For the purpose of this demo, I will be using Visual Studio 2013, MVC 5, EF 6, and .net 4.5.1.

For our sample, we will create a simple application that allows for basic CRUD of a person object. So, fire up NuGet and install EF 6 and add a calls called PersonModel to Models folder:

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

Now, add a basic Person controller, an Empty one so that only the Index Get method is created:

image

namespace CustomViewTemplate.Controllers
{
    public class PersonController : Controller
    {
        // GET: Person
        public ActionResult Index()
        {
            return View();
        }
    }
}

If you right-click inside Index() and choose Add View, use scaffolding to add a List view.

image

Now, the view will open and you’ll see the default List view, which just happens to be a table.

Take special notice to lines 9-11:

<p>
    @Html.ActionLink("Create New", "Create")
</p>

Now, of course this will create a standard issue hyperlink, but what if, for the sake of this example, we want it to be a button instead. Well, we could just change the HTML, but if we wanted this to be a button by default, we would have to change the button. So, first things first, we need to identify the location of the global template files. That location is

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

Now, you can modify the .t4 files in this directory, but ONLY do that if you want the changes to be global. To make changes only to the project you are working in, copy the direct to a new folder in your project’s root, called “CodeTemplates”. You only need to copy down the files you will want to change, but it is important that you copy the folder structure down. That is, we want to modify a file called List.cs.t4, so we need to copy our file to “~ProjectFolder~\CodeTemplates\MvcView\List.cs.t4”. So for me, that looks like this:

C:\My Applications\Playground\CustomViewTemplate\CustomViewTemplate\CodeTemplates\MvcView\List.cs.t4

Note, if you are using VB, you’ll want List.vb.t4.

IMPORTANT NOTE
You also need to copy over MvcView\Imports.include.t4 and MvcView\ModelMetadataFunctions.cs.include.t4

Back in Visual Studio, in Solution Explorer, you’ll need to “Show all files”, and do a refresh, but you should see your CodeTemplates folder now. Of course, you won’t need the templates for deployment, but if you are modifying them, you should add to source control, so go ahead and right-click and “Include in project”.

Now, you can open the t4 file, but beware, VS doesn’t come with a T4 editor out-of-the-box. That means, no styling and no intellisense. There are, however, some good T4 editor’s in the VS extensions. I use tangible T4 Editor 2.2.5 plus modeling tools for VS 2013 (free edition). Of course, you’ll need Visual Studio to be closed before you can run the installer.

Remember our hyperlink we want to change to a button, well, there’s no special t4 stuff going on with that, and this isn’t a tutorial on the basics of t4, so with List.cs.t4 open, look for our HTML snippet above and change it to:

<p>
    <button type="button" onclick="addNewPerson();">Create New Person</button>
</p>

Now save the file. Now, back to the controller and right-click inside the Index method, and select AddView and add a new List View, but overwrite the existing one.

Now, when you look at the new version of Index.cshtml, you’ll see that instead of a hyperlink, you now have a button!

Happy Templating!