MVC Custom Validation

Introduction

In this post we will discuss custom validation in MVC using data annotation. In addition, instead of hard coding error messages we will get custom error messages from a XML file. This XML file will support multilingual and multi geographical messages.

There are scenarios when data validations provided by MVC are not sufficient to meet the requirements and we need custom validation messages, like to show custom error messages based on language and geography.

To showcase the code base, we will utilize default MVC web application provided by Visual Studio 2013 and will add code required to customize validation in registration page available in that.

Create basic MVC default application

Open Visual Studio 2013 IDE, and create a new project as shown below:

Select ASP.NET Web Application:

Select MVC as template and click on OK, no need to change any other option.

This will give you a readily available small application which we can use as a base to add our code. I have used a registration page for creating custom validation.

Using the code

Now create two custom validators, one for custom required validation, and another for custom special char not allowed validation.

Below are the new class files we will be creating to achieve this.

Create a new folder “Helpers” inside existing Models folder, we will use this folder to keep all new class files we will create.

Add a class RequiredValidation. All of the validation annotations like Required, EmailAddress are ultimately derive from the ValidationAttributebase class. So we will also derive RequiredValidation class also from ValidationAttributebase.

Here IsValid function, which is provided by the base class, is overridden to create a custom Validation. It take two parameters, first one is ValidationContext which will give display name of the property to be validated and second one is value to be validated.

Here in this validation we are checking whether value is Null or empty, if yes it returns a custom message. This custom message in this example case of "Display Name of the control - <message from resource file>".

To get the custom message, a function GetResourceString is used, which is explained at the end, it took four parameter Resource file path, resource key, language and state.

ValidationResourceFile in below code contain path of the XML file where all error messages will be added. Structure of this XML file and the function GetResourceString are explained at the end.

Hide Copy Code
  1. namespace WebAppCustomValidation.Models.Helpers   
  2. {  
  3.     public class RequiredValidationAttribute: ValidationAttribute  
  4.     {  
  5.         public static string ValidationResourceFile = "/App_Data/ValidationMessages.xml";  
  6.         public RequiredValidationAttribute(): base("{0}") {}  
  7.         protected override ValidationResult IsValid(object value, ValidationContext validationContext) {  
  8.             if (value == null || value.ToString().Trim() == "")  
  9.             {  
  10.                 return new ValidationResult(FormatErrorMessage(validationContext.DisplayName) + " - " + Models.Helpers.ValidationHelper.GetResourceString(ValidationResourceFile, "Required""EN""XY"));  
  11.             }  
  12.             return ValidationResult.Success;  
  13.         }  
  14.     }  
  15. }  
Similarly for not allowing some specific special character, create a new class CharNotAllowed. Here characters not allowed will be passed with data annotation itself and will be taken in _invalidchars as shown below.

ValidationResourceFile as above contain path of the error XMl file. (This file path I am passing to the function in all validation rule, if this is one file only throughout the project you can keep it in Validation helper also).

Here the value, coming from the control, is checked for any special character set as not allowed. If any such character is found in the value a custom error text return as error message. This custom error message as above is fetched from error XML file based on key, language and state. (GetResourceFunction has been used for this purpose, explained later)

Hide Shrink Copy Code
  1. namespace WebAppCustomValidation.Models   
  2. {  
  3.     public class CharNotAllowedAttribute: ValidationAttribute  
  4.     {  
  5.         public static string ValidationResourceFile = "/App_Data/ValidationMessages.xml";  
  6.         private readonly string _invalidChars;  
  7.         public CharNotAllowedAttribute(string invalidChars): base("{0}")  
  8.         {  
  9.             _invalidChars = invalidChars;  
  10.         }  
  11.         protected override ValidationResult IsValid(object value, ValidationContext validationContext) {  
  12.             if (value != null) {  
  13.                 for (int i = 0; i < _invalidChars.Length; i++)  
  14.                 {  
  15.                     if (value.ToString().Contains(_invalidChars[i]))  
  16.                     {  
  17.                         return new ValidationResult(FormatErrorMessage(validationContext.DisplayName) + " - " + Models.Helpers.ValidationHelper.GetResourceString(ValidationResourceFile, "Invalid_Chars""EN""XY"));  
  18.                     }  
  19.                 }  
  20.             }  
  21.             return ValidationResult.Success;  
  22.         }  
  23.     }  
  24. }  
Now create another class ValidationHelper.cs. This will be used to get error message against a key, based on language and state provided.

Before adding this class let’s have a look in to the error XML file, we have used in this code example. Please refer the following XML sample, here expression tag contain all conditions we need to check, in our case we are using language and state, if require we can add more and further customize messages.

Any ValidationMessage can have multiple TargetRules for different keys. Function GetResourceString fetch the required <Message> from the following XML based on language and state.

Hide Shrink Copy Code
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ArrayOfMessages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
  3.     <ValidationMessage ID="Invalid_Chars">  
  4.         <TargetableRules>  
  5.             <TargetableRule>  
  6.                 <Expression>  
  7.                     <lang>EN</lang>  
  8.                     <state>AB,CD</state>  
  9.                 </Expression>  
  10.                 <Message>Invalid charactors found! (AB,CD)</Message>  
  11.             </TargetableRule>  
  12.             <TargetableRule>  
  13.                 <Expression>  
  14.                     <lang>EN</lang>  
  15.                     <state>BC,DF,XY</state>  
  16.                 </Expression>  
  17.                 <Message>Invalid charactors found! (BC,DF,XY)</Message>  
  18.             </TargetableRule>  
  19.             <TargetableRule>  
  20.                 <Expression>  
  21.                     <lang>FR</lang>  
  22.                     <state>AB,CD,BC</state>  
  23.                 </Expression>  
  24.                 <Message>Invalid charactors found! [fr] (AB,CD,BC)</Message>  
  25.             </TargetableRule>  
  26.         </TargetableRules>  
  27.     </ValidationMessage>  
  28.     <ValidationMessage ID="Required">  
  29.         <TargetableRules>  
  30.             <TargetableRule>  
  31.                 <Expression>  
  32.                     <lang>EN</lang>  
  33.                     <state>AB,DF,XY</state>  
  34.                 </Expression>  
  35.                 <Message>Field required! (AB,DF,XY)</Message>  
  36.             </TargetableRule>  
  37.             <TargetableRule>  
  38.                 <Expression>  
  39.                     <lang>FR</lang>  
  40.                     <state>AB,CD</state>  
  41.                 </Expression>  
  42.                 <Message>Invalid charactors found! [fr] (AB,CD)</Message>  
  43.             </TargetableRule>  
  44.         </TargetableRules>  
  45.     </ValidationMessage>  
  46. </ArrayOfMessages>  
Now add function GetResourceString, inside ValidationHelper class.

Load XML from the path in xDocument, and through a LINQ query fetch ValidationMessage node for given key. Next, through another LINQ query get the element where language and state are matching.

This will provide the node filtered for given key, language and state. Fetch value of Message element and convert that in a string and return.

Hide
Copy Code

  1. namespace WebAppCustomValidation.Models.Helpers  
  2. {  
  3.     public class ValidationHelper  
  4.     {  
  5.         public static string GetResourceString(string resourceFile, string resourceKey, string lang, string state) {  
  6.             string retValue = string.Empty;  
  7.             XDocument xDoc = XDocument.Load(resourceFile);  
  8.             var MessageResource = from m in xDoc.Descendants("ValidationMessage").Where(r => (string) r.Attribute("ID") == resourceKey) select m;  
  9.             var Msg = from m in MessageResource.Descendants("Expression").Where(r => (string) r.Element("lang").Value == lang && (bool) r.Element("state").Value.Contains(state) == true) select m.Parent;  
  10.             foreach(XElement element in Msg.Descendants("Message"))  
  11.             {  
  12.                 retValue = element.Value.ToString();  
  13.             }  
  14.             return retValue;  
  15.         }  
  16.     }  
  17. }  
Use new validation methods, as annotation

Hide Copy Code
  1. public class RegisterViewModel  
  2. {  
  3.     [RequiredValidation][EmailAddress][Display(Name = "Email")] public string Email   
  4.     {  
  5.         get;  
  6.         set;  
  7.     }[RequiredValidation][CharNotAllowed("@#")][StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)][DataType(DataType.Password)][Display(Name = "Password")] public string Password 
  8.    {  
  9.         get;  
  10.         set;  
  11.     }[DataType(DataType.Password)][Display(Name = "Confirm password")][Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword {  
  12.         get;  
  13.         set;  
  14.     }  
  15. }