Disclaimer: This article and the ideas presented are based on 7-month-old thoughts that have been undeveloped. While I expect the ideals disclosed are still valid, they might be totally off-base. I wanted to finish writing this series and share my thoughts, but I've been unable to keep up with the ASP.NET MVC framework. Please check here for more information.
Another Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way. I am not involved in the ASP.NET MVC project.
This article discusses how I think the ASP.NET MVC framework could be changed to offer better extensibility and customization. With that said, I think ASP.NET MVC is one of the most extensible frameworks offered by Microsoft. Its extensibility has been praised, unlike most other stuff that comes out of Microsoft. But I think one aspect of the framework has been unintentionally locked down from extensibility and customization--the actual HTML rendering of "controls."
When I talk about "controls" in this context, I'm not talking about WebForms controls or 3rd party controls like the Infragistics stuff, rather I'm talking about primitive HTML tags like <input> and <select>. Many of these tags/controls are emitted through the HTML Helpers library, a set of extension methods such as Html.TextBox(). This is where customization and extensibility are taken away. I discussed this in part 1 of this series. I think a different approach should have been taken to roll up common controls into shortcuts.
What I Would Have Done
Part 2 of this series showed how I would implement Custom Controls Everywhere in ASP.NET MVC; essentially, I think that approach should have been promoted in ASP.NET MVC. Instead of creating an exhaustive library of extension methods that are overloaded like mad, accepting 'object' parameters for anonymous types, fully typed classes should have been created for the different controls. Instead of having overloaded Html.TextBox() methods, there should be a TextBox class with properties to replace the parameters of the method. Then, as I demonstrated, the ToString() method on the TextBox class can be responsible for translating the class with its properties into an HTML tag.
My approach also resulted in a base class for MvcControl that handled the raw HTML tag and attributes, allowing a TextBox class to simply define its HTML tag, its properties and how they map to HTML attributes. This is extremely extensible, because I can create new controls very quickly and easily while still using the shortcut to simply the actual rendering. With the extension method approach of the HTML Helpers, every extension method is responsible for rendering the HTML, and I suspect there's a lot of duplication going on across the methods and the overloads.
With the TextBox class approach, intellisense is superior to that of the extension methods. Instead of named parameters into overloads, you can use object initializers and see all possible properties available for the control. This was illustrated in Part 2 with the following screen shot.
Why (I Think) My Approach is More Extensible and Customizable
Extensibility and customization is improved with my approach because I can easily add additional behavior to the TextBox by simply deriving from it and adding new properties and optionally overriding ToString(). This still gives you 100% control over how the HTML is rendered, unlike ASP.NET WebForms custom controls, but it offers the rich customizations of WebForms unlike MVC's HTML Helpers. See, with the HTML Helpers, if I need to slightly tweak the output of an extension method, I cannot derive from it and do so, I must create my own extension methods. Considering the number of overloads involved in this, in can get completely out of control.
Composition Over Inheritance
Having a custom MvcControl class would probably be frowned upon by many. I was actually just talking with Glenn Block yesterday and he talked about how pattern folks often prefer composition over inheritance. With that in mind, the custom controls approach can be tweaked slightly to work in a composition mode. Instead of having a base class, we could easily have a service class that would accept an object, a map of property name to attribute name, and a tag name, and return an HTML tag string. This service would be called from within the TextBox ToString() method. This approach would require a little more code in each control, but you would be free to have your own inheritance hierarchy without having to keep MvcControl at the root. I think the best approach would be to combine the inheritance and composition approaches, offering both by having a static method on MvcControl such as ConvertObjectToTag(object, tagName, propertyAttributeMap). The ToString() method on MvcControl would use this method itself as well, ensuring consistent results regardless of the approach used.
What ASP.NET MVC Could Change
To make my suggestions more concrete, here's a summary of what I would have done differently. Instead of creating a huge library of extension methods, I would have created classes for the different controls. This would have been close but not equal to a 1:1 ratio of control to HTML Tag; there'd be slightly more "controls" than HTML tags, since there are cases where 2 or more logical controls map to the same HTML tag. ListBox and DropDownList are a simple example, where both map to a <select> tag. Where the extension methods have been overloaded, the classes would have simply had properties added to them, with some occasional conditional logic in the overridden ToString() method.
But ya know what else I would have done? I would have made the VS templates include a folder chock full of classes, one for each control. These classes would have derived from the provided control classes, stubbing out the place for you to add your own extensions and customizations to the controls. Even though it's cheap to create these on your own, it's not encouraged enough. By stubbing the custom controls out for you, the entrance barrier is torn down and you are shown exactly how and where to manipulate the HTML output of these controls. The templates would be set up so that your controls would be used in your views rather than those that ship with the framework. And to really drive the point home, the framework controls could be marked as abstract, forcing you to use your custom controls everywhere, guiding you into the pit of success.
For what it's worth, there's nothing in ASP.NET MVC that prevents this approach from being used; nothing at all. But what's frustrating is that in order to use this, you have to completely ignore all of the hard work that was put into the HTML Helpers library. There's a ton of sugar in there that you won't get to benefit from. If the methods had been build with my approach, you could completely customize your controls where needed, but utilize the default behaviors where that's all you need.
I do wish I had had time to explore these ideas earlier this year, with the opportunity to perhaps build out some of the classes and templates I'm suggesting. But such is life. Maybe someone else that supports my ideas here can run with it and add it to the MvcContrib project or something. Regardless, I am glad to have finally documented my thoughts on this and I hope that some of you find this valuable.