Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Introduction:

          ASP.NET MVC 2 makes it very easy to enable client side validation in your application, due to which your application users will see the feedback immediately before your form will submit anything to the server. ASP.NET MVC 2 enable client side validation by emitting client side script immediately after the form close tag. But due to performance, developers likes to emit inline script as low in the page as possible, as explained at here. There is another concern of inline script is that javascript that is embedded in the HTML of the page can be seen by search engines. This could be a concern for SEO. For detail of this please see this. Therefore in this article I will show you how to move inline scripts (which is emitted by ASP.NET MVC to enable client side validation) to the bottom. 

 

    Description:

          Let's say (for demonstration purpose) you are collecting user information in two forms. First form includes user's First Name and Last Name. The second form includes user's Email and Company Name. Your page will look like this,

 

           

 

          Let's create a sample ASP.NET MVC 2 application to see this approach. First of all open HomeController.cs and add the following code,

 

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public ActionResult SaveFirstLastName([Bind(Exclude = "Email, CompanyName")]UserInformation u)
        {
            ModelState["Email"].Errors.Clear();
            ModelState["CompanyName"].Errors.Clear();            
            if(!ModelState.IsValid)
                return View("Index");
            return Content("Thanks you for submitting First Name and Last Name information");
        }
        [HttpPost]
        public ActionResult SaveEmailCompanyName([Bind(Exclude = "FirstName, LastName")]UserInformation u)
        {
            ModelState["FirstName"].Errors.Clear();
            ModelState["LastName"].Errors.Clear();
            if (!ModelState.IsValid)
                return View("Index");
            return Content("Thanks you for submitting Email and Company Name information");
        }
    }

 

          Next open the Index view for Home Controller and add the following lines,

 

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ClientSideValidationWithDynamicContent.Models.UserInformation>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <table>
        <tr>
            <td align="left">
                <% Html.EnableClientValidation(); %>
                <%using (Html.BeginForm("SaveFirstLastName", "Home"))
                  { %>
                <table>
                    <tr style="background-color: #E8EEF4; font-weight: bold">
                        <td colspan="3" align="center">
                            User First and Last Name
                        </td>
                    </tr>
                    <tr>
                        <td>
                            First Name
                        </td>
                        <td>
                            <%=Html.TextBoxFor(a => a.FirstName)%>
                        </td>
                        <td>
                            <%=Html.ValidationMessageFor(a => a.FirstName)%>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Last Name
                        </td>
                        <td>
                            <%=Html.TextBoxFor(a => a.LastName)%>
                        </td>
                        <td>
                            <%=Html.ValidationMessageFor(a => a.LastName)%>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3" align="center">
                            <input type="submit" value="Submit" />
                        </td>
                    </tr>
                </table>
                <%} %>
            </td>
            </tr>
            <tr>
            <td align="left">
                <%using (Html.BeginForm("SaveEmailCompanyName", "Home"))
                  { %>
                <table>
                    <tr style="background-color: #E8EEF4; font-weight: bold">
                        <td colspan="3" align="center">
                            User Email and Company Name
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Email
                        </td>
                        <td>
                            <%=Html.TextBoxFor(a => a.Email)%>
                        </td>
                        <td>
                            <%=Html.ValidationMessageFor(a => a.Email)%>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Company Name
                        </td>
                        <td>
                            <%=Html.TextBoxFor(a => a.CompanyName)%>
                        </td>
                        <td>
                            <%=Html.ValidationMessageFor(a => a.CompanyName)%>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3" align="center">
                            <input type="submit" value="Submit" />
                        </td>
                    </tr>
                </table>
                <%} %>
            </td>
        </tr>
    </table>
</asp:Content>

 

          Next add the necessary script files in Site.Master,

 

    <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>

 

          Next create a new class file UserInformation.cs inside Model folder and add the following code,

 

    public class UserInformation
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "First Name is required")]
        [StringLength(10, ErrorMessage = "First Name max length is 10")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is required")]
        [StringLength(10, ErrorMessage = "Last Name max length is 10")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Email is required")]
        [RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "Email Format is wrong")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Company Name is required")]
        [StringLength(10, ErrorMessage = "Company Name max length is 10")]
        public string CompanyName { get; set; }
    }

 

          Now run this application and see the page view source, you will find something like this,

 

<body>
    <div class="page">

        <div id="header">
            <div id="title">                
            </div>
              
            <div id="logindisplay">
            </div> 
            
            <div id="menucontainer">
            
                <ul id="menu">              
                    <li><a href="http://weblogs.asp.net/">Home</a></li>

                </ul>
            
            </div>
        </div>

        <div id="main">
            
    <table>
        <tr>
            <td align="left">
                <form action="/Home/SaveFirstLastName" id="form0" method="post">

                <table>
                    <tr style="background-color: #E8EEF4; font-weight: bold">
                        <td colspan="3" align="center">
                            User First and Last Name
                        </td>
                    </tr>
                    <tr>
                        <td>
                            First Name
                        </td>

                        <td>
                            <input id="FirstName" name="FirstName" type="text" value="" >
                        </td>
                        <td>
                            <span class="field-validation-valid" id="FirstName_validationMessage"></span>
                        </td>
                    </tr>
                    <tr>
                        <td>

                            Last Name
                        </td>
                        <td>
                            <input id="LastName" name="LastName" type="text" value="" />
                        </td>
                        <td>
                            <span class="field-validation-valid" id="LastName_validationMessage"></span>
                        </td>
                    </tr>

                    <tr>
                        <td colspan="3" align="center">
                            <input type="submit" value="Submit" />
                        </td>
                    </tr>
                </table>
                </form><script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"FirstName","ReplaceValidationMessageContents":true,"ValidationMessageId":"FirstName_validationMessage","ValidationRules":[{"ErrorMessage":"First Name max length is 10","ValidationParameters":{"minimumLength":0,"maximumLength":10},"ValidationType":"stringLength"},{"ErrorMessage":"First Name is required","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"LastName","ReplaceValidationMessageContents":true,"ValidationMessageId":"LastName_validationMessage","ValidationRules":[{"ErrorMessage":"Last Name max length is 10","ValidationParameters":{"minimumLength":0,"maximumLength":10},"ValidationType":"stringLength"},{"ErrorMessage":"Last Name is required","ValidationParameters":{},"ValidationType":"required"}]}],"FormId":"form0","ReplaceValidationSummary":false});
//]]>
</script>
            </td>

            </tr>
            <tr>
            <td align="left">
                <form action="/Home/SaveEmailCompanyName" id="form1" method="post">
                <table>
                    <tr style="background-color: #E8EEF4; font-weight: bold">
                        <td colspan="3" align="center">
                            User Email and Company Name
                        </td>

                    </tr>
                    <tr>
                        <td>
                            Email
                        </td>
                        <td>
                            <input id="Email" name="Email" type="text" value="" />
                        </td>
                        <td>

                            <span class="field-validation-valid" id="Email_validationMessage"></span>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Company Name
                        </td>
                        <td>
                            <input id="CompanyName" name="CompanyName" type="text" value="" />

                        </td>
                        <td>
                            <span class="field-validation-valid" id="CompanyName_validationMessage"></span>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3" align="center">
                            <input type="submit" value="Submit" />
                        </td>

                    </tr>
                </table>
                </form><script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"FieldName":"Email","ReplaceValidationMessageContents":true,"ValidationMessageId":"Email_validationMessage","ValidationRules":[{"ErrorMessage":"Email Format is wrong","ValidationParameters":{"pattern":"^\\w+([-+.\u0027]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"},"ValidationType":"regularExpression"},{"ErrorMessage":"Email is required","ValidationParameters":{},"ValidationType":"required"}]},{"FieldName":"CompanyName","ReplaceValidationMessageContents":true,"ValidationMessageId":"CompanyName_validationMessage","ValidationRules":[{"ErrorMessage":"Company Name max length is 10","ValidationParameters":{"minimumLength":0,"maximumLength":10},"ValidationType":"stringLength"},{"ErrorMessage":"Company Name is required","ValidationParameters":{},"ValidationType":"required"}]}],"FormId":"form1","ReplaceValidationSummary":false});
//]]>
</script>
            </td>
        </tr>
    </table>


            <div id="footer">

            </div>
        </div>
    </div>
</body>

 

          The above page view source shows that validation scripts is embedded just after the closing tag of both form elements. To move these scripts to the bottom of the page, we will add a response filter(which is used to modify the HTTP entity) in our application. So create a new class file MoveFormScriptStream.cs inside Helper folder and add the following code,

 

    public class MoveFormScriptStream : Stream
    {
        //Other Members are not included for brevity

        StringBuilder s = new StringBuilder();
        public override void Write(byte[] buffer, int offset, int count)
        {
            string HTML = Encoding.UTF8.GetString(buffer, offset, count);
            Regex regex = new Regex(@"</form><script[^>]*>(?<script>([^<]|<[^/])*)</script>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            HTML = regex.Replace(HTML, m => { s.Append(m.Groups["script"].Value); return "</form>"; });
            if(s.Length>0)
                HTML = HTML.Replace("</body>", "<script type='text/javascript'>" + s.ToString() + "</script></body>");
            buffer = System.Text.Encoding.UTF8.GetBytes(HTML);
            this.Base.Write(buffer, 0, buffer.Length);
        }
    }

           The above code is used to filter the response by moving validation scripts to the bottom of the page. Now create a custom action filter MoveFormsScriptAttribute inside your Helper folder and add the following code,

 

    public class MoveFormsScriptAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Filter = new MoveFormScriptStream(filterContext.HttpContext.Response.Filter);
        }
    }

 

           The above code will register the response filter. Now add [MoveFormsScript] attribute to your action methods by again opening HomeController.cs and adding the following code,

 

    public class HomeController : Controller
    {
        [MoveFormsScript]
        public ActionResult Index()
        {
            return View();
        }
        [HttpPost, MoveFormsScript]
        public ActionResult SaveFirstLastName([Bind(Exclude = "Email, CompanyName")]UserInformation u)
        {
            ModelState["Email"].Errors.Clear();
            ModelState["CompanyName"].Errors.Clear();            
            if(!ModelState.IsValid)
                return View("Index");
            return Content("Thanks you for submitting First Name and Last Name information");
        }
        [HttpPost, MoveFormsScript]
        public ActionResult SaveEmailCompanyName([Bind(Exclude = "FirstName, LastName")]UserInformation u)
        {
            ModelState["FirstName"].Errors.Clear();
            ModelState["LastName"].Errors.Clear();
            if (!ModelState.IsValid)
                return View("Index");
            return Content("Thanks you for submitting Email and Company Name information");
        }
    }

 

          Now run your application again and you will see the difference in the page view source,

 

<body>
    <div class="page">
        <div id="header">
            <div id="title">
            </div>
            <div id="logindisplay">
            </div>
            <div id="menucontainer">
                <ul id="menu">
                    <li><a href="http://weblogs.asp.net/">Home</a></li>
                </ul>
            </div>
        </div>
        <div id="main">
            <table>
                <tr>
                    <td align="left">
                        <form action="/Home/SaveFirstLastName" id="form0" method="post">
                        <table>
                            <tr style="background-color: #E8EEF4; font-weight: bold">
                                <td colspan="3" align="center">
                                    User First and Last Name
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    First Name
                                </td>
                                <td>
                                    <input id="FirstName" name="FirstName" type="text" value="" />
                                </td>
                                <td>
                                    <span class="field-validation-valid" id="FirstName_validationMessage"></span>
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    Last Name
                                </td>
                                <td>
                                    <input id="LastName" name="LastName" type="text" value="" />
                                </td>
                                <td>
                                    <span class="field-validation-valid" id="LastName_validationMessage"></span>
                                </td>
                            </tr>
                            <tr>
                                <td colspan="3" align="center">
                                    <input type="submit" value="Submit" />
                                </td>
                            </tr>
                        </table>
                        </form>
                    </td>
                </tr>
                <tr>
                    <td align="left">
                        <form action="/Home/SaveEmailCompanyName" id="form1" method="post">
                        <table>
                            <tr style="background-color: #E8EEF4; font-weight: bold">
                                <td colspan="3" align="center">
                                    User Email and Company Name
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    Email
                                </td>
                                <td>
                                    <input id="Email" name="Email" type="text" value="" />
                                </td>
                                <td>
                                    <span class="field-validation-valid" id="Email_validationMessage"></span>
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    Company Name
                                </td>
                                <td>
                                    <input id="CompanyName" name="CompanyName" type="text" value="" />
                                </td>
                                <td>
                                    <span class="field-validation-valid" id="CompanyName_validationMessage"></span>
                                </td>
                            </tr>
                            <tr>
                                <td colspan="3" align="center">
                                    <input type="submit" value="Submit" />
                                </td>
                            </tr>
                        </table>
                        </form>
                    </td>
                </tr>
            </table>
            <div id="footer">
            </div>
        </div>
    </div>
    <script type='text/javascript'>
//<![CDATA[
        if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
        window.mvcClientValidationMetadata.push({ "Fields": [{ "FieldName": "FirstName", "ReplaceValidationMessageContents": true, "ValidationMessageId": "FirstName_validationMessage", "ValidationRules": [{ "ErrorMessage": "First Name is required", "ValidationParameters": {}, "ValidationType": "required" }, { "ErrorMessage": "First Name max length is 10", "ValidationParameters": { "minimumLength": 0, "maximumLength": 10 }, "ValidationType": "stringLength"}] }, { "FieldName": "LastName", "ReplaceValidationMessageContents": true, "ValidationMessageId": "LastName_validationMessage", "ValidationRules": [{ "ErrorMessage": "Last Name is required", "ValidationParameters": {}, "ValidationType": "required" }, { "ErrorMessage": "Last Name max length is 10", "ValidationParameters": { "minimumLength": 0, "maximumLength": 10 }, "ValidationType": "stringLength"}]}], "FormId": "form0", "ReplaceValidationSummary": false });
        //]]>

        //<![CDATA[
        if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
        window.mvcClientValidationMetadata.push({ "Fields": [{ "FieldName": "Email", "ReplaceValidationMessageContents": true, "ValidationMessageId": "Email_validationMessage", "ValidationRules": [{ "ErrorMessage": "Email Format is wrong", "ValidationParameters": { "pattern": "^\\w+([-+.\u0027]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$" }, "ValidationType": "regularExpression" }, { "ErrorMessage": "Email is required", "ValidationParameters": {}, "ValidationType": "required"}] }, { "FieldName": "CompanyName", "ReplaceValidationMessageContents": true, "ValidationMessageId": "CompanyName_validationMessage", "ValidationRules": [{ "ErrorMessage": "Company Name is required", "ValidationParameters": {}, "ValidationType": "required" }, { "ErrorMessage": "Company Name max length is 10", "ValidationParameters": { "minimumLength": 0, "maximumLength": 10 }, "ValidationType": "stringLength"}]}], "FormId": "form1", "ReplaceValidationSummary": false });
//]]>
    </script></body>

 

          The above page view source shows that now all the client side validation scripts is correctly moved to the bottom of the page.

 

    Summary:

          In this article I showed you how you can easily move your inline validation scripts to the bottom of the page. This will improve your application performance. This will/may also improve your page rank and search engine results. I explain you with an example. Hopefully you will enjoy this article too. You can also download the attached file.

Published Sunday, September 26, 2010 11:40 PM by imran_ku07

Comments

# re: Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Monday, September 27, 2010 2:42 AM by gabriel.lozano-moran

Nice one!

# re: Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Monday, September 27, 2010 3:02 AM by nachid

Hi, I gave a try to your method and it has big drawback for me

It needs an action to generate a form

The majority of my forms are not generated from actions.

I usually display them with

Html.RenderPartial(@"MyForm", MyModel);

I use actions only for posting

# re: Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Monday, September 27, 2010 3:09 AM by imran_ku07

@nachid,

It just a demonstration.

You can use Html.RenderPartial(@"MyForm", MyModel), it will equally work

# re: Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Monday, September 27, 2010 3:23 AM by nachid

Imran

I am facing another issue with actions that have compression attribute

I am still investigating

# re: Moving ASP.NET MVC Client Side Validation Scripts to Bottom

Monday, September 27, 2010 8:12 AM by gabriel.lozano-moran

Like I said this is very nice and I would suggest to create a Html helper out of this to move away to responsability from the controller/action to the view (seperation of concerns).

This would allow you to write anywhere in the bottom of your view something like <%: Html.ClientValidationScript() %> and you would have control overe where the script would be emitted.