Recently I've had the opportunity to use the latest MVC drop to create a web site that is more than just a play thing. Along my travels I found an annoying problem with using the Html.Form helper. I wanted to call this action:
If I wanted to let the framework automatically map values from my form to the action's parameters I was unable to strongly type the action URL using the Html.Form helper. This forced me to use code such as:
Now this meant that I was heavily dependant on strings to tie everything together properly. The controller, action and form value mapping was dependent on it. This felt a bit nasty, but it worked.
The reason I could not use the existing strongly type lambdas was that the action required arguments and in order to use the existing lambdas you have to supply arguments for all the parameters. This is possible with for the Verify action by supplying dummy values:
This would work for my simple example. However, it relies on the userName and password arguments not being mapped in your routing rules. If you wanted a value such as id to be selected from a drop down using this method would hard code in the id which isn't what you want.
I thought there must be some way round this using a combination of generics, reflection and LINQ so I used the existing generic method as a starting point and got an understanding of how it used Expressions and Funcs to tie things together. From this I figured there must be a way to create an expression pointing at my action which I'd then be able to interrogate to grab the values I wanted.
At this point I must thank Daniel Cazzulino and Ayende Rahien for their fantastic posts on getting methods from expressions and static reflection respectively. These helped me work out the code which did the hard stuff without having to know absolutely everything about Expressions and Funcs.
Through the use of an Expression we can create a form helper which looks like this:
Though quite verbose, this has allowed us to strongly type the action to be called and the type of its parameters (this will generate a URL for the form of /Home/Verify). I believe this type safety is a great improvement on the bunch of strings we started off with.
Here's how the magic happens:
It works by taking the Expression that is passed to it to get a MethodInfo object which has the details of our Action. From the MethodInfo object we can grab the name of the action and the name of its controller, passing it in to the existing Html.Form helper method. Through a generic constraint we can ensure that this method can only be used for an IController and that the method called must return an ActionResult object.
From this single method it is simply a case of writing a bunch of overrides so that you can have all the control over the form available in the existing helpers (i.e. setting the form method and passing in a dictionary of values). There is a limit of 3 on the number of parameters that this can currently handle due to the limit on arguments that a Func can take. There may be a way to work around this but it is beyond my needs so I have not looked into it.
I couldn't find a decent way to host the source so drop me a line if you want it, or even better let me know where I could host it for free without people having to suffer pop ups or general eye spam.