Поделиться через


DRY-ing out the MVC 2 Templated Helpers

In my MSDN article  Using Templated Helpers to Display Data, I show how they provide a very productive means of building a UI for data sets. The code below shows a typical use of the Display and Label helpers.

 <span style="font-weight:bold;">
<%= Html.Label("Name") %>
:</span>
<%=  Html.Display("Name") %>

<br/><span style="font-weight:bold;">
 <%= Html.LabelFor(Pals => Pals.ID)%>
 :</span>
 <%= Html.DisplayFor(Pals => Pals.ID)%>
 
<br/><span style="font-weight:bold;">
<%= Html.LabelFor(Pals => Pals.Height)%>
:</span>
 <%= Html.DisplayFor(Pals => Pals.Height)%>

<br/><span style="font-weight:bold;">
  <%= Html.LabelFor(Pals => Pals.cool)%>
  :</span>
 <%= Html.DisplayFor(Pals => Pals.cool)%>

<br/><span style="font-weight:bold;">
<%= Html.LabelFor(Pals => Pals.email)%>
:</span>
 <%= Html.DisplayFor(Pals => Pals.email)%>


<br/><span style="font-weight:bold;">
 <%= Html.LabelFor(Pals => Pals.Bio)%>
 :</span>
 <%= Html.DisplayFor(Pals => Pals.Bio)%>

What I didn't like was the DRY violation; so I wrote a helper class that allows me to pass a field expression once.  Essentially I need a helper that will return Label(expression) + middle HTML + Display(expression). The following snippet solves this problem with added flexibility.

 public static class LabelEditorExtensions {
       const int _sbCap = 512;

       public static string LabelEditor(this HtmlHelper html, string expression, string postLblPreDisplay) {
           return html.Label(expression) + postLblPreDisplay + html.Editor(expression);
       }

       public static string LableEditorFor<TModel, TValue>(this HtmlHelper<TModel> html,
        Expression<Func<TModel, TValue>> expression, string postLblPreDisplay) where TModel : class {
           return LableEditorFor(html, expression, String.Empty, postLblPreDisplay, String.Empty, _sbCap);
       }

       public static string LableEditorFor<TModel, TValue>(this HtmlHelper<TModel> html,
         Expression<Func<TModel, TValue>> expression, string preLbl, string postLblPreDisplay,
         string postDisplay) where TModel : class {
           return LableEditorFor(html, expression, preLbl, postLblPreDisplay, postDisplay, _sbCap);
       }

       public static string LableEditorFor<TModel, TValue>(this HtmlHelper<TModel> html,
          Expression<Func<TModel, TValue>> expression, string preLbl, string postLblPreDisplay,
          string postDisplay, int sbCap) where TModel : class {

           StringBuilder sb = new StringBuilder(preLbl, sbCap);
           sb.Append(html.LabelFor(expression));
           sb.Append(postLblPreDisplay);
           sb.Append(html.EditorFor(expression));
           sb.Append(postDisplay);
           return sb.ToString();
       }
   }

Now I can write my view code in the DRYer method shown below.

  <span style="font-weight:bold;">
<%= Html.LabelDisplay("Name"," :</span>")%>

<br/><span style="font-weight:bold;">
   <%= Html.LabelDisplayFor(Pals => Pals.ID," :</span>")%>
 
<br/><span style="font-weight:bold;">
<%= Html.LabelDisplayFor(Pals => Pals.Height," :</span>")%>

<br/><span style="font-weight:bold;">
  <%= Html.LabelDisplayFor(Pals => Pals.cool," :</span>")%>

<br/><span style="font-weight:bold;">
<%= Html.LabelDisplayFor(Pals => Pals.email," :</span>")%>

<br/><span style="font-weight:bold;">
 <%= Html.LabelDisplayFor(Pals => Pals.Bio," :</span>")%>

Purists (and the Visual Studio Editor) won't approve of passing HTML into the helper method. (Visual Studio reports my span tag is not terminated because it has no way of knowing my LabelDisplay helper call will return the closing span.)

Because I'm a lazy programmer and I often repeat the same HTML pattern between my properties, I added a couple lazy helpers  to reduce the above snippet to the following:

 <%= Html.LabelDisplayForSB(Pals => Pals.Height)%>
    <%= Html.LabelDisplaySB("Name")%>
    <%= Html.LabelDisplayForSB(Pals => Pals.cool)%>
    <%= Html.LabelDisplayForSB(Pals => Pals.ID)%>
    <%= Html.LabelDisplayForSB(Pals => Pals.email)%>
    <%= Html.LabelDisplayForSB(Pals => Pals.Bio)%>

I also have a lazy helper version that uses tables, giving the nice appearance shown below.

When using the Editor templated helpers you have an additional call to ValidationMessage, forcing you to repeat your property one more time. Because there is currently no ValidationFor helper, I had to steal some internal source to roll in the Validation message. The snippet below shows only two of the six properties, but gives you an idea of un-DRYness:

 span style="font-weight:bold;">
<%= Html.Label("Name")%> 
:</span>
<%= Html.Editor("Name")%> 
<%= Html.ValidationMessage("Name", "*") %>
<br/>

<span style="font-weight:bold;">
<%= Html.LabelFor(Pals => Pals.Height)%>
:</span>
<%= Html.EditorFor(Pals => Pals.Height)%>
<%= Html.ValidationMessage("Height", "*")%>
<br/>

The complete lazy helper version shown below is much easier to read and maintain.

 <%= Html.ValLableEditorForTD(Pals => Pals.Name)%> 
<%= Html.LabelDisplayForTD(Pals => Pals.ID)%>
<%= Html.ValLableEditorForTD(Pals => Pals.Height)%>
<%= Html.LableEditorForTD(Pals => Pals.cool)%>
<%= Html.ValLableEditorForTD(Pals => Pals.email)%>
<%= Html.LableEditorForTD(Pals => Pals.Bio)%>

The image below shows the DataAnnotations driven validation error from the code above.

A complete sample is included with my Label/Editor/Display integration helper (see Attachment below). I provide the Expression overloads for my helpers and a few string based helpers.

Lbl4Ed4.zip