An Alternative Approach To Strongly Typed Helpers

One of the features contained in the MVC Futures project is the ability to generate action links in a strongly typed fashion using expressions. For example:

<%= Html.ActionLink<HomeController>(c => c.Index()) %>

Will generate a link to to the Index action of the HomeController.

It’s a pretty slick approach, but it is not without its drawbacks. First, the syntax is not one you’d want to take as your prom date. I guess you can get used to it, but a lot of people who see it for the first time kind of recoil at it.

The other problem with this approach is performance as seen in this slide deck I learned about from Brad Wilson. One of the pain points the authors of the deck found was that the compilation of the expressions was very slow.

I had thought that we might be able to mitigate these performance issues via some sort of caching of the compiled expressions, but that might not work very well. Consider the following case:

<% for(int i = 0; i < 20; i++) { %>

  <%= Html.ActionLink<HomeController>(c => c.Foo(i)) %>

<% } %>

Each time through that loop, the expression is the same: c => c.Foo(i)

But the value of the captured “i” is different each time. If we try to cache the compiled expression, what happens?

So I started thinking about an alternative approach using code generation against the controllers and circulated an email internally. One approach was to code gen action specific action link methods. Thus the about link for the home controller (assuming we add an id parameter for demonstration purposes) would be:

<%= HomeAboutLink(123) %>

Brad had mentioned many times that while he likes expressions, he’s no fan of using them for links and he tends to write specific action link methods just like the above. So what if we could generate them for you so you didn’t have to write them by hand?

A couple hours after starting the email thread, David Ebbo had an implementation of this ready to show off. He probably had it done earlier for all I know, I was stuck in meetings. Talk about the best kind of declarative programming. I declared what I wanted roughly with hand waving, and a little while later, the code just appears! ;)

David’s approach uses a BuildProvider to reflect over the Controllers and Actions in the solution and generate custom action link methods for each one. There’s plenty of room for improvement, such as ensuring that it honors the ActionNameAttribute and generating overloads, but it’s a neat proof of concept.

One disadvantage of this approach compared to the expression based helpers is that there’s no refactoring support. However, if you rename an action method, you will get a compilation error rather than a runtime error, which is better than what you get without either. One advantage of this approach is that it performs fast and doesn’t rely on the funky expression syntax.

These are some interesting tradeoffs we’ll be looking closely at for the next version of ASP.NET MVC.

What others have said

Requesting Gravatar... Scott Jun 02, 2009 9:52 AM
# re: An Alternative Approach To Strongly Typed Helpers
YAY!

Death to clever code via Expressions.
Requesting Gravatar... Josh Bush Jun 02, 2009 9:57 AM
# re: An Alternative Approach To Strongly Typed Helpers
That is interesting. You might could do it with static methods on the controller. It could look something like "<%=HomeController.IndexLink(123) %>". It's still a little more verbose than what you have above though.

I'm working on a project and I went heavily with strong typing. I really enjoy the fact that when I make changes it breaks the compilation.
Requesting Gravatar... Scott Allen Jun 02, 2009 10:18 AM
# re: An Alternative Approach To Strongly Typed Helpers
I think I'd prefer the controller name as a distinct property [.Home.About(123)]. Feels more discoverable and can be easier to auto-complete.
Requesting Gravatar... Haacked Jun 02, 2009 10:34 AM
# re: An Alternative Approach To Strongly Typed Helpers
@Josh the problem there is you'd have to write your HomeController as a partial class. We don't want to require that.

@Scott Unfortunately, we can't generate extension properties. If we generate those as extension methods of HtmlHelper, it would look like:

Html.Home().About(123)

Or maybe we could add a new top level property to ViewPage.

Link.Home().About(123)

Or we could generate something more clever like enums

Html.HomeLink(Action.Index, 123)

Phil
Requesting Gravatar... eyston Jun 02, 2009 10:40 AM
# re: An Alternative Approach To Strongly Typed Helpers
I kind of like the futures way really, although I have to admit I wasn't comfortable with them when first starting MVC (which is an audience you care about).
Requesting Gravatar... Steve Willcock Jun 02, 2009 11:29 AM
# re: An Alternative Approach To Strongly Typed Helpers
Having used the mvc futures lambda syntax for a while it seems really natural and the strong typing and refactoring support is a big plus for this way of doing things. If there was a *simple* way to cache parameterised expressions to avoid the recompilation cost on every call I feel that would be a great approach. The build provider idea is undoubtedly a very clever solution to the problem but somehow it seems rather 'clunky'.
Requesting Gravatar... Rob Conery Jun 02, 2009 11:29 AM
# re: An Alternative Approach To Strongly Typed Helpers
Good stuff! Will this work in partial trust? SubSonic's BuildProvider stuff doesn't and it uses almost the exact same thing :).

I do like this approach a LOT.
Requesting Gravatar... Elad Ossadon Jun 02, 2009 11:48 AM
# re: An Alternative Approach To Strongly Typed Helpers
I couldn't see this overload (with generic controller type and an expression) on Url, but only on Html. Will it be available?
Sometimes I need the URL itself without ... (e.g. for JS).
Of course I can work around and make a method that takes the output string and extracts the URL but this should be more elegant.

Thanks
Requesting Gravatar... Paco Jun 02, 2009 12:05 PM
# re: An Alternative Approach To Strongly Typed Helpers
"First, the syntax is not one you’d want to take as your prom date"

I would definitely prefer a lamda over a a bunch of anonymous objects/strings. We are not programming in a dynamic language and emulating it with anonymous objects will never be as good as a real dynamic language. Lamda's are a very important and well-used feature of C# since 2008, so every developer have to become familiar with it.
We use workarounds for every single string/anonymous object used in mvc since we started with mvc more than a year ago.
We things like "return this.View<TViewType>(model)" instead of return this.View("magicstring", model). That saves us the effort writing a test per view by making the compiler doing the job.

You might want to look at the html helpers like in mvccontrib.
The performance of the expression based html helpers is a major disadvantage, but hardcoding the most frequently used will make it working on larger views too.
Requesting Gravatar... configurator Jun 02, 2009 12:45 PM
# re: An Alternative Approach To Strongly Typed Helpers
Phil, how about generating a class for the links?
For instance, if you have a controller in Controllers.HomeController, you could generate a class in the same namespace Controllers.HomeLinks or Controllers.HomeControllerLinks, which would be used like this:
HomeLinks.About(122)
Requesting Gravatar... Andrew Davey Jun 02, 2009 12:51 PM
# re: An Alternative Approach To Strongly Typed Helpers
What about web application projects? I thought build providers only work for web sites.

IMO a view should not be responsible for creating URLs anyway!
The controller should be deciding where user can go next (i.e. URLs) and passing the links to the view.

Check out: http://www.assembla.com/wiki/show/snooze
for a REST/URL centric approach to asp.net mvc.
Requesting Gravatar... Haacked Jun 02, 2009 1:33 PM
# re: An Alternative Approach To Strongly Typed Helpers
@Rob Build providers will work in Medium trust in ASP 4 AFAIK (but not in 3.5 SP1). I'm sure we can find a way to do this that does work in medium. This was just a proof-of-concept after all.

@Andrew BuildProviders work in Web Applications too. The proof-of-concept David built used a WAP.

Requesting Gravatar... Zihotki Jun 02, 2009 3:21 PM
# re: An Alternative Approach To Strongly Typed Helpers
This approach will help to increase performance. But what about refactoring? Due to the reason that these "magic action links" will be generated at build time we'll need to:
1. rename method of action link
2. run the site - "When the first request is made at runtime, ASP.NET tries to build the App_Code assembly"
3. stop debugging
4. fix all errors
5. run again to verify all

In my opinion this is very terrible. In my opinion it's better to use magic strings in this case and use a Resharper. When we trying to rename a method R# looks over the code for usages of this method and also it looks for strings equals to the name of the method and it ask to rename them too.
And what about routes? Is the generator is smart enough to generate links based on routes? And what about unit tests?

For me it's too much black magic at this moment with these generators. I prefer to store all these "magic strings" in some kind of Controllers and Views Managers and manage them manually.
Requesting Gravatar... Mike Jun 02, 2009 3:25 PM
# re: An Alternative Approach To Strongly Typed Helpers
Seems like a lot of complexity just to generate a string. How about...

public class AccountController : Controller
{
public const string NAME = "Account";
public const string LOGIN_ACTION = "Login";
public const string LOGOUT_ACTION = "Logout";

...
}

Then in your view,

Html.ActionLink(AccountController.LOGIN_ACTION, AccountController.NAME);

This approach is performant, type-safe, and easy to understand.
Requesting Gravatar... Zihotki Jun 02, 2009 3:27 PM
# re: An Alternative Approach To Strongly Typed Helpers
PS. sorry for my pidgin English, it's a bit late here and I'm tired so I'm not able to catch all mistakes but I did my best.
Requesting Gravatar... Scott Allen Jun 02, 2009 5:51 PM
# re: An Alternative Approach To Strongly Typed Helpers
@Phil - I understand there are no extension properties, but since we already have a few helpers (Ajax, Html, Url, etc), it might be nice to have the provider generate a completely new class. RouteLink, perhaps. RouteLink.Home.About(123).

Either way, I like it better than the ActionLink extension.
Requesting Gravatar... Haacked Jun 02, 2009 6:31 PM
# re: An Alternative Approach To Strongly Typed Helpers
@Mike You've only addressed the action name. What about action methods that have parameters? Wouldn't you like a type safe mechanism for specifying those paramters?
Requesting Gravatar... Peter Obiefuna Jun 02, 2009 7:34 PM
# re: An Alternative Approach To Strongly Typed Helpers
@mike: Your suggestion may be simple but is actually just as good as embedding magic strings in views. There's no compile-time confirmation that those controller and action names are correct (renaming will not break compiles). And as Phil already noted, you still have to deal with args.
Requesting Gravatar... Vijay Santhanam Jun 02, 2009 8:55 PM
# re: An Alternative Approach To Strongly Typed Helpers
I like where this is going!

But how do you handle optional parameters?

For example, I often have

public ActionResult List(Paging paging) {return View(model);}

where Paging class is custom bound. It's entirely optional and the model binder knows about default values.
Requesting Gravatar... Lanwin Jun 03, 2009 2:38 AM
# re: An Alternative Approach To Strongly Typed Helpers
I like Link.Home().About(123)! The Enum version is to much verbose.
Requesting Gravatar... Nagarajan Jun 03, 2009 3:51 AM
# re: An Alternative Approach To Strongly Typed Helpers
Why can't use T4 for this?
Requesting Gravatar... Simone Jun 03, 2009 6:19 AM
# re: An Alternative Approach To Strongly Typed Helpers
Using buildprovider will make the method "HomeAboutLink(123)" appear for real only at runtime. Refactoring tools will warn me of calls to non existing methods. What if I change the name of an Action? There will be no refactoring tool to help me change the ActionLink method name in the view.
Or am I missing something obvious?
Requesting Gravatar... nic Jun 03, 2009 7:49 AM
# re: An Alternative Approach To Strongly Typed Helpers
We fought with those ActionLinks too, but since I wanted to keep the advantages of the generic ActionLink (like compile-time type checking, refactoring support) we created an overload that takes exactly one parameter.
<%= Html.ActionLink((ProductController c) => c.Show, product, product.FullName) %>
The ActionLink's signature is
public string ActionLink<K, T>(Expression<Func<T, Func<K, ActionResult>>> action, K parameter, string linkText) where T : Controller
Of course, this works only for ActionLinks that take one parameter, but since most of our ActionLinks do so, we have good performance with this. In cases where the ActionLink takes multiple parameters, we use the old school MvcFutures ActionLink
<%= Html.ActionLink<HomeController>(c => c.Index()) %>
Requesting Gravatar... Elad Ossadon Jun 03, 2009 2:06 PM
# re: An Alternative Approach To Strongly Typed Helpers
OK, answering myself: That method I looked for is Html.BuildUrlFromExpression<T>()
I would have expected it to be in UrlHelper and not in HtmlHelper, but at least it exists.
Requesting Gravatar... Eric hexter Jun 04, 2009 11:25 AM
# re: An Alternative Approach To Strongly Typed Helpers
I pulled down the source code and implemented the UrlHelper version of this. It is an interesting approach, although I kind of struggle with why have a runtime generation versus a build time code gen.

What is interesting.. is Resharp totally does not pick up the new methods created by the generation.. but Visual Studio understands the extensions and provides full intellisense with this approach.

I like the idea of using this for Url generation particularly for my Form markup. Since I just need the url with out the parameters for forms. since all of my parameters typically come from my form input elements..... love to see more on this and perhaps a version that does this with a postBuild step similar to the aspCompile...

keep up the experiements in alternative approaches, I love to see the little prototypes in areas of the framework that are not well documented.
Requesting Gravatar... Andy Kutruff Jun 05, 2009 6:46 AM
# re: An Alternative Approach To Strongly Typed Helpers
I added expression caching to CLINQ that supports both closure expressions (expressions that capture values.) It yields a 100X increase for open expressions, and an 8X increase for closures. The code is well isolated from the rest of the framework, so you should be able to easily lift it out.

For example, if you have

Expression&lt;Func&lt;int, int&gt;&gt; myExpression = arg =&gt; arg + 1;
myExpression.Compile()

You just need to change it to:

Expression&lt;Func&lt;int, int&gt;&gt; myExpression = arg =&gt; arg + 1;
myExpression.CachedCompile()

CLINQ 2.1

It's in the ContinuousLinq.Expressions namespace / solution folder. Enjoy.
Requesting Gravatar... Haacked Jun 05, 2009 8:11 AM
# re: An Alternative Approach To Strongly Typed Helpers
@Simone it works with Visual Studio Intellisense. Visual Studio knows to generate it. You can have compile time by simply pre-compiling your site.
Requesting Gravatar... Simone Jun 06, 2009 3:45 AM
# re: An Alternative Approach To Strongly Typed Helpers
Thanks... is it accomplished by the pageparser filter?
Requesting Gravatar... Andrew Davey Jun 06, 2009 5:14 AM
# re: An Alternative Approach To Strongly Typed Helpers
I'm using a web application project.
How can the build provider help if I want to generate Urls in my action method (not in the views)? For example, redirecting to another action...
Requesting Gravatar... Andrew Davey Jun 06, 2009 5:23 AM
# re: An Alternative Approach To Strongly Typed Helpers
Maybe creating a custom VS tool (by implementating IVsSingleFileGenerator) would be a more robust approach.
Requesting Gravatar... sulumits retsambew Jun 12, 2009 2:13 PM
# re: An Alternative Approach To Strongly Typed Helpers
nice tutorial for newbie like me,, good article and keep posting thank you
Requesting Gravatar... Jamie Cansdale Jun 15, 2009 1:19 PM
# re: An Alternative Approach To Strongly Typed Helpers
Another solution would be to constrain the complexity of expressions that are allowed as parameters. Simple expressions can be evaluated quickly using a little reflection.

For example, the following expression evaluator would support constants and for loops (your example):

static object GetValue(Expression expression)
{
ConstantExpression constant = expression as ConstantExpression;
if (constant != null)
{
return constant.Value;
}

MemberExpression member = expression as MemberExpression;
if (member != null)
{
FieldInfo field = member.Member as FieldInfo;
ConstantExpression c = member.Expression as ConstantExpression;
if (field != null && c != null)
{
return field.GetValue(c.Value);
}
}

throw new ArgumentException("ComplexExpressionsNotSupported", "expression");
}

With a little more work I suspect you could nail the common cases. It is really necessary to support all complex expressions as ActionLink parameters? I think not supporting them would be a reasonable compromise. :)

What do you have to say?

(will show your gravatar)
Please add 1 and 3 and type the answer here: