Documentation - T4MVC/T4MVC GitHub Wiki

T4MVC Documentation page

1. Introduction

T4MVC is a T4 template for ASP.NET MVC apps that creates strongly typed helpers that eliminate the use of literal strings when referring the controllers, actions and views. It helps make your MVC code much more maintainable, and gives you intellisense where you normally would not have any. It was started by David Ebbo as a little blog post thing, and has since gained many new features based on great community feedback.

T4MVC runs in Visual Studio 2010, 2012 and 2013, and supports ASP.NET MVC 4.0 and 5.0 (older MVCs may work but are untested).

To use it, simply install the T4MVC package using NuGet. You will get T4MVC.tt in your project, and will instantly see a number of files get created in Visual Studio under it.

The code in those generated files enable a whole range of scenarios, all relating to replacing unwanted literal strings with a strongly typed alternative. The documentation below gives a complete list of those scenarios.

1.1 It's still just standard MVC under the covers

One key concept to understand about T4MVC is that it is just a thin layer over MVC. e.g. while it provides strong typing, in the end it's just putting values in a dictionary, and using standard MVC to generate the routes.

One thing I often suggest when people have questions is to first figure out how they would do things without T4MVC. If you have no idea how it would work, or think it may not be possible at all, then you are probably expecting too much of T4MVC! Generally, it will not do anything that MVC does not support.

2. Scenarios

Instead of a formal documentation, we'll just demonstrate by example the various scenarios where T4MVC helps improve your code. The examples are shown in a before/after format to outline the change that is made.

Note: a lot of the examples given below are based on the Nerd Dinner sample MVC application.

2.1 Using View Name constants

Any time you would use a literal string to refer to a view, T4MVC gives you a strongly typed alternative. e.g. suppose you have this code in a view:

<% Html.RenderPartial("DinnerForm"); %>

This ugly "DinnerForm" literal string needs to go! Instead, you can now write:

<% Html.RenderPartial(MVC.Dinners.Views.DinnerForm); %>

When you need to use a view name inside a controller, things get even easier. e.g. you can replace:

return View("InvalidOwner");

by

return View(Views.InvalidOwner);

Note how you can omit the MVC.Dinners prefix, since this is implied by the fact that the code lives in the Dinners controller.

2.1.1 Non-Qualified View Name Constants

Non-qualified view names can be used via the ViewNames property underneath the Views property. Non-qualified views are usefull for allowing overloading and are needed by certain setups, e.g. Spark.

Traditional "Magic String" Non-Qualified view:

return View("Index");

T4MVC Non-Qualified view:

return View(Views.ViewNames.Index);

2.2 Referring to controller actions

Many MVC APIs follow a pattern where you need to pass three pieces of information in order to refer to a controller action:

  1. the controller name (often implied when dealing with the current controller)
  2. the action method name
  3. the parameters to be passed to the action method

All of this is typically done by passing literal strings for #1 and #2, and an anonymous object for #3, e.g.

<%= Html.ActionLink("Delete Dinner", "Delete", "Dinners", new { id = Model.DinnerID }, null)%>

Here, "Delete" and "Dinners" are clear literal strings that we'd like to get rid of. But that's not all, as the parameter name 'id' is as much of a problem as those other two. Even though it doesn’t look like a literal string, it very much is one in disguise. Don’t let those anonymous objects fool you!

With T4MVC, you would instead write:

<%= Html.ActionLink("Delete Dinner", MVC.Dinners.Delete(Model.DinnerID))%>

Basically, we got rid of the three unwanted literal strings (“Delete”, "Dinners" and “Id”), and replaced them by a very natural looking method call to the controller action. Of course, this is not really calling the controller action, which would be very wrong here. But it’s capturing the essence of method call, and turning it into the right route values.

One other great benefit of this approach is that you get full intellisense while you type this line. In fact, you even get full refactoring support if you use a tool like resharper which supports aspx refactoring!

Adding additional route parameters

In some situation, you may need to add extra values to the route that don’t exist in the action method. You can do it as follows:

Html.ActionLink("Delete Dinner", MVC.Dinners.Delete(Model.DinnerID)
    .AddRouteValue("foo", 17))

You can add multiple values this way using a fluent approach, e.g.

Html.ActionLink("Delete Dinner", MVC.Dinners.Delete(Model.DinnerID)
    .AddRouteValue("foo", 17)
    .AddRouteValue("bar", "abc"))

As an alternative for adding multiple values, you can write:

RedirectToAction(MVC.Home.MyAction().AddRouteValues(Request.QueryString));

Note that in some cases, you may want to omit the action method parameters from the route, possibly because they are meant to come from form data and not from the URL. For those cases, you can always use the parameter-less override that T4MVC generates (that part is not new), e.g.

Html.ActionLink("Delete Dinner", MVC.Dinners.Delete()
    .AddRouteValues(new { foo = 17, bar = "abc"}))

Now suppose you want to add all the current query string values to the route values produced by T4MVC. You can use the AddRouteValues() overload that takes a NameValueCollection. e.g.

Html.ActionLink("Delete Dinner", MVC.Dinners.Delete()
    .AddRouteValues(new { foo = 17, bar = "abc"}))

This adds to the existing set of AddRouteValue/AddRouteValues fluent APIs to make it easier to deal with all kind different situations.

The nice thing about this fluent approach is that it doesn’t require adding overloads to all the T4MVC helpers (e.g. Html.ActionLink is this case) to take in the extra route values. Instead, it automatically applies to any method that uses the T4MVC pattern.

The general pattern of making a pseudo-call to a controller action is used is many places in T4MVC:

2.2.1 Html.ActionLink

Ok, we just covered that one above.

2.2.2 Url.Action

<%= Url.Action(MVC.Dinners.Delete(Model.DinnerID))%>

2.2.2a Url.JavaScriptReplaceableUrl

Two use cases for the above.

1st Use Case: If you have an action you want to call from javascript like this:

<%= Url.Action(MVC.Dinners.Display(Model.DinnerID))%>

Odds are you want to get the Model.DinnerID from a javascript variable. So you end up with

var myUrl=<%= Url.Action(MVC.Dinners.Display())%>
//myUrl will be  /Dinners/Display
var myFinalUrl=myUrl+"/"+dinnerId;   ///Dinners/Display/12
//or does it look like 
var myFinalUrl=myUrl+"?dinnerID"+dinnerId;  ///Dinners/Display?dinnerId=12

The issue is you are not sure how to compose the URL and if it changes, this will silently break.

2nd Use Case: For the above action in your routing table you created a route like: "Dinners/Display/{dinnerId}" - and you constrain dinnerId to be an integer larger than zero.

Now, if you do the same scenario above:

var myUrl=<%= Url.Action(MVC.Dinners.Display())%>

The route will not be found - because there is not a route match (since dinnerId was not provided).

JavaScriptReplaceableUrl solves both of these issues No matter which use case, you define a specific route for the action (constrained parameters or not) In the routing table you add: "Dinners/Display/{dinnerId}" - (constrain dinnerId if you want) Note: You must define a specific route for JavaScriptReplaceableUrl to work like this - it cannot work with 'default routes'. If it cannot find a matching route it behaves like Url.Action().

var myUrl=<%= Url.JavaScriptReplaceableUrl(MVC.Dinners.Display())%>
//myUrl will be Dinners/Display/{dinnerId} 
var myFinalUrl=myUrl.replace("{dinnerId}", dinnerId);   //this will have whatever route shape you defined in routing table!

Note you can use the parameter name constants from T4MVC (and that will give you compile time checking for parameter name changes:

var myFinalUrl=myUrl.replace("{"+MVC.Dinners.DisplayParams.dinnerId+"}", dinnerId);   //this will have whatever route shape you defined 

2.2.3 Ajax.ActionLink

<%= Ajax.ActionLink( "RSVP for this event",
                     "Register", "RSVP",
                     new { id=Model.DinnerID },
                     new AjaxOptions { UpdateTargetId="rsvpmsg", OnSuccess="AnimateRSVPMessage" }) %>

becomes

<%= Ajax.ActionLink( "RSVP for this event",
                     MVC.RSVP.Register(Model.DinnerID),
                     new AjaxOptions { UpdateTargetId="rsvpmsg", OnSuccess="AnimateRSVPMessage" }) %>

2.2.4 RedirectToAction (in controller code)

return RedirectToAction("Details", new { id = dinner.DinnerID });

becomes

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

2.2.5 routes.MapRoute (typically in global.asax)

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

becomes

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

2.2.6 Html.BeginForm

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.

2.2.7 Html.RenderAction and Html.Action (only in MVC 2 or newer)

These are new to MVC 2. See Phil Haack's post about them for details.

<%= Html.Action("Menu", new { options = new MenuOptions { Width=400, Height=500} })%>

becomes

<%= Html.Action(MVC.Home.Menu(new MenuOptions { Width=400, Height=500})); %>

Same deal for Html.RenderAction.

2.3. Use constants to refer to area, controller, action and parameter names

2.3.1 Area name constants

MVC.CoolArea.Name

2.3.2 Controller name constants

MVC.Home.Name

2.3.3 Action name constants

MVC.Home.ActionNames.Index

2.3.4 Parameter name constants

Assuming a Home.MyAction action that takes a foo parameter, this evaluates to "foo":

MVC.Home.MyActionParams.foo

2.4. Strongly typed links to script files and static resources

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

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

You can 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>

Additionally T4MVC also scans the Areas for content folders and create static helpers for these too. e.g. if you have an area called CustomerPortal with its own logo then it would look like the following:

<img src="<%= Links.Areas.CustomerPortal.Content.Logo_png %>" />

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.

Important note: if you see strange links getting generated in your <head> tag, you just need to remove the runat="server" from it.

Support for MVC's script and style bundles

Starting from MVC4, one can define "bundles" of scripts and stylesheets which are automatically combined and minified. However, magic strings are used to define the resource URLs and the files included in each bundle. To eliminate them, follow steps 1 to 3 below.

  1. Add the following to your own code, which will extend T4MVC's static partial "Links.Bundles.Scripts" and "Links.Bundles.Styles" classes:
  namespace Links {
    public static partial class Bundles {
      public static partial class Scripts {
        public static readonly string jquery = "~/scripts/jquery";
        public static readonly string jqueryui = "~/scripts/jqueryui";
      }
      public static partial class Styles {
        public static readonly string bootstrap = "~/styles/boostrap";
        public static readonly string theme = "~/styles/theme";
        public static readonly string common = "~/styles/common";
      }
    }
  }

Notice how the magic strings are now defined in one place only, and are thus easy to maintain.

  1. Use the following to add your bundles to the runtime:
  bundles.Add(new ScriptBundle(Links.Bundles.Scripts.jquery).Include("~/scripts/jquery-{version}.js"));
  bundles.Add(new StyleBundle(Links.Bundles.Styles.bootstrap).Include("~/styles/bootstrap*.css"));
  bundles.Add(new StyleBundle(Links.Bundles.Styles.common).Include(Links.Bundles.Content.Assets.Site_css));
  1. Use the following to use a bundle in a cshtml layout/view:
  @Scripts.Render(Links.bundles.scripts.jquery)
  @Styles.Render(Links.bundles.scripts.bootstrap)

For a complete example, see BundleConfig.cs in the sample app

Support for Minified Javascript files

Minified javascript files are alternate version of the files that makes them as small as possible, but using various strategies like removing white space. e.g. in an MVC application’s Scripts folder, you’ll typically see both jquery-1.3.2.js and jquery-1.3.2.min.js. Usually, you want to use the minified version at deployment time (to make your site load faster), but the regular version at development time, so you can more easily debug into it.

T4MVC makes it automatic to use one vs. the other depending on the context. e.g. suppose you have:

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

You don’t need to change anything to this line, but the token jquery_1_2_6_js will automatically point to either jquery-1.3.2.js or jquery-1.3.2.min.js depending on whether you’re running in production. How does it know whether you’re in production? It calls a method defined in T4MVC.tt.hooks.t4 which makes the decision. By default, it looks at whether debugging is enabled:

// Logic to determine if the app is running in production or dev environment
public static bool IsProduction() { 
    return (HttpContext.Current != null && !HttpContext.Current.IsDebuggingEnabled); 
}

But you can easily change this logic if you have a different way of determining ‘production’.

2.5 Using T4MVC with "Areas"

One of MVC 2's major new features is the support for breaking up a large application into "Areas". This works by following a structure that looks like:

  • Root folder

    • Areas

      *   NerdDinner
      
              *   Models
      *   Views
      *   Controllers
      
          *   Wiki
      
              *   Models
      *   Views
      *   Controllers
      

T4MVC automatically works with your areas and makes them available in its object model. Here is an example:

<%= Html.ActionLink("Delete Dinner", MVC.NerdDinner.Dinners.Delete(Model.DinnerID))%>

Notice how we refered to the controller as MVC.NerdDinner.Dinners. Note that if you happen to have a top level controller with the same name as the area, this naming pattern would cause a conflict. In that case, T4MVC appends 'Area' at the end of the area name to avoid the conflict. e.g. If you have both a Home area and a Home controller (top level), you would use MVC.HomeArea.Hello to refer to the area.

Optionally, if you set IncludeAreasToken to true in the settings.xml file, the naming scheme becomes:

<%= Html.ActionLink("Delete Dinner", MVC.Areas.NerdDinner.Dinners.Delete(Model.DinnerID))%>

Note that in this case, the conflict situation discussed above cannot occur. But this comes with a price as you end up with one more segment all the time. I'm debating whether to even keep this mode given that the default mode works quite well.

2.5.1 Alternative "Feature Folder" project structure

The "Feature Folders" convention is an alternative folder structure in which files within an area are grouped by feature (e.g. "Home", "Account") instead of file-type ("Controllers", "Views"):

  • Root folder

    • Areas

      *   Forum
      
              *   Posts
      
                      *   PostsController.cs
          *   List.cshtml
          *   Create.cshtml
      
                  *   Categories
      
                      *   CategoriesController.cs
          *   List.cshtml
      

To enable this in ASP.NET MVC, you must create a custom ViewEngine that resolves the view paths correctly. Controllers aren't bound to a "Controllers"-folder anyway, so nothing must be changed for them. The ViewEngine could be implemented as follows:

public class FeatureFolderViewEngine : RazorViewEngine
{
    public FeatureFolderViewEngine()
    {
        // {0} ActionName; {1} ControllerName; {2} AreaName

        AreaViewLocationFormats = new[]
                                    {
                                        "~/Areas/{2}/{1}/{0}.cshtml",
                                        "~/Areas/{2}/SharedViews/{0}.cshtml", // Replacement for "Views/Shared"
                                    };

        AreaMasterLocationFormats = new[]
                                    {
                                        "~/Areas/{2}/{1}/{0}.cshtml",
                                        "~/Areas/{2}/SharedViews/{0}.cshtml",
                                    };

        AreaPartialViewLocationFormats = new[]
                                            {
                                                "~/Areas/{2}/{1}/{0}.cshtml",
                                                "~/Areas/{2}/SharedViews/{0}.cshtml",
                                            };

        // see System.Web.Mvc.RazorViewEngine for default settings if you want to have Controllers/Views in the root of your project
        ViewLocationFormats = new[] { ... };
        MasterLocationFormats = new[] { ... };
        PartialViewLocationFormats = new[] { ... };

        FileExtensions = new[] { "cshtml" };
    }
}

Starting with version 3.9.0, T4MVC supports this structure with the optional settings parameter FeatureFolderAreas.

If every area in your project follows this convention, you can set it to "*":

<FeatureFolderAreas>
  <Area>*</Area>
</FeatureFolderAreas>

If only certain areas follow this convention, you can specify them with separate Area-tags:

<FeatureFolderAreas>
  <Area>Forum</Area>
  <Area>Wiki</Area>
</FeatureFolderAreas>

Since T4MVC by default creates entries for every file within a Views-folder you have to exclude ".cs"-files. Otherwise you would also get a "Views"-entry for your controller or other classes within the folder. This file extension has been added to the settings file in v3.9.0! If it's not present in your project, you can add it as follows:

<ExcludedViewExtensions>
  <Extension>.cs</Extension>
  <!-- ... -->
</ExcludedViewExtensions>

2.6 Using T4MVC with AsyncController

2.6.1 Enable Asynchronous Action Support

To enable support for using asynchronous actions in T4MVC you first need to change the SupportAsyncActions setting to true. This is setting is false by default since Async action calls are handled a little differently than normal action calls.

2.6.1 What it does

When enabled any AsyncController with an **Async action will have two new methods generated in it. The first method is the standard Parameterless Overload and the second one one with the same signature as the **Async method but with out the Async extension. In addition, this method returns the standard ActionResult regarless of the possible return types of the *Completed methods.

For example the following action in your controller

public void IndexAsync(long id) 
{
   ...
}

public JsonResult IndexCompleted(Models.SomeModel model)
{
   return this.JSON(model);
}

would cause the following code to be generated by T4MVC

public virtual ActionResult Index() 
{
   ...
}

public virtual ActionResult Index(long id)
{
   ...
}

With this additional code you can use MVC.Controller.Action() to generate links, regardless if the action is Async or not, however, you cannot mix and match with the same method names, all must be either Async or not.

3. Tweaking the behavior of T4MVC

When you download T4MVC, you not only get the main T4MVC.tt template, but you also get a file named T4MVC.tt.hooks.t4, which can conatin some extensibility hooks. In addition to that, T4MVC supports a T4MVC.tt.settings.xml. This file will be created with some defaults the first time T4MVC gets to run. This file contains various knobs that you can use to tweak the code generated by T4MVC. This section describes the various switches:

3.1 Using Model 'unbinders'

By default T4MVC basically performs a .ToString() operation when adding objects to URLs (route values). It works great for value types and strings, but usually fails at complex objects. Model unbinders let you inject your custom logic into the process of serializing model to URL parameters. Say, you have the following action:

public ActionResult Profile(User user) {}
public class User {
  public int Id { get; set; }
  public string Login { get; set; }
}

Here's the typical result of generating an URL for that action with T4MVC:

@Url.Action(MVC.Home.Profile(new User {Id = 1})); // -> /Home/Profile?user=Mvc.HomeController.User

If you'd like to get an URL like /Home/Profile?user=1 you could write a model unbinder, which should implement either IModelUnbinder or IModelUnbinder<T>:

public class UserUnbinder : IModelUnbinder<User>
{
    public void UnbindModel(RouteValueDictionary routeValueDictionary, string routeName, User user)
    {
        if (user != null)
            routeValueDictionary.Add(routeName, user.Id);
    }
}

and then inject it to T4MVC in Application_Start:

ModelUnbinderHelpers.ModelUnbinders.Add(new UserUnbinder());

After that you'll get exactly what described above:

@Url.Action(MVC.Home.Profile(new User {Id = 1})); // -> /Home/Profile?user=1

If you want T4MVC to serialize all the properties of the certain type to url parameters, built-in PropertiesUnbinder could help you do this:

ModelUnbinderHelpers.ModelUnbinders.Add(typeof(User), new PropertiesUnbinder());

In this case you'll get:

@Url.Action(MVC.Home.Profile(new User {Id = 1, Login = "test" })); // -> /Home/Profile?user.Id=1&user.Login=test

which will gently suit the default MVC model binder. T4MVC searches for unbinders through the base types and interfaces that the object implements.

3.2 XML Settings File

The T4MVC.tt.settings.t4 file have been replaced with a more "upgrade friendly" XML configuration file. This file is automatically generated/updated by the template when it is run, so the file will always reflect the current options and settings for the template. This new structure means that future upgrade to the template will not cause manual merging of your settings into a new settings, or for the template to break because a new setting was added. Your modified existing T4MVC.tt.settings.t4 will be left behind after upgrade and will not longer be used, however, the template cannot import your settings so you will have to edit the new XML file and copy them over. Once done you can safely delete the settings.t4 file.

Two methods from the old settings file were moved into a new T4MVC.tt.hooks.t4 file, AddTimestampToStaticLink and RenderAdditionalCode. Neither of these tweaks could be properly represented in an XML file since they are adjustments to the template its self. Changes to this file should be rare and documented when done, so during upgrades you can generally ignore replacement errors if you have customized its contents.

3.3 Miscellaneous tweaks

Force include/exclude some controllers

By default, T4MVC uses a heuristic to determine which controller it needs to process. This can be overridden using the [T4MVC] attribute, e.g.

To force process a controller:

[T4MVC]

To force ignore a controller:

[T4MVC(false)]

Set SplitIntoMultipleFiles to false to generate a single file with everything

<!-- If true,the template output will be split into multiple files. -->
<SplitIntoMultipleFiles>true</SplitIntoMultipleFiles>

Use HelpersPrefix to change the MVC prefix of the generated classes

<!-- The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID) -->
<HelpersPrefix>MVC</HelpersPrefix>

Use ControllersFolder and ViewsRootFolder to rename the Controllers and Views folders

<!-- The folder under the project that contains the controllers -->
<ControllersFolder>Controllers</ControllersFolder>

<!-- The folder under the project that contains the views -->
<ViewsRootFolder>Views</ViewsRootFolder>

Use LinksNamespace the generated links' namespace

<!-- The namespace that the links are generated in (e.g. "Links", as in Links.Content.nerd_jpg) -->
<LinksNamespace>Links</LinksNamespace>

Use StaticFilesFolders to tweak the list of folders from which links are generated

<!-- Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js) -->
<StaticFilesFolders>
    <FileFolder>Scripts</FileFolder>
    <FileFolder>Content</FileFolder>
</StaticFilesFolders>

Use GenerateParamsAsConstantsForActionMethods to generate constants instead of static classes for parameter names. This lets you use them in attributes.

<!--The true the parameter name class will be generated as constants (allowing use in attributes).  This is the suggested way to do this.  However, if you used T4MVC Params before and referenced them outside the controller, your references will need to change from MVC.User.MyActionParams   to MVC.UserController.MyActionParams.  -->
<GenerateParamsAsConstantsForActionMethods>True</GenerateParamsAsConstantsForActionMethods>

Customize Action values

The generated T4MVC_ControllerClass classes are now partial classes so you can extend and customize the values returned from MVC.Controller.Action() calls. In this class, for each overridden method a partial method call is available, if you implement that partial you have the ability to modify the the action values before they are returned.

partial T4MVC_SomeController
{
    partial void SomeAction(T4MVC_System_Web_Mvc_ActionResult callInfo)
    {
        callInfo.RouteValues["other"] = 1;
    }
}

Also, the parameterless methods are not virtual so they can be overridden in the partial as well.

partial T4MVC_SomeController
{
    public override ActionResult SomeAction()
    {
        var callInfo = base.SomeAction();
        callInfo.GetRouteValueDictionary()["other"] = 1;
        return callInfo;
    }
}

4. Related resources

David Ebbo has written a series of blog posts on T4MVC Scott Hanselman also blogged about it here

⚠️ **GitHub.com Fallback** ⚠️