惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 热门话题
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
Microsoft Security Blog
Microsoft Security Blog
月光博客
月光博客
Jina AI
Jina AI
博客园_首页
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
腾讯CDC
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
G
Google Developers Blog
D
Docker
罗磊的独立博客
美团技术团队
爱范儿
爱范儿
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
J
Java Code Geeks
U
Unit 42
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
T
Troy Hunt's Blog
H
Heimdal Security Blog
The Cloudflare Blog
Webroot Blog
Webroot Blog
aimingoo的专栏
aimingoo的专栏
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Full Disclosure
AI
AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Last Week in AI
Last Week in AI
T
The Exploit Database - CXSecurity.com
V
V2EX
Spread Privacy
Spread Privacy
M
MIT News - Artificial intelligence
量子位
Y
Y Combinator Blog
阮一峰的网络日志
阮一峰的网络日志
NISL@THU
NISL@THU
Hacker News: Ask HN
Hacker News: Ask HN
The GitHub Blog
The GitHub Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News and Events Feed by Topic
Attack and Defense Labs
Attack and Defense Labs

博客园 - WEBBER

SQL语句中使用变量作为条件。遇到NULL时怎么写 checkbox单选 ASIHTTPRequest 详解, http 请求终结者 (转) ASP.NET版Memcached监控工具(转) 完整的DataTable和list转换 老外写的(转) 技巧:使用User Control做HTML生成(转) Silverlight跟ASP.NET之間要如何溝通querystring(转) asp.net取得Silverlight中的值(转) IIS6 Silverlight部署經驗(转) c#四种eval方法(转) - WEBBER - 博客园 一行内文本超出指定宽度溢出的处理 sql2005数据同步技术(数据订阅,发布)转 C# 域用户操作(转) 看到了一些面试题,发给大家,看看多少人能都全回答上来 Membership角色与权限管理 (转) 我的跳槽求职经验谈(转) IHttpHandler的妙用(2):防盗链!我的资源只有我的用户才能下载 (转) JavaScript组件之JQuery(A~Z)教程(基于Asp.net运行环境)(转) 说说大型高并发高负载网站的系统架构 (转)
T4MVC Documentation page(转)
WEBBER · 2012-02-27 · via 博客园 - WEBBER

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 both Visual Studio 2008 SP1 and Visual Studio 2010, and supports both ASP.NET MVC 1.0 and 2.0.

To use it, simply download the zip file, and copy both T4MVC.tt and T4MVC.tt.settings.t4 to the root of your MVC application. You should instantly see a number of files get created in Visual Studio under T4MVC.tt.

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.

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.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 paramter 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:

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

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.

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

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("Delete", "Dinners", new { id = Model.DinnerID })%>

becomeshttp://mvccontrib.codeplex.com/wikipage?action=Edit&title=T4MVC_doc&version=2&referringTitle=T4MVC

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

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. 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>

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 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.settings.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.4 Using T4MVC with MVC 2 "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 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.

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.settings.t4. This file contains various knobs that you can use to tweak the code generated by T4MVC. This section describes the various switches:

Set AlwaysKeepTemplateDirty to false to turn on the always-dirty behavior

// If true, the template marks itself as unsaved as part of its execution.
// This way it will be saved and update itself next time the project is built.
// Basically, it keeps marking itself as unsaved to make the next build work.
// Note: this is certainly hacky, but is the best I could come up with so far.
bool AlwaysKeepTemplateDirty = true;

Set SplitIntoMultipleFiles to false to generate a single file with everything

// If true,the template output will be split into multiple files.
bool SplitIntoMultipleFiles = true;

Use HelpersPrefix to change the MVC prefix of the genrated classes

// The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID)
const string HelpersPrefix = "MVC";

Use ControllersFolder and ViewsRootFolder to rename the Controllers and Views folders

// The folder under the project that contains the controllers
const string ControllersFolder = "Controllers";

// The folder under the project that contains the views
const string ViewsRootFolder = "Views";

Use LinksNamespace the generated links' namespace

// The namespace that the links are generated in (e.g. "Links", as in Links.Content.nerd_jpg)
const string LinksNamespace = "Links";

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)
readonly string[] StaticFilesFolders = new string[] {
    "Scripts",
    "Content",
};

4. Related resources

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