Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Creating Custom Validation Attribute in MVC 3

4.88/5 (14 votes)
16 Dec 2011CPOL5 min read 177.9K   2.8K  
This article explains how to create custom validation attribute in MVC 3 for validating data on server side as well as on client side.

Introduction

This article will demonstrate how to create custom validation attribute in MVC 3 application. There are lots of validation attributes available in MVC 3 but sometimes we may require specific type of validation which is dependent on some other properties as well. In this article, I will explain how you can create your own custom validation attribute and also how to make it work on client side as well. In MVC application for validating the data, you just need to add an attribute in the properties specified in modelclass, and for invoking the validations while updating the information, you just need to call “TryUpdateModel” method or check “ModelState.IsValid” property. That’s all, but it will validate data on server side what one should do to perform validations on client side? One should add validation controls in view and add reference to the validation jquery files “ will validate data on server side what one should do to perform validations onclient side? One should add validation controls in view and add reference to the validation jquery files “jquery.validate.min.js” and “jquery.validate.unobtrusive.min.js” in the view where you are using validations.

Creating custom validations in MVC 3 application is a 4 step process:

  1. Create a class inheriting “ValidationAttribute” and implementing “IClientValidatable” interface. “IClientValidatable” interface implementations is required if you want to enable your validation at client side.
  2. Override “IsValid” method and add your custom validation logic there.
  3. Implement “GetClientValidationRules” method. You can add custom parameters in this method that you require on client side for validation.
  4. Create a JavaScript file and register your client methods here using “jQuery.validator.unobtrusive.add” and “jQuery.validator.addMethod” methods here using “jQuery.validator.unobtrusive.add” and “jQuery.validator.addMethod”. We will look into it in detail when I will give detailed explanation of the code attached in the coming section.

Background

While creating a sample MVC application, I come across a situation where I need validation on a set of checkboxes. Validation I need was simple that at least one checkbox from the set must be checked. Though this can be done using simple script and invoking it on button click, I thought why not write a custom validation attribute for it. The reason behind this thought was in doing so I will be going to learn a new feature, code will remain neat, and code will be reusable.

The Code

Create a MVC 3 application with Razor view engine (You can use aspx view engine also as per your comfort). For creating MVC 3 application with Visual Studio, you will need to install service pack 1 for Visual Studio and MVC3 wpi which you can download from Microsoft’s site. In this application, I have created a EmployeeModel with some properties and a create method, a EmployeeController which contains 2 actions, a view which contains controls to display and accept input values for employee properties. For validation, I have created a separate folder named “Validations” and added a file “MvcCustomValidation.cs” in it. Now let's check what we should write in “MvcCustomValidation.cs” file to make our custom validator work.

Following is the code written in “MvcCustomValidation.cs” file:

C#
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Collections.Generic;
namespace MvcCustomValidation.Validations
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
    public class MyCustomValidation : ValidationAttribute, IClientValidatable
    {
        public string CommaSeperatedProperties { get; private set; }
        public MyCustomValidation(string commaSeperatedProperties)
            : base("Please select an option.")
        {
            if (!string.IsNullOrEmpty(commaSeperatedProperties))
                CommaSeperatedProperties = commaSeperatedProperties;
        }
        protected override ValidationResult IsValid
        (object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                string[] strProperties = 
        CommaSeperatedProperties.Split(new char[] { ',' });
                bool bIsAnyChecked = false;
                if (Convert.ToBoolean(value))
                {
                    bIsAnyChecked = true;
                }
                else
                {
                    foreach (string strProperty in strProperties)
                    {
                        var curProperty = validationContext.ObjectInstance.GetType().
                    GetProperty(strProperty);
                        var curPropertyValue = curProperty.GetValue
                (validationContext.ObjectInstance, null);
                        if (Convert.ToBoolean(curPropertyValue))
                        {
                            bIsAnyChecked = true;
                            break;
                        }
                    }
                }
                if (!bIsAnyChecked)
                    return new ValidationResult("Please select an option.");
            }
            return ValidationResult.Success;
        }
        public IEnumerable<ModelClientValidationRule> 
        GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationSelectOneRule
            (FormatErrorMessage(metadata.DisplayName), 
        CommaSeperatedProperties.Split(new char[] { ',' })) };
        }
        public class ModelClientValidationSelectOneRule : ModelClientValidationRule
        {
            public ModelClientValidationSelectOneRule
        (string errorMessage, string[] strProperties)
            {
                ErrorMessage = errorMessage;
                ValidationType = "mycustomvalidation";
                for (int intIndex = 0; intIndex < strProperties.Length; intIndex++)
                {
                    ValidationParameters.Add("otherproperty" + 
                    intIndex.ToString(), strProperties[intIndex]);
                }
            }
        }
    }
} 

Above, I have created a class which is inherited from ValidationAttribute class and implement IClientValidatable interface for this class. I want to access some other properties while validating value for this property, so I have passed those property names in the constructor as comma separated string. These property names are used for retrieving the property values in IsValid method where I have used these property names to retrieve the property values from ValidationContext object parameter. Here I have implemented my custom validation logic where I have checked the value of the property on which validation is to be performed and the properties passed as parameter which also needs to be checked for validation.

After implementing IsValid method, I have implemented GetClientValidationRules method, this will be required for clientside validation. In this method, I have set some properties like ErrorMessage, ValidationType, ValidationParameters. In ValidationParameters, I am adding property names with key as otherproperty0, otherproperty1 and so on. Later, when we will register methods in jquery, the same name must be used.

Now we are done with ValidationAttribute coding. Let's move on to JQuery, so that we can enable validation at client side too. Look at JQuery file which I have added in Script folder "MyCustomValidation.js":

JavaScript
jQuery.validator.unobtrusive.adapters.add
    ("mycustomvalidation", ['otherproperty0', 'otherproperty1'],
    function (options) {
        options.rules['mycustomvalidation'] = { other: options.params.other,
            otherproperty0: options.params.otherproperty0,
            otherproperty1: options.params.otherproperty1
        };
        options.messages['mycustomvalidation'] = options.message;
    }
);
jQuery.validator.addMethod("mycustomvalidation", function (value, element, params) {
    var retVal = false;
    if ($(element)) {
        retVal = $(element).attr("checked");
    }
    if (retVal == true) {
        return retVal;
    } 
    if (params.otherproperty0) {
        if ($('#' + params.otherproperty0)) {
            retVal = $('#' + params.otherproperty0).attr("checked");
        }
    }
    if (retVal == true) {
        return retVal;
    }
    if (params.otherproperty1) {
        if ($('#' + params.otherproperty1)) {
            retVal = $('#' + params.otherproperty1).attr("checked");
        }
    }
    if (retVal == true) {
        return retVal;
    }
    return false;
}); 

In "MyCustomValidation.js" file, we have just registered 2 methods. One method we have added in "jQuery.validator.unobtrusive.adapters". Here we have validation name and other property names. One thing to be noted here is the other property names must be the same as we have specified at server side while implementing GetClientValidationRule method. We will set message property and validation rule for our custom validation in this method. The second method we will add to the validator "jQuery.validator.addMethod" in this method we will perform our custom validation logic by getting other properties from the params. Yiipii!!!!!!!! We are done with the validation attribute on both server side and client side, now let's see how we are going to use it. Check the EmployeeModel.cs file in Model folder.

C#
using System.Web.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcCustomValidation.Validations;
namespace MvcCustomValidation.Models
{
    public class EmployeeModel
    {
        [Required]
        [StringLength(50)]
        [Display(Name = "Employee Name")]
        public string EmpName { get; set; }
        [Required]
        [StringLength(150)]
        [Display(Name = "Email Id")]
        public string Email { get; set; }
        [Required]
        [StringLength(15)]
        [Display(Name = "Contact No.")]
        public string Number { get; set; }
        [Required]
        [Range(18, 150)]
        public int Age { get; set; }
        [Display(Name = "Email")]
        public bool IsEmail { get; set; }
        [Display(Name = "SMS")]
        [MyCustomValidation("IsEmail,IsAlert")]
        public bool IsSMS { get; set; }
        [Display(Name = "Alert")]
        public bool IsAlert { get; set; }
    }
    public class Employee
    {
        public Employee()
        {
           
        }
        public List<EmployeeModel> _empList = new List<EmployeeModel>();
        public void Create(EmployeeModel umToUpdate)
        {
            foreach (EmployeeModel um in _empList)
            {
                if (um.EmpName == umToUpdate.EmpName)
                {
                    throw new System.InvalidOperationException
            ("Duplicate username: " + um.EmpName);
                }
            }
            _empList.Add(umToUpdate);
        }
    }
} 

Look at the properties in class EmployeeModel. Here I have a requirement that properties IsEmail, IsAlert, and IsSMS will be displayed as checkbox against a single caption Notification mode, and if user did not select any of the checkboxes on UI, system should have validation message. I have added our custom validation attribute on IsSMS property and passed name of the properties "IsAlert", and "IsSMS" as comma separated strings to it. In model class, we just need to add our custom attribute to the specific property and pass required parameters to it.

Now look at the view. Location of our view is "Views/Employees/Create.cshtml". Following is the HTML code we will need to write in view.

HTML
@model MvcCustomValidation.Models.EmployeeModel
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript">
</script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" 
    type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/MyCustomValidation.js")" 
    type="text/javascript"></script>
@using (Html.BeginForm())
{    
    <fieldset>
        <legend>EmployeeModel</legend>
        <table style="width: 100%; padding: 1; border: 0;">
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.EmpName)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.EmpName)
                    @Html.ValidationMessageFor(model => model.EmpName)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Email)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Email)
                    @Html.ValidationMessageFor(model => model.Email)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Number)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Number)
                    @Html.ValidationMessageFor(model => model.Number)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Age)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Age)
                    @Html.ValidationMessageFor(model => model.Age)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.Label("Notification Mode/s")
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.CheckBoxFor(model => model.IsAlert) 
            @Html.LabelFor(model => model.IsAlert) @Html.Raw("     ")
                    @Html.CheckBoxFor(model => model.IsEmail) 
            @Html.LabelFor(model => model.IsEmail) @Html.Raw("     ")
                    @Html.CheckBoxFor(model => model.IsSMS) 
            @Html.LabelFor(model => model.IsSMS)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.ValidationMessageFor(model => model.IsSMS)
                </td>
            </tr>
            <tr>
                <td colspan="2" style="border:0;">
                    <input type="submit" value="Save" />
                </td>
            </tr>
        </table>
    </fieldset>
}

Look at the HTML code above here. I have rendered checkboxes for IsAlert, IsEmail, and IsSMS property against notification modes and added a validation control for IsSMS property only as:

HTML
@Html.ValidationMessageFor(model => model.IsSMS) 

If you want to test how this validation works on server side, just remove the script reference from view or disable client validations from web.config and run the application.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)