Most simple forms we write, especially in LOB applications, are repetitive sections of inputs and/or displays. Take a look at an exmaple from the MVC Music Store sample, the album editor template, notice anything repetitive?
<%@ Import Namespace="MvcMusicStore"%> <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcMusicStore.Models.Album>" %> <script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script> <script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script> <script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script> <p> <%: Html.LabelFor(model => model.Title)%> <%: Html.TextBoxFor(model => model.Title)%> <%: Html.ValidationMessageFor(model => model.Title)%> </p> <p> <%: Html.LabelFor(model => model.Price)%> <%: Html.TextBoxFor(model => model.Price)%> <%: Html.ValidationMessageFor(model => model.Price)%> </p> <p> <%: Html.LabelFor(model => model.AlbumArtUrl)%> <%: Html.TextBoxFor(model => model.AlbumArtUrl)%> <%: Html.ValidationMessageFor(model => model.AlbumArtUrl)%> </p> <p> <%: Html.LabelFor(model => model.Artist)%> <%: Html.DropDownList("ArtistId", new SelectList(ViewData["Artists"] as IEnumerable, "ArtistId", "Name", Model.ArtistId))%> </p> <p> <%: Html.LabelFor(model => model.Genre)%> <%: Html.DropDownList("GenreId", new SelectList(ViewData["Genres"] as IEnumerable, "GenreId", "Name", Model.GenreId))%> </p>
It didn’t take me long to notice that the duplication was driving me absolutely crazy! Yet, the community is hailing these template helpers as the next best thing since web forms and for the life of me I have no idea why! Why stop at the abstraction of DisplayFor, InputFor and LabelFor? Our views were suffering the same problem, even with a spark and HtmlTags spin:
<div class="fields"> !{Html.LabelFor(m => m.Quantity).AddClass("label")} !{Html.InputFor(m => m.Quantity).AddClass("field")} </div> <div class="fields"> !{Html.LabelFor(m => m.Price).AddClass("label")} !{Html.InputFor(m => m.Price).AddClass("field")} </div>
Both frameworks (HtmlTags or MVC2 Templates) can be extended to support the abstraction we produced, but HtmlTags is geared to do this in a much cleaner and flexible fashion. We created the idea of an edit template that includes the label and input (and validation if you so desire), all of which gets nested in some container tag. We rolled all of this up into two conventions: EditTemplateFor and DisplayTemplateFor. The difference being whether or not the field on the form is editable. I’ll admit that we could probably roll this up further into a TemplateFor convention that has configurable builders much like DisplayFor/InputFor/LabelFor in HtmlTags, but right now these are static conventions in that they don’t have hot swappable builders. That is an effort for another day :)
So here is what the views would look like now (sigh of relief):
<%: Html.EditTemplateFor(m => m.Title) %> <%: Html.EditTemplateFor(m => m.Price) %> <%: Html.EditTemplateFor(m => m.AlbumArtUrl)%> <%: Html.EditTemplateFor(m => m.Artist)%> <%: Html.EditTemplateFor(m => m.Genre)%> !{Html.EditTemplateFor(m => m.Quantity)} !{Html.EditTemplateFor(m => m.Price)}
public static class TemplateHelpers { public static string FieldDivClass = "field"; public static string FieldsDivClass = "fields"; public static string LabelClass = "label"; public static HtmlTag EditTemplateFor<T>(this T model, params Expression<Func<T, object>>[] fieldSelectors) where T : class { Contract.Requires(fieldSelectors.Any()); var label = model.LabelFor(fieldSelectors[0]).AddClass(LabelClass); var fields = fieldSelectors .Select(f => Tags.Div .AddClass(FieldDivClass) .Nest(model.InputFor(f))); return Tags.Div .AddClass(FieldsDivClass) .Nest(label) .Nest(fields.ToArray()); } public static HtmlTag EditTemplateFor<T>(this HtmlHelper<T> helper, params Expression<Func<T, object>>[] fieldSelectors) where T : class { return EditTemplateFor(helper.ViewData.Model, fieldSelectors); }Here is an alteration of the above to work with the MVC Music store ablum editor:
public static class TemplateHelpers { public static string FieldDivClass = "field"; public static string FieldsDivClass = "fields"; public static string LabelClass = "label"; public static string ValidationMessageClass = "validation-message"; public static HtmlTag EditTemplateFor<T>(this HtmlHelperhelper, Expression<Func<T, object>> field) where T : class { var label = helper.LabelFor(field); var input = helper.InputFor(field); var validation = Tags.Span.AddClass(ValidationMessageClass) return Tags.Paragraph .Nest(label,field,validation); } }
public static MvcHtmlString EditTemplateFor<T>(this HtmlHelper<T> helper, Expression<Func<T, object>> field) where T : class { var label = helper.LabelFor(field); var input = helper.EditorFor(field); var validation = helper.ValidationMessageFor(field); var template = String.Format("<p>{0}{1}{2}</p>", label, input, validation); return MvcHtmlString.Create(template); } public static MvcHtmlString EditTemplateFor2<T>(this HtmlHelper<T> helper, Expression<Func<T, object>> field) where T : class { var label = helper.LabelFor(field); var input = helper.EditorFor(field); var template = String.Format("<div class=\"fields\">{0}<div class=\"field\">{1}</div></div>", label, input); return MvcHtmlString.Create(template); }
This was my best attempt at producing the same behavior with ASP.Net MVC templates, and it's already run into some serious issues.
-Wes
Back in January, Jeremy Miller posted a nice article on HtmlTags: Shrink your Views with FubuMVC Html Conventions. We were immediately in love with the idea and have spent several months adapting the conventions to work with our ASP.Net MVC applications. I was having a conversation with Ryan recently, reflecting on how far we’ve come and how we had no vision of that when we first read that article. I want to share some of that, so I will be working on a series of blog posts to show “What we are doing with HtmlTags.” But first: why?
A lot of buzz has been generated around reusability and views, particularly html. Html being a highly compositional language, but lacking any ability for reuse, has led to copy paste hell. Several different philosophies are employed in View Engines (VE) to try to address this issue and recently MVC2 was released with it’s templated helpers. The problem with reusing html directly is that lacks the benefits of separating the concern of what we need from the html (building it) and generating it, classically, the difference between “What” I want versus “How” I get it. The power of the Linq abstraction shows the benefits of separating “What” versus “How.” HtmlTags is another model based approach that separates the “What” and “How.” It goes even further by allowing the “How” to be configured via conventions. Here is a list of the benefits this separation has brought to our team:
The next set of posts will cover examples of how we are using HtmlTags and how it’s paying dividends.
-Wes