T4MVC 2.2 update: Routing, Forms, DI container, fixes

To get the latest build of T4MVC:

Go to T4MVC home page

This post is a continuation of various recent posts, most notably:

First, I’d like to thank all those who are using the MVC T4 template and sent me suggestions and bug reports.  Most issues have been addressed, and most suggestions have been integrated.  I’m up to the 8th CodePlex drop, and it’s only been a week! You can see the history of changes at the top of the .tt file.

Frankly, when I started playing with this, I just thought it’d be a fun thing to spend the afternoon on.  Instead, I have probably spent close to half my time working on it in the last week.  And I do have other things to work on! :)  But it’s been a lot of fun, so no regrets!

I’m not going to detail every change since the last blog post, since many are bug fixes that are not particularly exciting (though they help make the thing work!).  Instead, I’ll just focus on a few new key areas.

Strongly typed support for MapRoute

This is similar to the RedirectToAction/ActionLink support, but applied to route creation.  The original Nerd Dinner routes look like this:

 routes.MapRoute(
    "UpcomingDinners", 
    "Dinners/Page/{page}", 
    new { controller = "Dinners", action = "Index" }
);

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
);

The ‘defaults’ line of each route is what we’re trying to fix.  It makes heavy use of anonymous objects and literal strings, which should be avoided whenever possible.  If your controller or action name change, you won’t easily catch it.

Previous version of T4MVC let you change that line to:

 new { controller=MVC.Dinners.Name, action=MVC.Dinners.Actions.Index }

This fixes the issue for the most part, but it’s a bit wordy and doesn’t support refactoring.  Now, the latest version of T4MVC lets you change those routes to:

 routes.MapRoute(
    "UpcomingDinners", 
    "Dinners/Page/{page}", 
    MVC.Dinners.Index(null)
);

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    MVC.Home.Index(),
    new { id = "" }
);

Note how we are now pointing at the action by simply making a call to it (though it’s a pseudo-call, and your action method won’t actually be called).

Also note that in the second route, we’re still using an anonymous object for the ‘id’.  This is because even though the route expects an id, the default Action method (HomeController.Index()) doesn’t take one.  You could get around this by adding a ‘string id’ param to HomeController.Index() and ignoring it in the method.  You could then write MVC.Home.Index(null) in the route and avoid all anonymous objects.

BeginForm support

This came courtesy of Michael Hart.  Essentially, it’s the same support as ActionLink() but for the BeginForm() method.  But note that because form posts typically pass most of the data via the form and not the URL, BeginForm() is trickier to use correctly than the other methods.

Here is how you might use this:

 using (Html.BeginForm(MVC.Account.LogOn(), FormMethod.Post)) { ... }

Or if the action method takes parameters, you would pass them in the call.  However, when you do this you need to make sure that you aren’t also trying to pass those same parameters via the form (e.g. using a text box).

Generally, the rule of thumb is that this support works well when you’re dealing with forms where the Action method signature exactly matches the form URL.

Support for Dependency Injection containers

Though this is a bug fix rather than a new feature, I’ll mention it because it affected several people and had me puzzled for a while.

The root of the problem is that T4MVC generate new constructors for the controllers, and that DI containers use a somewhat arbitrary algorithm to decide what constructor to use.  And in those users’ cases, they ended up calling the new constructor generated by T4MVC, messing things up badly.

The fix was simply to change the generated constructor to be protected, to prevent DI containers from using it.  Since T4MVC only uses it when it instantiates the controller derived class, protected works fine.

Well, I should note that I haven’t actually verified that the fix addresses the issue, but I’m sure those users will tell me if it doesn’t! :)