The MVC T4 template is now up on CodePlex, and it does change your code a bit

Short version: the MVC T4 template (now named T4MVC) is now available on CodePlex, as one of the downloads in the ASP.NET MVC v1.0 Source page.

Go to T4MVC home page

Poll verdict: it’s ok for T4MVC to make small changes

Yesterday, I posted asking how people felt about having the template modify their code in small ways.  Thanks to all those who commented!  The fact that Scott Hanselman blogged it certainly helped get traffic there :)

The majority of people thought that it was fine as long as

  • It’s just those small changes: make classes partial and action methods virtual. Don’t mess with ‘real’ code!
  • It asks for permission, or at least tells you what it’s doing

I started looking for a way to pop up a Yes/No dialog, but ended up going with a slightly different approach: T4MVC adds a warning line for every item it modifies.  e.g. when you run it, you might see these in the warnings area:

Running transformation: T4MVC.tt changed the class DinnersController to be partial
Running transformation: T4MVC.tt changed the action method DinnersController.Index to be virtual

Some people were worried about version control.  I tried using TFS, and everything worked fine.  i.e. when the template modifies files, VS automatically checks them out.  We’ll need to see how that works for folks using different systems.

What’s new in this version?

The template on CodePlex (version 2.0.01 at the top of the file) supports what I described in my previous post, plus some new goodies.

Refactoring support for action methods

One of the big issues before was the lack of refactoring support.  e.g. when you wrote:

 return RedirectToAction(MVC.Dinners.Details(dinner.DinnerID));

This looked like a call to you Details controller action, but it was actually an unrelated method by the same name.  Hence, if you renamed your action method and refactored, this call was not modified.  It would give a compile error, and had to be hand fixed.

Now the template takes a drastically different approach:

  • It extends the controller class
  • It overrides the action method (hence the need for it to be virtual!)
  • The override never calls the base (that would be very wrong), but instead returns a special ActionResult which captures the call (controller name, action name, parameter value).
  • The template emit a new RedirectToAction (or ActionLink, …) overload which understands this special ActionResults, and turns the call data into a ‘regular’ RedirectToAction call.

Pretty tricky stuff, but it works quite well.  Some credit to my manager Mike Montwill for coming up with this crazy idea!

Because the method you call is an override of the real action method, refactoring works perfectly.  Also, if you F12 (Go To Definition) on the call, it’ll go straight to your Action method and not some generated code.

Unfortunately, Visual Studio doesn’t support refactoring in Views, but 3rd party tools like Resharper and CodeRush do, so if you use one of those, you’re fully covered.

The T4 file automatically runs whenever you build

This was the other big painful issue I was up against: every time you made a change to your code that affect the generated code (e.g. new Action, new View, …), you had to manually save the .tt file to cause it to regenerate the new helper code.

This was a really hard issue, and I must warn you that what I ended up with is more of a workaround than a fix.  However, it is pretty effective, so until we find a better solution, it’ll have to do.

Here is how it works.  Warning: reading this has been shown to cause headaches in lab rats:

  • As part of its execution, the T4 file finds itself in the VS project system (it had to do that anyway)
  • It then runs the magic instruction ‘projectItem.Document.Saved = false; ’, which causes it to become dirty.
  • It then proceeds to do its code generation, leaving its file in an unsaved state
  • Next time you Build your project, VS first saves all the files
  • This causes the ‘dirty’ T4 template to execute, mark itself as dirty again, and redo its code generation
  • You get the idea!  If you feel like the lab rats, this may help.

One caveat is that you have to initiate the cycle by opening and saving T4MVC.tt once.  After you do that, you don’t need to worry about it.

Credit for this idea goes to Jaco Pretorius, who blogged something similar.

The template generates static helpers for your content files and script files.  So instead of writing:

 <img src="/Content/nerd.jpg" />

You can now write:

 <img src="<%= Links.Content.nerd_jpg %>" />

Likewise, instead of

 <script src="/Scripts/Map.js" type="text/javascript"></script>

You can write:

 <script src="<%= Links.Scripts.Map_js %>" type="text/javascript"></script>

The obvious benefit is that you’ll get a compile error if you ever move or rename your static resource, so you’ll catch it earlier.

Another benefit is that you get a more versatile reference.  When you write src="/Content/nerd.jpg", your app will only work when it’s deployed at the root of the site.  But when you use the helper, it executes some server side logic that makes sure your reference is correct wherever your site is rooted.  It does this by calling VirtualPathUtility.ToAbsolute("~/Content/nerd.jpg").

One unfortunate thing is that for some reason, VS doesn’t support intellisense in the view for parameter values.  As a workaround, you can type it outside of the tag to get intellisense and then copy it there.

More consistent short form to refer to a View from a Controller class

Previously, it supported an _ based short form inside the controller:

 return View(View_InvalidOwner);

That was a bit ugly.  Now, the short form is:

 return View(Views.InvalidOwner);

Here, Views.InvalidOwner is the same as MVC.Dinners.Views.InvalidOwner, but can be shortened because ‘Views’ is a property on the controller.

Many bug fixes

I also fixed a number of bugs that people reported and that I ran into myself, e.g.

  • It supports controllers that are in sub-folders of the Controllers folder and not directly in there
  • It works better with nested solution folder

I’m sure there are still quite a few little bugs, and we’ll work through them as we encounter them