Integrate Html5 Form in ASP.NET MVC

This article is divided into three parts. In the first, part I will show you how you can add Html5 forms in your ASP.NET MVC application with very minimum effort. In the second part, I will show you how to implement client side validation which will trigger automatically even when the browser does not have the html5 client side validation support and in the last part I will show widgetify the form in the client side with jQuery for the older browser that does not have support for Html5 form.

Let’s begin with the server side first. To add Html5 form I will utilize the ModelMetadata and DataAnnotation features that are available in ASP.NET MVC. The DataAnnotation has several attributes which we can use when generating the input types. Let’s say I have a dummy model which is fully decorated with the DataAnnotation attributes:

public class Html5FormModel
{
    [Display(Name = "String", Prompt = "Type a string"), Required]
    public string StringProperty { get; set; }

    [Display(Name = "Decimal", Prompt = "Type a decimal"), Range(1.0, 100.0), Required]
    public decimal? DecimalProperty { get; set; }

    [Display(Name = "Date", Prompt = "Type a date"), DataType(DataType.Date), Required]
    public DateTime? DateProperty { get; set; }

    [Display(Name = "Url", Prompt = "Type an url"), DataType(DataType.Url), Required]
    public string UrlProperty { get; set; }

    [Display(Name = "Email", Prompt = "Type an email"), DataType(DataType.EmailAddress), Required]
    public string EmailProperty { get; set; }

    [Display(Name = "Phone number", Prompt = "Type a phone number"), DataType(DataType.PhoneNumber), Required]
    public string PhoneProperty { get; set; }
}

And I am using the following code to generate the form:

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true, "Validation errors. Please correct the errors and try again.")
    <div>
        <fieldset>

            <legend>Html5 form</legend>

            <div class="editor-label">
                @Html.LabelFor(m => m.StringProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.StringProperty)
                @Html.ValidationMessageFor(m => m.StringProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.DecimalProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.DecimalProperty)
                @Html.ValidationMessageFor(m => m.DecimalProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.DateProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.DateProperty)
                @Html.ValidationMessageFor(m => m.DateProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.UrlProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.UrlProperty)
                @Html.ValidationMessageFor(m => m.UrlProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.EmailProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.EmailProperty)
                @Html.ValidationMessageFor(m => m.EmailProperty)
            </div>

            <div class="editor-label">
                @Html.LabelFor(m => m.PhoneProperty)
            </div>
            <div class="editor-field">
                @Html.EditorFor(m => m.PhoneProperty)
                @Html.ValidationMessageFor(m => m.PhoneProperty)
            </div>

            <p>
                <input type="submit" value="Submit" />
            </p>

        </fieldset>
    </div>
}

Check that I am not using the TextBox of HtmlHelper as it will inject the old input type="text" instead I am using the EditorFor so that I can put my own template to render the inputs. Now, If you open the zip file that I have attached in the bottom of the post, you will find there are separate editor template for each type of DataAnnotation DataType attribute and Html5 form input type (both chstml and aspx templates are included). Some of the codes are something like these:

<input type="date"/>

@Html.Html5Form().InputDate()

<input type="email"/>

@Html.Html5Form().InputEmail()

<input type="number"/>

@Html.Html5Form().InputNumber()

Behind the scene there is class InputHelper which does the actual heavy lifting, here is the code:

public static class HtmlHelperExtension
{
    public static Html5Form Html5Form(this HtmlHelper instance)
    {
        return new Html5Form(instance);
    }
}

public class Html5Form
{
    private readonly HtmlHelper htmlHelper;

    public Html5Form(HtmlHelper htmlHelper)
    {
        this.htmlHelper = htmlHelper;
    }

    private static CultureInfo Culture { get { return CultureInfo.CurrentCulture; } }

    public virtual IHtmlString InputText()
    {
        return Build("text");
    }

    public virtual IHtmlString InputEmail()
    {
        return Build("email");
    }

    public virtual IHtmlString InputUrl()
    {
        return Build("url");
    }

    public virtual IHtmlString InputNumber()
    {
        return Build("number");
    }

    public virtual IHtmlString InputRange()
    {
        return Build("range");
    }

    public virtual IHtmlString InputPassword()
    {
        return Build("password");
    }

    public virtual IHtmlString InputTelephone()
    {
        return Build("tel");
    }

    public virtual IHtmlString InputDate()
    {
        return Build("date");
    }

    public virtual IHtmlString InputTime()
    {
        return Build("time");
    }

    public virtual IHtmlString InputDateTime()
    {
        return Build("datetime");
    }

    private IHtmlString Build(string type)
    {
        var viewData = htmlHelper.ViewData;
        var metadata = viewData.ModelMetadata;

        var placeHolder = !string.IsNullOrWhiteSpace(metadata.Watermark) ? "placeholder=\"" + metadata.Watermark + "\"" : string.Empty;
        var value = viewData.TemplateInfo.FormattedModelValue.ToString();
        var valueAttribute = !string.IsNullOrWhiteSpace(value) ? "value=\"" + value + "\"" : string.Empty;
        var id = viewData.TemplateInfo.GetFullHtmlFieldId(string.Empty);
        var name = viewData.TemplateInfo.GetFullHtmlFieldName(string.Empty);
        var cssClass = "text-box single-line";

        ModelState state;

        if (viewData.ModelState.TryGetValue(name, out state) && (state.Errors.Count > 0))
        {
            cssClass += " " + HtmlHelper.ValidationInputCssClassName;
        }

        var validators = GetValidators(metadata);

        string input = string.Format(Culture, "<input type=\"{0}\" id=\"{1}\" name=\"{2}\" class=\"{3}\" {4} {5} {6}/>", type, id, name, cssClass, valueAttribute, placeHolder, validators);

        return MvcHtmlString.Create(input);
    }

    private string GetValidators(ModelMetadata metadata)
    {
        var unobtrusiveJavaScriptEnabled = htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;

        var rules = new StringBuilder();
        var validatorsDictionary = ModelValidatorProviders.Providers
                                                          .GetValidators(metadata, htmlHelper.ViewContext)
                                                          .SelectMany(v => v.GetClientValidationRules());

        foreach (var rule in validatorsDictionary)
        {
            if (rule is ModelClientValidationRequiredRule)
            {
                rules.Append(" required=\"required\"");

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-required=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }

            }
            else if (rule is ModelClientValidationRegexRule)
            {
                rules.AppendFormat(Culture, " pattern=\"{0}\"", rule.ValidationParameters["pattern"]);

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-pattern=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }
            }
            else if (rule is ModelClientValidationRangeRule)
            {
                rules.AppendFormat(Culture, " min=\"{0}\"", rule.ValidationParameters["min"]);
                rules.AppendFormat(Culture, " max=\"{0}\"", rule.ValidationParameters["max"]);

                if (unobtrusiveJavaScriptEnabled)
                {
                    rules.AppendFormat(Culture, " data-val-range=\"{0}\"", HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
                }
            }
            else
            {
                if (!unobtrusiveJavaScriptEnabled)
                {
                    continue;
                }

                var dictionaryKey = "data-val-" + rule.ValidationType;

                rules.AppendFormat(Culture, " {0}=\"{1}\"", dictionaryKey, HttpUtility.HtmlEncode(rule.ErrorMessage));

                dictionaryKey = dictionaryKey + "-";

                foreach (var pair in rule.ValidationParameters)
                {
                    rules.AppendFormat(Culture, " {0}=\"{1}\"", dictionaryKey + pair.Key, HttpUtility.HtmlEncode(pair.Value));
                }
            }
        }


        return rules.ToString();
    }
}

Here is the markup that is generated when it is running in the browser:

<form action="/" method="post">
<div>
    <fieldset>
        <legend>Html5 form</legend>
        <div class="editor-label">
            <label for="StringProperty">String</label>
        </div>
        <div class="editor-field">
            <input type="text" id="StringProperty" name="StringProperty" class="text-box single-line" placeholder="Type a string" required="required" />
        </div>
        <div class="editor-label">
            <label for="DecimalProperty">Decimal</label>
        </div>
        <div class="editor-field">
            <input type="number" id="DecimalProperty" name="DecimalProperty" class="text-box single-line" placeholder="Type a decimal" min="1" max="100" required="required" />
        </div>
        <div class="editor-label">
            <label for="DateProperty">Date</label>
        </div>
        <div class="editor-field">
            <input type="date" id="DateProperty" name="DateProperty" class="text-box single-line" placeholder="Type a date" required="required" />
        </div>
        <div class="editor-label">
            <label for="UrlProperty">Url</label>
        </div>
        <div class="editor-field">
            <input type="url" id="UrlProperty" name="UrlProperty" class="text-box single-line" placeholder="Type an url" required="required" />
        </div>
        <div class="editor-label">
            <label for="EmailProperty">Email</label>
        </div>
        <div class="editor-field">
            <input type="email" id="EmailProperty" name="EmailProperty" class="text-box single-line" placeholder="Type an email" required="required" />
        </div>
        <div class="editor-label">
            <label for="PhoneProperty">Phone number</label>
        </div>
        <div class="editor-field">
            <input type="tel" id="PhoneProperty" name="PhoneProperty" class="text-box single-line" placeholder="Type a phone number" required="required" />
        </div>
        <p>
            <input type="submit" value="Submit" />
        </p>
    </fieldset>
</div>
</form>

As you can see all the DataAnnotation attributes are generated with correct input attributes. Input type to DataAnnotation data type, required and range also prompt to placeholder.

In my previous post, I mentioned that most of the current desktop browsers does not have the full support of Html5 form but it will fallback to regular input even we are using the html5 form. Here are the screenshots of some of the major browsers:

Opera 10.6

opera

Chorme 8.0

webkit

FF 3.6

ff

IE9

ie

 

As you can see both chorme and opera are utilizing some of the Html5 form features but firefox and ie is rendering it as regular input elements.

That’s it for today, in the next post I will show the client side validation of html5 form, so stay tuned.

Download: Html5Form.zip

Shout it

8 Comments

Comments have been disabled for this content.