ASP.NET Framework MVC 2 Dynamic Validation

Introduction

ASP.NET MVC 2 supports server-side as well as client-side validation, out of the box. This validation is based on the DataAnnotations attributes, which can be easily implemented by decorating the model properties with the validation attributes. ASP.NET MVC 2 will automatically enforce the validation rules, which also helps in maintaining all the validation rules in once place. However, this DataAnnotations based validation provides very limited support for the dynamic validation. Here I will cover how we can extend the out of the box validation to support dynamic validation.

Let's walkthrough a simple scenario that takes advantage of the ASP.NET MVC 2 Validation. Create a new ASP.NET MVC 2 web application and name it as Mvc2SampleApp. Add a model to the project and name it as Interview as shown below:

public class Interview
        public Interview() 
        {
            BillingAddressList = new List<SelectListItem>()
                                {
                                    new SelectListItem(){ Text = "Select ...", Value = "Select"},
                                    new SelectListItem(){ Text = "Mailing", Value = "Mailing"},
                                    new SelectListItem(){ Text = "Property", Value = "Property"}
                                };
        }
        [Required(ErrorMessage = "Name is required")]
        public string Name { get; set; }
        public Address MailingAddress { get; set; }
        public Address PropertyAddress { get; set; }
        [Required(ErrorMessage = "Billing Address Type is required")]
        public string BillingAddress { get; set; }
        public List<SelectListItem> BillingAddressList { get; set; }
        [Required(ErrorMessage = "Grace is required")]
        public string Grace { get; set; }
        [Required(ErrorMessage = "HouseType is required")]
        public string HouseType { get; set; }
    }
    public class Address
    {
        [Required(ErrorMessage = "Street is required")]
        public string Street { get; set; }
        public string Street2 { get; set; }
        [Required(ErrorMessage = "City is required")]
        public string City { get; set; }    
        [Required(ErrorMessage = "State is required")]
        [StringLength(2, ErrorMessage = "Please enter the valid State code")]
        public string State { get; set; }
        public string County { get; set; }
        [Required(ErrorMessage = "Zip is required")]
        [StringLength(5, ErrorMessage = "Please enter the valid Zip code")]
        public string Zip { get; set; }
    }
ASP.NET MVC 2 currently supports below DataAnnotations attributes for validation:
[Required]
[StringLength]
[Range]
[RegularExpression]
Now let's add a controller to the project and name it as InterviewController.
public class InterviewController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(new Interview());
        }
        [HttpPost]
        public ActionResult Index(Interview interview)
        {
            if (!ModelState.IsValid)
            {
                return View(interview);
            }
            return Content("Interview saved successfully!");
        }
    }

Now add the strongly typed view for the model. This view will show the Property/Mailing address fields based on the selected value in the Billing Address dropdown. The final code of the view should look like below:

  <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Mvc2SampleApp.Models.Interview>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <script type ="text/javascript" src="../../Scripts/jquery-1.4.1.js"></script> 
    <script type ="text/javascript" src="../../Scripts/MicrosoftAjax.js"></script> 
    <script type ="text/javascript" src="../../Scripts/MicrosoftMvcAjax.js"></script> 
    <script type ="text/javascript" src="../../Scripts/MicrosoftMvcValidation.js"></script> 
    <script type="text/javascript">
        $(document).ready(function() {
            $('#dMailingAddress').children().hide();
            $('#dPropertyAddress').children().hide();
            $('#Grace').attr('disabled', true);
            $('input[id^=MailingAddress]').attr('disabled', true);
            $('#HouseType').attr('disabled', true);
            $('input[id^=PropertyAddress]').attr('disabled', true);
            $("#BillingAddress").bind('change', function() {
                var selectedBillingAddress = $("#BillingAddress option:selected").text();
                if (selectedBillingAddress == 'Mailing') {
                    $('#dMailingAddress').children().show();
                    $('#dPropertyAddress').children().hide();
                    $('#Grace').removeAttr('disabled');
                    $('input[id^=MailingAddress]').removeAttr('disabled');
                    $('#HouseType').attr('disabled', true);
                    $('input[id^=PropertyAddress]').attr('disabled', true);
                }
                else if (selectedBillingAddress == 'Property') {
                    $('#dPropertyAddress').children().show();
                    $('#dMailingAddress').children().hide();
                    $('#Grace').attr('disabled', true);
                    $('input[id^=MailingAddress]').attr('disabled', true);
                    $('#HouseType').removeAttr('disabled');
                    $('input[id^=PropertyAddress]').removeAttr('disabled');
                }
            })
        })
    </script> 
    <h2>Index</h2>
        <% Html.BeginForm(); %>
    <p>
    <label for="Name" id="NameLabel">Name:</label>
    <%= Html.TextBoxFor(model => model.Name) %>
    <%= Html.ValidationMessageFor(model => model.Name) %>
    </p>
    <p>
    <label for="BillingAddress" id="BillingAddressLabel">Billing Address:</label>
    <%= Html.DropDownListFor(model => model.BillingAddress, Model.BillingAddressList) %>
    <%= Html.ValidationMessageFor(model => model.BillingAddress) %>
    </p> 
    <div id = "dMailingAddress">
    <p>
    <label for="Grace" id="GraceLabel">Grace:</label>
    <%= Html.TextBoxFor(model => model.Grace)%>
    <%= Html.ValidationMessageFor(model => model.Grace)%>
    </p>  
    <p>
    <label for="MailingAddress" id="MailingAddressLabel">Mailing Address:</label>
    <br />
    <br />
    <label for="MailingAddress_Street" id="MailingAddress_StreetLabel">Street:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.Street)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.Street)%>
    </p>  
    <p>
    <label for="MailingAddress_Street2" id="MailingAddress_Street2Label">Street2:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.Street2)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.Street2)%>
    </p>  
    <p>
    <label for="MailingAddress_City" id="MailingAddress_CityLabel">City:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.City)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.City)%>
    </p>    
    <p>
    <label for="MailingAddress_State" id="MailingAddress_StateLabel">State:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.State)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.State)%>
    </p>    
    <p>
    <label for="MailingAddress_County" id="MailingAddress_CountyLabel">County:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.County)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.County)%>
    </p>    
    <p>
    <label for="MailingAddress_Zip" id="MailingAddress_ZipLabel">Zip:</label>
    <%= Html.TextBoxFor(model => model.MailingAddress.Zip)%>
    <%= Html.ValidationMessageFor(model => model.MailingAddress.Zip)%>
    </p>    
    </div>
    <div id = "dPropertyAddress">
    <p>
    <label for="HouseType" id="HouseTypeLabel">House Type:</label>
    <%= Html.TextBoxFor(model => model.HouseType)%>
    <%= Html.ValidationMessageFor(model => model.HouseType)%>
    </p>    
    <p>
    <label for="PropertyAddress" id="PropertyAddressLabel">Property Address:</label>
    <br />
    <br />
    <label for="PropertyAddress_Street" id="PropertyAddress_StreetLabel">Street:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.Street)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.Street)%>
    </p>   
    <p>    
    <label for="PropertyAddress_Street2" id="PropertyAddress_Street2Label">Street2:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.Street2)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.Street2)%>
    </p>    
    <p>
    <label for="PropertyAddress_City" id="PropertyAddress_CityLabel">City:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.City)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.City)%>
    </p>    
    <p>
    <label for="PropertyAddress_State" id="PropertyAddress_StateLabel">State:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.State)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.State)%>
    </p>   
    <p>
    <label for="PropertyAddress_County" id="PropertyAddress_CountyLabel">County:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.County)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.County)%>
    </p>    
    <p>
    <label for="PropertyAddress_Zip" id="PropertyAddress_ZipLabel">Zip:</label>
    <%= Html.TextBoxFor(model => model.PropertyAddress.Zip)%>
    <%= Html.ValidationMessageFor(model => model.PropertyAddress.Zip)%>
    </p>
    </div>
    <input type="submit" value = "Save Interview" />
    <% Html.EndForm(); %>
</asp:Content>

Now set the InterviewController as the default controller and run the application. In the view choose Mailing in the Billing Address dropdown, which will make all the Mailing Address related fields visible. Fill all the required fields and click on the Save Interview button. If you put the breakpoint in the post Action method, you will see that validation fails on the server side and view is returned back to the user. This happens because ASP.NET MVC is validating all the fields/Model properties, which includes the fields that were not even shown to the user. In this case, the validation on the server side failed because we haven't entered values in the Property Address related fields, which were not shown on the UI.

Now let's extend this model so that it validates only those fields that are applicable in a particular scenario, both on server side as well as on client side. On the client side we are already disabling the hidden fields, in the action filter we will clear out errors of the fields that were not posted to the server.

Add a new Action Filter to the project and override its OnActionExecuting method. In the OnActionExecuting method, clear out all errors of those fields that were not posted by the client.

  public class DynamicValidationAttribute : System.Web.Mvc.ActionFilterAttribute   
 {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var modelState = filterContext.Controller.ViewData.ModelState;
            var valueProvider = filterContext.Controller.ValueProvider;
            var keysWithNoIncomingValue = modelState.Keys.Where(x => !valueProvider.ContainsPrefix(x));
            foreach (var key in keysWithNoIncomingValue)
                modelState[key].Errors.Clear();
        }
    }
Now add this Action Filter to the post Index method of the InterviewController as shown below:
        [HttpPost]
        [DynamicValidationAttribute]
        public ActionResult Index(Interview interview)
        {
	…
         }

Now run the application again, choose Mailing as the Billing Address, fill all the required fields and click on the Save Interview button. Now validation on the server side passes as it validates only the posted fields. So far, so good. We implemented the dynamic validation on the server side but what about the client side validation. To enable the client side validation, add the below statement in the view just above the form.

  …
<% Html.EnableClientValidation(); %>    
<% Html.BeginForm(); %>
...

If you run the application again, you will observe that the validation fails on the client side; even form is not getting posted to the server. Like server side, client side validation is also validating all the fields in the form including hidden/disabled fields, due to which client side validation is failing. Now there are two ways to solve this problem.

  • A) Override the validate function of the FormContext defined in MicrosoftMvcValidation.js/MicrosoftMvcValidation.debug.js files, in our own JavaScript file and validate only those fields that are enabled.
  • B) Microsoft has written all its JavaScript code in C# programming and generated the JavaScript out of it using Script# tool. So we can get the source code of ASP.NET MVC Framework and modify the Validate method of FormContext class that is written in C# and rebuild the project, which will generate updated MicrosoftMvcValidation.js/MicrosoftMvcValidation.debug.js files with dynamic validation built-in.

If you follow the approach A), it can be easily implemented by including our own JavaScript file in the project but if you use both MicrosoftMvcValidation.js and MicrosoftMvcValidation.debug.js files, then we will have to maintain two JavaScript files separately for each of them and maintain MicrosoftMvcValidation.js file may be a bit hard due to its cryptic nature.

Following approach b) makes the maintenance very easy as we are directly dealing with the C# code but there is an additional overhead of compiling the C# project and adding version control for the same. We will cover both of these approaches here.

Solution A)

If you open the MicrosoftMvcValidation.js file you will see code block assigned to Sys.Mvc.FormContext.prototype, copy this code block to a new JavaScript file and name it DynamicValidation.js. In this block there is a validate function, which validates all the fields in for loop. Add a condition in this loop that will ensure only enabled fields are validated. Updated code of the validate function is shown below.

validate: function(eventName) {
        var $0 = this.fields;
        var $1 = [];
        for (var $2 = 0; $2 < $0.length; $2++) {
            var $3 = $0[$2];
            var fName = '#' + ($3.elements[0].name.replace(".", "_"));
            if ($(fName).is(':disabled') == false) {
                var $4 = $3.validate(eventName);
                if ($4) {
                    Array.addRange($1, $4);
                }
            }
        }
        if (this.replaceValidationSummary) {
            this.clearErrors();
            this.addErrors($1);
        }
        return $1;
    } 

Similarly in MicrosoftMvcValidation.debug.js file there is a code block assigned to Sys.Mvc.FormContext.prototype. Copy this block to a new JavaScript file and name it is as DynamicValidation.debug.js. Add a condition in the validate function to validate only enabled fields.

validate: function Sys_Mvc_FormContext$validate(eventName) {
        /// <param name="eventName" type="String">
        /// </param>
        /// <returns type="Array" elementType="String"></returns>
        var fields = this.fields;
        var errors = [];
        for (var i = 0; i < fields.length; i++) {
            var field = fields[i];
            var fName = '#' + (field.elements[0].name.replace(".", "_"));
            if ($(fName).is(':disabled') == false) {
                var thisErrors = field.validate(eventName);
                if (thisErrors) {
                    Array.addRange(errors, thisErrors);
                }
            }
        }
        if (this.replaceValidationSummary) {
            this.clearErrors();
            this.addErrors(errors);
        }
        return errors;
    }

Now add this new JavaScript file to the view after MicrosoftMvcValidation.js/MicrosoftMvcValidation.debug.js file. If you are using the debug files then it should look like below:

<script type ="text/javascript" src="../../Scripts/ MicrosoftMvcValidation.debug.js"></script> 
<script type ="text/javascript" src="../../Scripts/DynamicValidation.debug.js"></script>

In case you are using release files then script tags should be as below:

<script type ="text/javascript" src="../../Scripts/ MicrosoftMvcValidation.js"></script>
<script type ="text/javascript" src="../../Scripts/ DynamicValidation.js"></script>

Now if run the application it will validate only displayed/enabled fields both on client and server side.

Solution B)

Download the ASP.NET MVC 2 source code from the Microsoft web site and open the solution in Microsoft Visual Studio 2008. Open the FormContext class of MicrosoftMvcValidationScript project and add a new condition to check whether field is enabled or disabled.

public string[] Validate(string eventName) {
            FieldContext[] fields = Fields;
            ArrayList errors = new ArrayList();
            for (int i = 0; i < fields.Length; i++) {
                FieldContext field = fields[i];               
                if (!field.Elements[0].Disabled)
                {
                    string[] thisErrors = field.Validate(eventName);
                    if (thisErrors != null)
                    {
                        ArrayList.AddRange(errors, thisErrors);
                    }
                }
            }
            if (ReplaceValidationSummary) {
                ClearErrors();
                AddErrors((string[])errors);
            }
            return (string[])errors;
        }

Build this project, which will also generate MicrosoftMvcValidation.js/MicrosoftMvcValidation.debug.js files. Copy the updated files from the bin folder into your project. Newly generated *.js files will have the conditional validation implemented in them. Run the project and everything should work as expected.

Happy Validating!



About the Author

Sajad Deyargaroo

Sajad, MCTS, MCP, started his career developing applications in VB 4.0 and C++. His interest in programming has spanned many languages but is now focused on .Net. Nowadays, he works with AIS on Microsoft technologies and has published many articles in several magazines and web. You can send an email to sajad@programmer.net.

Comments

  • Cheap Oakley Pit Boss wholesale store

    Posted by hhgwsdlnb on 07/09/2013 05:55am

    Best Oakley Sunglasses ,A product men and women love can be a case of pride that they are inherited the spirit in the Oakley fashion, as Oakley. Oakley sunglasses women's fashion, they've already a number of methods of patterns. They offer a large variety of each population. The character with the movement of Oakley sunglasses, however the concept has become intense promotion within the last few many years, Oakley sunglasses bear and also a fashion as well as a real statement manufacturers. Fake Oakley Radar ,The short wavelength light barrier, Oakley sunglasses along with protective effects within the lenses of sunglasses red lines are a lesser amount than the opposite four colors. Sunshine generally is a guide, you will observe a completely different party and found to think you can not do on the actual cover, accurate sun. Sunglasses maintenance along with the repair of ordinary glasses is identical, general, regular cleaning, proper storage should produce a good habit. Sunglasses, cleaning fluid is incredibly particular, need to know will it be seriously isn't correct make use of paper or fingernails, because it's easily scratched sunglasses products. Fake Oakleys ,Your livelihood to present you distress, but additionally long-term warm and pain on the facial skin, especially fluffy, and affect skin round the entire eye is very sensitive. Additional Oakley sunscreen to work with for medical use. Solutions of the UV radiation over the skin of psoriasis patients PUVA remedy within a timeframe is well aware of the ultraviolet radiation. Fake Ray Ban ,In short, not during the summer time of Oakley sun screen lotion, all-around noon, for the Four Seasons, the encircling sea or sailing or at thin air snowboarding. That summertime is coming, it is usually a great idea, trying to find the countless outdoor activities, protection, and cheap Oakley sunglasses. Sac En Bandoulière Longchamp ,From too much eye-catching glasses, the face of history couple of years, regardless of runway in Europe or perhaps the adjacent office cubicle, it is clear, glasses are becoming a vital chic accessories. Anyone who would like to put a fashion statement will choose Oakley brand, whenever a woman bought a pair of Oakley sunglasses, Ms. She is actually a smart investment. It doesn't matter what you desire, keep in mind when you shop online, resulting inside your best purchase decisions, to generate any difference what you look for to get sunglasses or perhaps the exact place.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds