The Weekly Source Code 43 - ASP.NET MVC and T4 and NerdDinner
UPDATE: David's put the T4 template with some nice updates on CodePlex - It's the last download here.
I really advocate folks reading as much source as they can because you become a better writer by reading as much as writing. That's the whole point of the Weekly Source Code - reading code to be a better developer.
Reading code in Open Source projects is a good way to learn, especially if the project has been around a while and been successful, or if you already respect the team of people working on it. Less reliably, you can find snippets of code by searching and sharing code.
David Ebbo is scary clever. You know someone is smart when they come up with something you don't think you yourself could come up with on your own, but you still kick yourself for not thinking of it in the first place.
David's been experimenting with ways to make ASP.NET MVC better, specifically in the area of strongly-typed helpers. He's trying to get rid of strings, magic or otherwise.
He started with a BuildProvider (not a lot of folks know about BuildProviders...it's a powerful secret.) David didn't like these kinds of links in ASP.NET MVC:
<%= Html.ActionLink("Home", "Index", "Home")%>
So his BuildProvider would get added to the web.config:
<buildProviders>
<add extension=".actions" type="MvcActionLinkHelper.MvcActionLinkBuildProvider" />
</buildProviders>
And it would give you better methods like this, by poking around in your project and dynamically generating helper methods for you:
<%= Html.ActionLinkToHomeIndex("Home")%>
Then, just days later, David got the T4 religion (I've argued we are doing a poor job of promoting this, so I'm telling everyone I know) and you should too. David explored the pros and cons of CodeDom vs. T4 for CodeGen then recreated his ASP.NET MVC Helpers using T4.
He's enabling not only better ActionLinks, but also Action Url Helpers, Constants for View Names, and a very clever thing, helpers for View Models, courtesy of David Fowler.
I thought this was particular cool, because it's a really visceral example of what you can do with Code Generation when you start exploring what you know at compile time with what you wish you knew at Design Time. It also reminds us that it's more than just Compile/Test/Run - your projects become more "meta" when you've got Inspect/CodeGen/Compile/Test/Run.
For example, if you have a ViewModel that say, has a string, rather than:
<label for="Name">Name:</label>
<%= Html.TextBox("Name", Model.Name) %>
<%= Html.ValidationMessage("Name", "*") %>
Why not this instead:
<%= ViewModel.Name.Label() %>
<%= ViewModel.Name.TextBox() %>
<%= ViewModel.Name.Validation() %>
This ViewModel stuff is in the earlier CodeDom templates, but not (yet?) the T4 templates. I hope we see it again, don't you? What do you think, Dear Reader, of this approach vs. the "Opinionated Input Builder" approach?
Just recently David put out his "new and improved" MVC T4 template, which is the one you should check out. I'm gently pushing him to put it on CodePlex somewhere and bake this goodness in. (Go tell him and Phil!)
He was doing some stuff at runtime, but has since moved to a pure design-time approach.
David Ebbo's ASP.NET MVC T4 Template applied to Nerd Dinner
David took the NerdDinner app as his base, and installed his T4 template. Just drop it in, and magic gets generated. How does it work, though?
Before he was using reflection but since this is a Design Time Code Generation process he had to delve into the *gasp* COM-based VS File Code Model API. However, this does really show off the power and flexibility of T4 itself. Kudos to David for looking COM in the face and not blinking!
Go download his template (direct ZIP here) and open it up.
T4 files can be scary to view in a text editor as they are constantly switching between <% code blocks %> and the code that's being generated. I use Notepad2 to view them, using the (interestingly enough) XML Document option that gives me rudimentary syntax highlighting.
If you're serious about T4, go get the Visual T4 Community Edition from Clarius for half-way syntax highlighting, or pay for the Professional Edition. They'll register themselves with Visual Studio and give you a better experience than just all-black text.
The first line in David's T4 template that made me chuckle was the the first line that's executed:
<# PrepareDataToRender(this); #>
As they say, it's funny because it's true. This is typical of code generation templates of all kinds. It's the "DoIt()" method that takes the left-hand's code (the COM crap) and prepares it for the right-hand (the template itself).
In order for David to make his template tidy and enable this nice clean level of abstraction:
<# foreach (var controller in Controllers.Where(c=>c.IsPartialClass)) { #>
namespace <#= controller.Namespace #> {
public partial class <#= controller.ClassName #> {
protected RedirectToRouteResult RedirectToAction(ControllerActionCallInfo callInfo) {
return RedirectToAction(callInfo.Action, callInfo.Controller, callInfo.RouteValues);
}
...snip...
...he has to prepare an object model, and that's what PrepareDataToRender does. It's funny also because it's buried at the end, as it should be. It's really a pleasure to read and does a LOT considering it's only a 415 line template.
He has a number of cute gems like this one-line helper function that combines a little COM a little LINQ and a little magic to a list of methods to his template using the Visual Studio API. I laughed at the "functionFunction" enum. "Do you like her? or do you like her like her?"
// Return all the CodeFunction2 in the CodeElements collection
public static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass) {
// Only look at regular method (e.g. ignore things like contructors)
return codeClass.Members.OfType<CodeFunction2>()
.Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
}
David is clearly deep into this exploration right now, and is asking your opinion about the design. I would encourage you to flood him with feedback, or leave it hear and I'll tell him. ;)
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
So the reason we went opinionated vs. specifying every. single. label. and. validation is that we always had the same pattern on our inputs - label, then input, then validation message. On a site with dozens and dozens of forms, specifying each leads to a lot of duplication. We didn't want to decide that all date time inputs had a specific format, or that all enumerations were in a radio button list, or that every nullable enumeration was in a drop down...well, you get the picture. To date, the project that inspired the opinionated input builders has two dozen distinct input builders, from which we can make global changes to the entire website, if our customers decide that "hey, can we add a mask to every date?". There are certain attributes we decide on the view, which come in the form of modifiers off of our builders.
With strong standards/opinions, we've also taken away some freedom from product owners in designing user experience. But what we found is that our customers would rather focus on the business domain than remembering (or even trying to form) standards for user input. Need a required field? Here's our standard, rendered by putting a single Castle Validator attribute on our View Model.
Not for every project of course, but good when you want a lot of forms to look and act the same.
I did some project where all was abstract like did it Dave. It works 5 years (too long for single task) but indeed if somebody asked for me improve code and add new modules i will feel myself not convenient - because long chain will between interface, name politic and code.
I hope Dave knows it without me.
Sincerely, LukCAD
All the mvc framework work great with eglish but are really hard to use with others. And I would not even go that far as to offer a MVC page as a multilanguage page.
I hope that the ASP.NET MVC framework will not take this direction.
from a "repository" class in another project in order to avoid crazy code in the view and avoid adding UI aware
code in the domain class. It seems to work well. Here's an example.
public static IEnumerable<SelectListItem> StateItemsList(
this Address address)
{
return ViewUtils.GetSelectListItems(address.StateItems, address.State);
}
public static IEnumerable<SelectListItem> GetSelectListItems(
IEnumerable<String> items,
String selected)
{
return
from p in items
select new SelectListItem
{
Text = p,
Value = p,
Selected = (p == selected)
};
}
public static string DoDropDownList(
this HtmlHelper htmlHelper,
ViewUtils.ViewMode mode,
String name,
String value,
IEnumerable<SelectListItem> selectList,
Object htmlAttributes)
{
if (ViewUtils.IsEditMode(mode))
{
StringBuilder builder = new StringBuilder();
builder.Append(SelectExtensions.DropDownList(htmlHelper, name, selectList));
builder.Append(ValidationExtensions.ValidationMessage(htmlHelper, name));
return builder.ToString();
}
else
{
return htmlHelper.Encode(value);
}
}
Then the strongly typed view just uses
<%= Html.DoDropDownList(ViewData.DisplayMode(),
ViewData.Prefix() + "Address.State",
Model.State,
Model.StateItemsList(),
new { minlength = "2", maxlength = "2" }) %>
Things that I think could be improved:
* Integrating template execution into the build system by default, and the ability to control when the template is executed. For our scenarios we need generation to occur before the build, so have edited the csproj file to call a custom TextTransform MSBuild task (avoiding a dependency on the command-line .exe, shelling out to it for each template is *slow*).
* Smart template execution: Always execute template on "Rebuild", for a standard "Build", only execute if template is newer than output file.
* Nice to have: Less hacky way of generating multiple outputs from a single template file, and representing this in the UI (e.g. expanding the template shows the complete list of generated files).
* Nice to have: Some way to indicate that a template depends on a different project. E.g. we have some T4 templates that generate code based on types in a separate assembly, based on the results of analyzing this assembly using the FxCop introspection framework. At the moment, we have to regenerate the templates for each build, as we can't really whether the assembly we're dependant on has changed or not since the last time templates were generated.
Any suggestions on where to send this feedback so the appropriate eyeballs see it?
Comments are closed.
Do T4 templates run automatically or do you have to save the file to get them to generate?