Custom Data Annotations in Asp.net MVC3 Razor


Imagine you want to restrict the first name of the user to limited number of words. For example, you may say that 10 words are too much for a first name. Then in this case you can create your own Custom Annotation attribute.

Watch Video


       All of the validation annotations (like Required and Range) ultimately derived from the ValidationAttribute base class. The base class is abstract and lives in the System.ComponentModel.DataAnnotations namespace. Your validation logic will live in a class deriving from ValidationAttribute.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomValidation.CustomValidator
{
    public class MaxWords : ValidationAttribute
    {
    }
}

To implement the validation logic, we need to override one of the IsValid methods provided by base class. Overriding the IsValid version taking a Validationcontext parameter provides more information to use inside the IsValid method. The ValidationContext will give you access to the model type, model object instance, and friendly display name of the property you are validating, among other piece of information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomValidation.CustomValidator
{
    public class MaxWords : ValidationAttribute
    {
        protected override System.ComponentModel.DataAnnotations.ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            return ValidationResult.Success;
        }
    }
}

The first parameter to the IsValid method is the value of the property to validate. If the value is we can return a successful validation result, but before you can decide if the value is valid, you will need to know how many words are too many. We can do this by adding a constructor to the attribute and force the user to pass the maximum number of words as a parameter.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomValidation.CustomValidator
{
    public class MaxWords : ValidationAttribute
    {
        private readonly int _maxWords;

        public MaxWords(int maxWords)
        {
            _maxWords = maxWords;
        }

        protected override System.ComponentModel.DataAnnotations.ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            return ValidationResult.Success;
        }
    }
}

Now that we have parameterized the maximum word count, we can implement the validation logic to catch an error :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomValidation.CustomValidator
{
    public class MaxWords : ValidationAttribute
    {
        private readonly int _maxWords;

        public MaxWords(int maxWords)
        {
            _maxWords = maxWords;
        }

        protected override System.ComponentModel.DataAnnotations.ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                var valueAsString = value.ToString();
                if (valueAsString.Split(' ').Length > _maxWords)
                {
                    return new ValidationResult("Too many words !!");
                }
                else
                {
                    return ValidationResult.Success;
                }
            }
            else
            {
                return new ValidationResult("No Value supplied !!");
            }
        }
    }
}

We are doing a relatively naive check for the number of words by splitting the incoming value using  the space character and counting the number of strings the Split method generates. If you find too many words, you return a ValidationResult object with a hard-coded error message to indicate a validation error. 
                         The problem with the last code is the hard-code error message. Developers who use the data annotations will expect to have the ability to customize an error message using the ErrorMessage property of ValidationAttribute.
                         To follow the pattern of the other validation attributes, you need to provide a default error message (to be used if the developer does not provide a custom error message) and generate the error message using the name of the property you are validating :


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace CustomValidation.CustomValidator
{
    public class MaxWords : ValidationAttribute
    {
        private readonly int _maxWords;

        public MaxWords(int maxWords)
            :base("{0} has too many words !!")
        {
            _maxWords = maxWords;
        }

        protected override System.ComponentModel.DataAnnotations.ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                var valueAsString = value.ToString();
                if (valueAsString.Split(' ').Length > _maxWords)
                {
                    var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                    return new ValidationResult(errorMessage);
                }
                else
                {
                    return ValidationResult.Success;
                }
            }
            else
            {
                return new ValidationResult("No Value supplied !!");
            }
        }
    }
}

We made following changes in preceding code :


  • First, we pass along a default error message to the base class constructors. You should pull this default error message from a resource file if you are building an internationalized application.
  • Notice how the default error message includes a parameter ({0}). The placeholder exists because the second change, the call to the inherited FormatErrorMessage method, will automatically format the string using the display name of the property. FormatErrorMessage ensures we use the correct error message string. The code needs to pass the value of this name, and the value is available from the DisplayName property of the ValidationContext parameter. With the validation logic in place, you can apply the attribute to any model property. 
        [Required]
        [MaxWords(20)]
        public string FirstName { get; set; }

We could also specify the error message to show.


        [Required]
        [MaxWords(20,ErrorMessage="There are too many words in {0}")]
        public string FirstName { get; set; }

Now if the user types too many words he will see the error message on UI.
Thus inheriting the ValidationAttribute class, we can create custom annotation attribute.

0 comments:

Post a Comment