Thursday, July 15, 2010

Html.SmartLabelFor

I was really happy with the "EditorFor", "LabelFor" etc helpers in MVC2 and I have been using them all over the place in my project. Most of my models have some kind of required fields in them and although they have been validating correctly and displaying the error messages correctly, I want to notify the user that the field is required on the initial form. Usually this is done by giving some kind of visual indicator for the required fields, such as "*" or making the label as bold. In my initial attempts, I just add "*":


        <div class="editor-label">
            <%: Html.LabelFor(m => m.FullName) %> *
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

After looking at it for a while, it looks ugly. It worked, but I want a more elegant solution and I made an Html Helper.

What I want it to do is basically appending an "*" next to the label, ONLY IF the field is marked as REQUIRED, so I can do this:

        <div class="editor-label">
            <%: Html.SmartLabelFor(m => m.FullName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

If "FullName" is attributed with "Required", it will render "Full Name *" but will just render "Full Name" if it's not marked "Required".

Without further ado, here is the code - pretty simple:
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;

    public static class LabelExtensions {
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString SmartLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
            return LabelHelper(html,
                               ModelMetadata.FromLambdaExpression(expression, html.ViewData),
                               ExpressionHelper.GetExpressionText(expression));
        }

        internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName) {
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText)) {
                return MvcHtmlString.Empty;
            }

            if (metadata.IsRequired) labelText = labelText + " *";

            TagBuilder tag = new TagBuilder("label");
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }
    }
}
Additional readings:
-- read more and comment ...

Wednesday, July 7, 2010

Html Helper for Model Binding in MVC2

In ASP.NET MVC2, you can create a model and bind its properties to the UI by using "DisplayFor" or "LabelFor" or "EditorFor". To illustrate, let's take a look an example. For instance, if I have a model:

 
    public class Person {
        [Required]
        [DisplayName("Full Name")]
        public string FullName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [DisplayName("Email Address")]
        public string Email { get; set; }

        [DataType(DataType.MultilineText)]
        [DisplayName("Address")]
        public string Address{ get; set; }
    }  
To create an editor for the model will look like this in the old way of MVC:
   

        <div class="editor-label">
            Full Name 
        </div>
        <div class="editor-field">
            <%=Html.TextBox("FullName") %>
            <%=Html.ValidationMessage("FullName") %>
        </div>

With the new "DisplayFor", "EditorFor" and "LabelFor" etc, we can transform that code into something like this - and it will render the same way:
  

        <div class="editor-label">
            <%: Html.LabelFor(m => m.FullName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(m => m.FullName) %>
            <%: Html.ValidationMessageFor(m => m.FullName) %>
        </div>

This code is much cleaner, flexible, easier to maintain, and validation is centralized in the business layer. So for example, if for whatever reason the label should be changed to "Name" instead of "Full Name", all we need to do is change the "DisplayName" attribute in the model declaration.

Now for the address field, we attributed "DataType(DataType.MultilineText)" to it. This will automatically render out a text area instead of a regular text box.

If we do not like the way the default template render, we can always override it. Brad Wilson has an excellent blog post series about this.
-- read more and comment ...