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.
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
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.
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.
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:
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.
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:
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.
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.
I also fixed a number of bugs that people reported and that I ran into myself, e.g.
I’m sure there are still quite a few little bugs, and we’ll work through them as we encounter them