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,
01 |
public
class
HomeController : Controller
|
02 |
{
|
03 |
public
ActionResult Index()
|
04 |
{
|
05 |
return
View();
|
06 |
}
|
07 |
[HttpPost]
|
08 |
public
ActionResult SaveFirstLastName([Bind(Exclude
= "Email, CompanyName")]UserInformation u)
|
09 |
{
|
10 |
ModelState["Email"].Errors.Clear();
|
11 |
ModelState["CompanyName"].Errors.Clear();
|
12 |
if(!ModelState.IsValid)
|
13 |
return
View("Index");
|
14 |
return
Content("Thanks you for submitting First Name and
Last Name information");
|
15 |
}
|
16 |
[HttpPost]
|
17 |
public
ActionResult
SaveEmailCompanyName([Bind(Exclude = "FirstName, LastName")]UserInformation u)
|
18 |
{
|
19 |
ModelState["FirstName"].Errors.Clear();
|
20 |
ModelState["LastName"].Errors.Clear();
|
21 |
if
(!ModelState.IsValid)
|
22 |
return
View("Index");
|
23 |
return
Content("Thanks you for submitting Email and Company
Name information");
|
24 |
}
|
25 |
}
|
Next open the Index view for Home Controller and add the following lines,
01 |
<%@ Page Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<ClientSideValidationWithDynamicContent.Models.UserInformation>" %>
|
02 |
03 |
<asp:Content
ID="Content1"
ContentPlaceHolderID="TitleContent"
runat="server">
|
04 |
Home Page
|
05 |
</asp:Content>
|
06 |
07 |
<asp:Content
ID="Content2"
ContentPlaceHolderID="MainContent"
runat="server">
|
08 |
<table>
|
09 |
<tr>
|
10 |
<td
align="left">
|
11 |
<% Html.EnableClientValidation();
%>
|
12 |
<%using
(Html.BeginForm("SaveFirstLastName",
"Home"))
|
13 |
{ %>
|
14 |
<table>
|
15 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
16 |
<td
colspan="3"
align="center">
|
17 |
User First and Last Name
|
18 |
</td>
|
19 |
</tr>
|
20 |
<tr>
|
21 |
<td>
|
22 |
First Name
|
23 |
</td>
|
24 |
<td>
|
25 |
<%=Html.TextBoxFor(a =>
a.FirstName)%>
|
26 |
</td>
|
27 |
<td>
|
28 |
<%=Html.ValidationMessageFor(a =>
a.FirstName)%>
|
29 |
</td>
|
30 |
</tr>
|
31 |
<tr>
|
32 |
<td>
|
33 |
Last Name
|
34 |
</td>
|
35 |
<td>
|
36 |
<%=Html.TextBoxFor(a =>
a.LastName)%>
|
37 |
</td>
|
38 |
<td>
|
39 |
<%=Html.ValidationMessageFor(a =>
a.LastName)%>
|
40 |
</td>
|
41 |
</tr>
|
42 |
<tr>
|
43 |
<td
colspan="3"
align="center">
|
44 |
<input
type="submit"
value="Submit"
/>
|
45 |
</td>
|
46 |
</tr>
|
47 |
</table>
|
48 |
<%} %>
|
49 |
</td>
|
50 |
</tr>
|
51 |
<tr>
|
52 |
<td
align="left">
|
53 |
<%using
(Html.BeginForm("SaveEmailCompanyName",
"Home"))
|
54 |
{ %>
|
55 |
<table>
|
56 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
57 |
<td
colspan="3"
align="center">
|
58 |
User Email and Company Name
|
59 |
</td>
|
60 |
</tr>
|
61 |
<tr>
|
62 |
<td>
|
63 |
Email
|
64 |
</td>
|
65 |
<td>
|
66 |
<%=Html.TextBoxFor(a =>
a.Email)%>
|
67 |
</td>
|
68 |
<td>
|
69 |
<%=Html.ValidationMessageFor(a =>
a.Email)%>
|
70 |
</td>
|
71 |
</tr>
|
72 |
<tr>
|
73 |
<td>
|
74 |
Company Name
|
75 |
</td>
|
76 |
<td>
|
77 |
<%=Html.TextBoxFor(a =>
a.CompanyName)%>
|
78 |
</td>
|
79 |
<td>
|
80 |
<%=Html.ValidationMessageFor(a =>
a.CompanyName)%>
|
81 |
</td>
|
82 |
</tr>
|
83 |
<tr>
|
84 |
<td
colspan="3"
align="center">
|
85 |
<input
type="submit"
value="Submit"
/>
|
86 |
</td>
|
87 |
</tr>
|
88 |
</table>
|
89 |
<%} %>
|
90 |
</td>
|
91 |
</tr>
|
92 |
</table>
|
93 |
</asp:Content>
|
Next add the necessary script files in Site.Master,
1 |
<script
src="../../Scripts/MicrosoftAjax.js"
type="text/javascript"></script>
|
2 |
<script
src="../../Scripts/MicrosoftMvcAjax.js"
type="text/javascript"></script>
|
3 |
<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,
01 |
public
class
UserInformation
|
02 |
{
|
03 |
public
int
Id { get; set; }
|
04 |
05 |
[Required(ErrorMessage = "First Name is required")]
|
06 |
[StringLength(10, ErrorMessage = "First Name max length is 10")]
|
07 |
public
string
FirstName { get; set; }
|
08 |
09 |
[Required(ErrorMessage = "Last Name is required")]
|
10 |
[StringLength(10, ErrorMessage = "Last Name max length is 10")]
|
11 |
public
string
LastName { get; set; }
|
12 |
13 |
[Required(ErrorMessage = "Email is required")]
|
14 |
[RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "Email Format is wrong")]
|
15 |
public
string
Email { get; set; }
|
16 |
17 |
[Required(ErrorMessage = "Company Name is required")]
|
18 |
[StringLength(10, ErrorMessage = "Company Name max length is 10")]
|
19 |
public
string
CompanyName { get; set; }
|
20 |
}
|
Now run this application and see the page view source, you will find something like this,
01 |
<body>
|
02 |
<div
class="page">
|
03 |
04 |
<div
id="header">
|
05 |
<div
id="title">
|
06 |
</div>
|
07 |
|
08 |
<div
id="logindisplay">
|
09 |
</div>
|
10 |
|
11 |
<div
id="menucontainer">
|
12 |
|
13 |
<ul
id="menu">
|
14 |
<li><a
href="/">Home</a></li>
|
15 |
16 |
</ul>
|
17 |
|
18 |
</div>
|
19 |
</div>
|
20 |
21 |
<div
id="main">
|
22 |
|
23 |
<table>
|
24 |
<tr>
|
25 |
<td
align="left">
|
26 |
<form
action="/Home/SaveFirstLastName"
id="form0"
method="post">
|
27 |
28 |
<table>
|
29 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
30 |
<td
colspan="3"
align="center">
|
31 |
User First and Last Name
|
32 |
</td>
|
33 |
</tr>
|
34 |
<tr>
|
35 |
<td>
|
36 |
First Name
|
37 |
</td>
|
38 |
39 |
<td>
|
40 |
<input
id="FirstName"
name="FirstName"
type="text"
value=""
>
|
41 |
</td>
|
42 |
<td>
|
43 |
<span
class="field-validation-valid"
id="FirstName_validationMessage"></span>
|
44 |
</td>
|
45 |
</tr>
|
46 |
<tr>
|
47 |
<td>
|
48 |
49 |
Last Name
|
50 |
</td>
|
51 |
<td>
|
52 |
<input
id="LastName"
name="LastName"
type="text"
value=""
/>
|
53 |
</td>
|
54 |
<td>
|
55 |
<span
class="field-validation-valid"
id="LastName_validationMessage"></span>
|
56 |
</td>
|
57 |
</tr>
|
58 |
59 |
<tr>
|
60 |
<td
colspan="3"
align="center">
|
61 |
<input
type="submit"
value="Submit"
/>
|
62 |
</td>
|
63 |
</tr>
|
64 |
</table>
|
65 |
</form><script
type="text/javascript">
|
1 |
//<![CDATA[
|
2 |
if
(!window.mvcClientValidationMetadata) {
window.mvcClientValidationMetadata = [];
}
|
3 |
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});
|
4 |
//]]>
|
01 |
</script>
|
02 |
</td>
|
03 |
04 |
</tr>
|
05 |
<tr>
|
06 |
<td
align="left">
|
07 |
<form
action="/Home/SaveEmailCompanyName"
id="form1"
method="post">
|
08 |
<table>
|
09 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
10 |
<td
colspan="3"
align="center">
|
11 |
User Email and Company Name
|
12 |
</td>
|
13 |
14 |
</tr>
|
15 |
<tr>
|
16 |
<td>
|
17 |
Email
|
18 |
</td>
|
19 |
<td>
|
20 |
<input
id="Email"
name="Email"
type="text"
value=""
/>
|
21 |
</td>
|
22 |
<td>
|
23 |
24 |
<span
class="field-validation-valid"
id="Email_validationMessage"></span>
|
25 |
</td>
|
26 |
</tr>
|
27 |
<tr>
|
28 |
<td>
|
29 |
Company Name
|
30 |
</td>
|
31 |
<td>
|
32 |
<input
id="CompanyName"
name="CompanyName"
type="text"
value=""
/>
|
33 |
34 |
</td>
|
35 |
<td>
|
36 |
<span
class="field-validation-valid"
id="CompanyName_validationMessage"></span>
|
37 |
</td>
|
38 |
</tr>
|
39 |
<tr>
|
40 |
<td
colspan="3"
align="center">
|
41 |
<input
type="submit"
value="Submit"
/>
|
42 |
</td>
|
43 |
44 |
</tr>
|
45 |
</table>
|
46 |
</form><script
type="text/javascript">
|
1 |
//<![CDATA[
|
2 |
if
(!window.mvcClientValidationMetadata) {
window.mvcClientValidationMetadata = [];
}
|
3 |
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});
|
4 |
//]]>
|
01 |
</script>
|
02 |
</td>
|
03 |
</tr>
|
04 |
</table>
|
05 |
06 |
07 |
<div
id="footer">
|
08 |
09 |
</div>
|
10 |
</div>
|
11 |
</div>
|
12 |
</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,
01 |
public
class
MoveFormScriptStream : Stream
|
02 |
{
|
03 |
//Other Members are not included for
brevity
|
04 |
05 |
StringBuilder s = new
StringBuilder();
|
06 |
public
override
void
Write(byte[] buffer, int
offset, int
count)
|
07 |
{
|
08 |
string
HTML = Encoding.UTF8.GetString(buffer,
offset, count);
|
09 |
Regex regex = new
Regex(@"</form><script[^>]*>(?<script>([^<]|<[^/])*)</script>", RegexOptions.IgnoreCase |
RegexOptions.Multiline);
|
10 |
HTML = regex.Replace(HTML, m => {
s.Append(m.Groups["script"].Value); return
"</form>"; });
|
11 |
if(s.Length>0)
|
12 |
HTML = HTML.Replace("</body>", "<script type='text/javascript'>"
+ s.ToString() + "</script></body>");
|
13 |
buffer =
System.Text.Encoding.UTF8.GetBytes(HTML);
|
14 |
this.Base.Write(buffer, 0, buffer.Length);
|
15 |
}
|
16 |
}
|
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,
1 |
public
class
MoveFormsScriptAttribute :
ActionFilterAttribute
|
2 |
{
|
3 |
public
override
void
OnActionExecuting(ActionExecutingContext
filterContext)
|
4 |
{
|
5 |
filterContext.HttpContext.Response.Filter = new
MoveFormScriptStream(filterContext.HttpContext.Response.Filter);
|
6 |
}
|
7 |
}
|
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,
01 |
public
class
HomeController : Controller
|
02 |
{
|
03 |
[MoveFormsScript]
|
04 |
public
ActionResult Index()
|
05 |
{
|
06 |
return
View();
|
07 |
}
|
08 |
[HttpPost, MoveFormsScript]
|
09 |
public
ActionResult SaveFirstLastName([Bind(Exclude
= "Email, CompanyName")]UserInformation u)
|
10 |
{
|
11 |
ModelState["Email"].Errors.Clear();
|
12 |
ModelState["CompanyName"].Errors.Clear();
|
13 |
if(!ModelState.IsValid)
|
14 |
return
View("Index");
|
15 |
return
Content("Thanks you for submitting First Name and
Last Name information");
|
16 |
}
|
17 |
[HttpPost, MoveFormsScript]
|
18 |
public
ActionResult
SaveEmailCompanyName([Bind(Exclude = "FirstName, LastName")]UserInformation u)
|
19 |
{
|
20 |
ModelState["FirstName"].Errors.Clear();
|
21 |
ModelState["LastName"].Errors.Clear();
|
22 |
if
(!ModelState.IsValid)
|
23 |
return
View("Index");
|
24 |
return
Content("Thanks you for submitting Email and Company
Name information");
|
25 |
}
|
26 |
}
|
Now run your application again and you will see the difference in the page view source,
001 |
<body>
|
002 |
<div
class="page">
|
003 |
<div
id="header">
|
004 |
<div
id="title">
|
005 |
</div>
|
006 |
<div
id="logindisplay">
|
007 |
</div>
|
008 |
<div
id="menucontainer">
|
009 |
<ul
id="menu">
|
010 |
<li><a
href="/">Home</a></li>
|
011 |
</ul>
|
012 |
</div>
|
013 |
</div>
|
014 |
<div
id="main">
|
015 |
<table>
|
016 |
<tr>
|
017 |
<td
align="left">
|
018 |
<form
action="/Home/SaveFirstLastName"
id="form0"
method="post">
|
019 |
<table>
|
020 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
021 |
<td
colspan="3"
align="center">
|
022 |
User First and Last Name
|
023 |
</td>
|
024 |
</tr>
|
025 |
<tr>
|
026 |
<td>
|
027 |
First Name
|
028 |
</td>
|
029 |
<td>
|
030 |
<input
id="FirstName"
name="FirstName"
type="text"
value=""
/>
|
031 |
</td>
|
032 |
<td>
|
033 |
<span
class="field-validation-valid"
id="FirstName_validationMessage"></span>
|
034 |
</td>
|
035 |
</tr>
|
036 |
<tr>
|
037 |
<td>
|
038 |
Last Name
|
039 |
</td>
|
040 |
<td>
|
041 |
<input
id="LastName"
name="LastName"
type="text"
value=""
/>
|
042 |
</td>
|
043 |
<td>
|
044 |
<span
class="field-validation-valid"
id="LastName_validationMessage"></span>
|
045 |
</td>
|
046 |
</tr>
|
047 |
<tr>
|
048 |
<td
colspan="3"
align="center">
|
049 |
<input
type="submit"
value="Submit"
/>
|
050 |
</td>
|
051 |
</tr>
|
052 |
</table>
|
053 |
</form>
|
054 |
</td>
|
055 |
</tr>
|
056 |
<tr>
|
057 |
<td
align="left">
|
058 |
<form
action="/Home/SaveEmailCompanyName"
id="form1"
method="post">
|
059 |
<table>
|
060 |
<tr
style="background-color: #E8EEF4; font-weight:
bold">
|
061 |
<td
colspan="3"
align="center">
|
062 |
User Email and Company Name
|
063 |
</td>
|
064 |
</tr>
|
065 |
<tr>
|
066 |
<td>
|
067 |
Email
|
068 |
</td>
|
069 |
<td>
|
070 |
<input
id="Email"
name="Email"
type="text"
value=""
/>
|
071 |
</td>
|
072 |
<td>
|
073 |
<span
class="field-validation-valid"
id="Email_validationMessage"></span>
|
074 |
</td>
|
075 |
</tr>
|
076 |
<tr>
|
077 |
<td>
|
078 |
Company Name
|
079 |
</td>
|
080 |
<td>
|
081 |
<input
id="CompanyName"
name="CompanyName"
type="text"
value=""
/>
|
082 |
</td>
|
083 |
<td>
|
084 |
<span
class="field-validation-valid"
id="CompanyName_validationMessage"></span>
|
085 |
</td>
|
086 |
</tr>
|
087 |
<tr>
|
088 |
<td
colspan="3"
align="center">
|
089 |
<input
type="submit"
value="Submit"
/>
|
090 |
</td>
|
091 |
</tr>
|
092 |
</table>
|
093 |
</form>
|
094 |
</td>
|
095 |
</tr>
|
096 |
</table>
|
097 |
<div
id="footer">
|
098 |
</div>
|
099 |
</div>
|
100 |
</div>
|
101 |
<script
type='text/javascript'>
|
1 |
//<![CDATA[
|
2 |
if
(!window.mvcClientValidationMetadata) {
window.mvcClientValidationMetadata = [];
}
|
3 |
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
});
|
4 |
//]]>
|
5 |
6 |
//<![CDATA[
|
7 |
if
(!window.mvcClientValidationMetadata) {
window.mvcClientValidationMetadata = [];
}
|
8 |
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
});
|
9 |
//]]>
|
1 |
</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.