Wednesday, May 19, 2010

Opinionated ASP.NET MVC 2 Template Helpers

If you have used ASP.NET MVC any, then you are probably aware of the MVC Contrib project hosted on CodePlex. It is a helpful library that provides useful tools that ASP.NET MVC doesn’t give you out of the box.

Opinionated Input Builders

One of the pieces that I really like is the Opinionated Input Builders that Eric Hexter wrote. The builder allows you to provide one property at a time from your View Model and it will output the label, required indicator, the appropriate input control, and whatever else that it needs to display.

<% using (Html.BeginForm()) { %>

    <%= Html.Input(m => m.FirstName) %>
    <%= Html.Input(m => m.LastName) %>
    <%= Html.Input(m => m.Email) %>

    <p class="actions">
        <input type="submit" value="Create" />
    </p>

<% } %>

 

MvcContribPicNik

ASP.NET MVC 2 EditorForModel

As it turns out, the ASP.NET MVC team took a similar approach when putting together the Template Helpers in  ASP.NET MVC 2. I ended up switching to the Template Helpers.

<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) { %>

    <%= Html.EditorForModel() %>

    <p class="actions">
        <input type="submit" value="Create" />
    </p>

<% } %>

With some CSS styling, the output of the EditorForModel is close to what the Opinionated Input Builder, but there are some problems as we’ll discuss in the next section.

Mvc2ModelPikNic

ASP.NET MVC 2 EditorFor

The problem is that I usually don’t want to display or edit my entire model. I often times have things in my View Model that I don’t particularly want to display.  I want to rather manually choose which properties I want to display or edit.

<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) { %>

    <%= Html.EditorFor(m => m.FirstName) %>
    <%= Html.EditorFor(m => m.LastName)%>
    <%-- Purposely Not Show the Email –%>

    <p class="actions"> 
        <input type="submit" value="Create" /> 
    </p> 

<% } %>

Using the above syntax doesn’t quite give the output I was looking for. Only the input controls are rendered (as seen in the following screenshot) instead of providing the scaffolding of label, input, and validation fields like the EditorForModel method provides.

Mvc2TemplatedHelperBeforePicNik

You end up having to provides the layout of the controls and explicitly mention the label, input controls, and validation for each property. 

Modify the Template Helpers MasterPage

I soon began to miss the simple syntax of rendering all the necessary code like I was used to when using the Opinionated Input Builders outputted from the MVC Contrib.

Well, a while back Brad Wilson (one of the ASP.NET MVC 2 developers), wrote an awesome series about Templated Helpers and the last post in the series, ASP.NET MVC 2 Templates, Part 5: Master Page Templates, he addressed this granular Opinionated Input Builder type syntax!

All you do, is to override the Master Page that the templates use internally. So, inside my Master Page I layout where I want the label, validation, and input controls to go and then ASP.NET MVC 2 does the rest by resolving which Template Helper to use!

I modified the Master Page some to suite my needs (I use divs instead of tables), but overall it is the same one that he lays out on his blog.

EditorTemplates/Tempate.Master

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<script runat="server">
    protected override void OnInit(EventArgs e) {
        base.OnInit(e);
 
        if (ViewData.ModelMetadata.HideSurroundingHtml) {
            TablePlaceholder.Visible = false;
        }
        else {
            Controls.Remove(Data);
            DataPlaceholder.Controls.Add(Data);
        }
    }
</script>
<asp:ContentPlaceHolder runat="server" id="Data" />
<asp:PlaceHolder runat="server" id="TablePlaceholder">
    <div class="displayLayout">
        <div class="displayLabel">
            <asp:ContentPlaceHolder runat="server" id="Label">
                <%= ViewData.ModelMetadata.IsRequired ? "*" : "" %>
                <%= ViewData.ModelMetadata.GetDisplayName() %>
            </asp:ContentPlaceHolder>
        </div>
        <div class="displayField">
            <asp:PlaceHolder runat="server" id="DataPlaceholder" />
            <asp:ContentPlaceHolder runat="server" ID="Validation">
                <%= Html.ValidationMessage("") %>
            </asp:ContentPlaceHolder>            
        </div>
        <div style="clear: both;"></div>
    </div>    
</asp:PlaceHolder>

EditorTemplate/String.aspx

<%@ Page Language="C#" MasterPageFile="Template.Master" Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="Data" runat="server">
    <%= Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
                     new { @class = "text-box single-line" }) %>
</asp:Content>

There are several other files that you’ll need. I’ve grabbed most of them from Brad’s blog post and have them in the demo application below that you can download.

0003d725337bf3a4617919ac6126dc07PicNik

Now we can try the above scenario one more time and get the output that we were expecting.

<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm()) { %>

    <%= Html.EditorFor(m => m.FirstName) %>
    <%= Html.EditorFor(m => m.LastName)%>
    <%-- Purposely Not Show the Email –%>

    <p class="actions"> 
        <input type="submit" value="Create" /> 
    </p> 

<% } %>

 

Mvc2TemplatedHelperAfterPicNik

 

cooltext439925016

No comments:

Post a Comment