Skip to main content

Modifying Code Generation Templates for MvcContrib Grid

Visual Studio 2010 in general, and MVC 2 in particular, bring the concept of code generation templates into the realm of first-class objects. Well, maybe it's not that good. However, MVC definitely makes tremendous use of the code generation template system, called Text Transformation Template Toolkit (or T4 for short). A fantastic introduction to T4 editing can be found in the January 2010 issue of MSDN Magazine, titled "Text Template Transformation Toolkit and ASP.NET MVC." That article covers the basics of editing T4 templates, so I won't bother repeating the same information. Instead, I'll describe the template I created for our team's latest project.

Before I get to the template itself, I need to describe the tools we're using, how to install them, and a little background on why we decided to do what we're doing.

The tools

MvcContrib Grid

Early on, we did what most teams do. We used the tools provided. For creating a list of strongly-typed objects (users, items, etc.), we simply chose the default "List" template that ships with MVC 2. Sure, it made our jobs easy, but I wasn't a huge fan of the HTML it generated.

Essentially, the List template uses reflection to look at the public properties of your model, and creates a simple HTML table using the raw property names as column headers. It also adds links to each row for "edit," "details," and "delete" actions. After the table's close tag, a handy link is provided to your "add" action.

About a month or so into our project, we discovered the MvcContrib project and its Grid helper. After experimenting with it for a few days, we decided that we preferred it to the default List view code, and that would use it exclusively for generating all of our List views.

At its simplest, given a strongly-typed Model, it will generate a grid very similar to what the default template gives you with a single line of code:

<%:Html.Grid(Model).AutoGenerateColumns()%>

For us, the concise and simple View made it a no-brainer for us. From that time forward, we simply created an empty strongly-typed View and then added the line above, any additional ActionLinks we needed, and we were done.

I'm nothing if not lazy, so I wanted to see what it would take to avoid even typing that one line above! I remembered reading the T4 article mentioned above, and thought I'd spend some time trying to make a new template that would do the work for me. Of course, if something is worth doing, then someone has probably already done it.

In this case, it was done more than a year ago, by a gentleman named Shiju Varghese. In his post titled, "Add a new T4 template for making MVCContrib Grid Helper Component," he describes how to alter the default List template into a new one that uses the MvcContrib grid. Perfect! I decided to start there.

tangible T4 Editor

So now I needed a way to edit the .tt files. Sure, I could open them as raw text file, but where's the fun in that? If you're running Visual Studio 2008 or 2010, there is a great free add-on called the tangible T4 Editor. Essentially, it provides syntax coloring and IntelliSense to Visual Studio while editing the .tt files. So I visited the download site and installed the add-on.

Editing the List Template

Once the template editor was installed, I had to find the existing List template and modify it. This turned out to be pretty straightforward. In my case, the MVC view templates can be found at C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates\AddView. On a 64-bit machine, check C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates\AddView. The template that I wanted to edit is "List.tt," which I copied into the same directory and immediately renamed it to "Grid.tt" to differentiate it from the original.

The really cool thing about this is that Visual Studio automatically recognizes it displays it in the Add View dialog.

One question you may be asking is, "If you already have the template, why do you still want to edit it?" One problem with both the default List template and Shiju's grid template is that neither honors the DataAnnotations on your view model. Specifically, our project requires us to support multiple languages. Simply iterating over the model's properties and blindly displaying the property name in the column header is not an option for us. We need to ensure that the column headers are localized. Furthermore, there may be columns we don't want to display at all. Fortunately, later versions of the MvcContrib Grid make this very simple.

So starting with Shiju's code as an example, I made my template look like this.

// See if the model has primary keys and generate appropriate ActionLinks for Edit and Delete.
List primaryKeys = GetEntityKeyProperties(mvcHost.ViewDataType);
String editAction = String.Empty;
String deleteAction = String.Empty;

if(primaryKeys.Count > 0) {
 editAction = String.Format("Html.ActionLink(\"Edit\", \"Edit\", new {{ id=x.{0} }})", primaryKeys[0]);
 deleteAction = String.Format("Html.ActionLink(\"Delete\", \"Delete\", new {{ id=x.{0} }})", primaryKeys[0]);
} else {
 editAction = "Html.ActionLink(\"Edit\", \"Edit\", new { /* id=x.PrimaryKey */ })";
 deleteAction = "Html.ActionLink(\"Delete\", \"Delete\", new { /* id=x.PrimaryKey */ })";
}

if(!String.IsNullOrEmpty(mvcViewDataTypeGenericString)) 
{
 Dictionary properties = new Dictionary();
 FilterProperties(mvcHost.ViewDataType, properties);
#>
 <%<#=nugget#> Html.Grid< <#=mvcHost.ViewDataType#> >(Model).AutoGenerateColumns()
 .Columns(column=>
  {
  column.For(x=><#=editAction#>).Encode(false);
  column.For(x=><#=deleteAction#>).Encode(false);
  })
  .Empty("There are no records.")
  %>
  <#}#>
(Download the entire TT file here)

Notice that I'm using AutoGenerateColumns(), and then adding the additional columns afterward. The beauty of this approach is that AutoGenerateColumns() honors the various attributes in the System.ComponentModel.DataAnnotations namespace. All of the display decisions (order, template, localized header name) can be handled in a declarative manner in the view model. For example, [DisplayName] and [ScaffoldColumn] on your view model's properties will automatically be reflected in the View. Likewise, any changes to your view model will be handled automatically when your project is rebuilt.

There is a known issue with using [DisplayName] that I won't go into here, except to say that if you're using MVC 2 with ASP.NET 4, you have to subclass the DisplayNameAttribute to get it to work. This is an issue with .NET, and not with the MvcGrid, and does not affect the use of the template.

Additional Enhancements

You may have noticed that I didn't localize the edit and delete ActionLinks, nor did I bother with an add ActionLink. I am still trying to come up with a generic way to handle that in a way that's still localizable. One thought I had is to access a properties on the view model called "AddTitle," "EditTitle," and "DeleteTitle." These could be read-only properties on a view model base class that simply returns the appropriate localized resource. I may end up taking that approach, but it still means infusing my template with domain knowledge. Maybe that's not a horrible thing. I'd love to hear comments from others who have run into the same problem.

Comments

Popular posts from this blog

How to copy your Frozen Free Fall progress to a new phone

It's happened to all of us. You are about to get a branch new smartphone, when it hits you. You're on level 250 of Frozen Free Fall. If you get a new phone, you'll lose all of that progress! Ok, admittedly this isn't the most pressing problem of our time, but it's annoying. So today I decided to do something about it.

I have a Samsung Galaxy S4 (Android), and just received an iPhone 5c. Before you bash me on my phone choice, let me explain that the iPhone is provided by my employer at no cost to me. Now you may proceed to bash me for putting games on my work phone. 
First step: Frozen Free Fall had already been installed on both devices. Next, using the Astro File Manager on my Galaxy, I searched and found the Frozen game save stored in /storage/sdcard0/Android/data/com.disney.frozensaga_goo/files/user.dat. I imagine it will be in the same location on any Android phone. If not, just look for user.dat in a folder with a similar name. So, using a USB cable and the A…

Ionic vs. Bootstrap - for a Web App

Ionic 1.x vs Bootstrap 3.x for a Web AppI was recently asked at work to come up with a comparison between Ionic Framework and a more traditional Angular/Bootstrap combination to create a web app. The application will primarily be used in a desktop web browser (probably Chrome or IE). There are also some use cases where it will be accessed from Safari on an iPad. However, this is purely a web; there are no plans to install the app onto the iPad as a hybrid app. Thus, recommending Ionic to build the UI hadn't occurred to me until the request was made.

This is even more surprising in that I recently published a Pluralsight course on Ionic Framework 1. It should have been the first thing that crossed my mind.
One constraint is that currently only Angular 1.x and Bootstrap 3 are authorized web technologies. Ionic 1.3 was recently approved, but not Ionic 2, Angular 2, or TypeScript yet.
Given those constraints, herein is my attempt at coming up with reasons to use (or not to use) Ionic…

How I Finally Got AdMob and Ionic Framework to Play Nice Together

Note: Some of the plugins referenced in this article have had their names, and possibly their APIs, changed. The information contained herein may no longer work. Please don't rely on it until I have had a chance to review and update it. -Mike Callaghan
TLDRThis is a summary of how to display ads in my MDCL (1650) mobile application, which was written using the Ionic Framework. If you want to see the sample project that displays both banner and interstitial ads, you can look at my AdMob Demo app on GitHub.

AdMob
The first thing that's necessary is to get an AdMob account, which you can create at https://www.google.com/admob/ Ionic Starter App
If you don't already have an ionic app, you can create one with the following command, which will create a folder named myApp in your current folder, and initialize it with an empty application.
ionic start myApp blank



ngCordova
Next, you'll need to install the ngCordova bower package and include that in your project. This script does m…