Click here to Skip to main content
15,888,113 members
Articles / Web Development / HTML

Conditional ASP.NET MVC Validation using Data Annotation Attributes

Rate me:
Please Sign up or sign in to vote.
4.71/5 (4 votes)
12 Aug 2014CPOL4 min read 84.8K   1   22   13
Declaration of conditional logic for Data annotation validation attributes

Introduction 

The article discusses the design of a simple module that allows declaration of conditional logic for Data annotation validation attributes 

Background

One of the amazing features of ASP.NET MVC is validation using Data annotations that is applied to both the client and server side. One of the proposed benefits of using this is the DRY concept (don’t repeat yourself). In projects, there will be cases when you need to have conditional validation depending on the action method or even the data.  One big limitation of the data annotation validation approach is that they don’t have any kind of conditional logic. So if you mark a field with the RequiredAttribute it will be required for all action methods. There are different workarounds, perhaps the most common one involving the use of view models for each action. However this goes against the DRY concept.

This article will discuss the design and usage of a module that allows adding conditional logic to data annotation validation attributes. 

Design 

The main class is ConditionalAttribute that itself is derived from Attribute. ConditionalAttribute class allows definition of data annotation attributes and specifying conditions for them.   

[AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true)]
public class ConditionalAttribute : Attribute
{
 public Type AttributeType { get; set; }
 public object[] ConstructorParam { get; set; }
 public bool Apply { get; set; }
 public string[] Actions { get; set; }
 public string[] Keys { get; set; }
 public object[] Values { get; set; }

 public ConditionalAttribute(string actionName, Type attributeType) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam) 
 public ConditionalAttribute(string actionName, Type attributeType, string key, string value) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam, string key, string value) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam, bool apply) 
 public ConditionalAttribute(string actionName, Type attributeType, object[] constructorParam, string key, string value, bool apply)
} 

The CustomModelValidatorProvider class inherits from DataAnnotationsModelValidatorProvider and contains logic to iterate through the conditional attributes, verify the condition and add the data annotation validation attributes.   

protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
 List<Attribute> newAttributes = new List<Attribute>(attributes);
 var attrsCustom = GetCustomAttributes(metadata);
 if (attrsCustom != null)
 {
   var attrs = attrsCustom.OfType<ConditionalAttribute>().ToList();
   if (attrs != null)
   {
     foreach (var attr in attrs)
     {
      if (ApplyAttribute(attr, metadata, context, attributes))
      {
        Attribute objAttr = CreateAttribute(attr.AttributeType, attr) as Attribute;
        newAttributes.Add(objAttr);
      }
     }
   }
  }
  return base.GetValidators(metadata, context, newAttributes);
}  

The IConditionalAttribute interface has a method Apply that will be called by the CustomModelValidatorProvider class, if the controller implements the interface. The controller can than decide whether to apply the validation attribute. 

The module allows calling any validation attribute as internally it uses the Type to create an instance and sets the properties dynamically.    

C#
virtual protected object CreateAttribute(Type type, ConditionalAttribute attr)
{
           object obj = Activator.CreateInstance(type,attr.ConstructorParam);
           var props = type.GetProperties();
           foreach (var prop in props)
           {
               object value = GetKeyValue(attr, prop.Name);
               if (value != null)
                   prop.SetValue(obj, value);
           }
           return obj;
}

The classes have been implemented in one single file CustomModelValidatorProvider.cs for easy integration in projects and declared under the namespace ConditionalAttributes.    

The main constructor for the ConditionalAttribute class is:  

C#
public ConditionalAttribute(string actionName, Type attributeType, object[] constructorParam, string key, string value, bool apply)   

The actionName parameter allows specifying the name of the action method on which the attribute should be applied. If empty it applies to all actions.

The attributeType parameter is used for defining the type of the data annotation validation attribute.<o:p>

The constructorParam is used to specify the parameters that might be required when instantiating the data annotation attribute, for example length for StringLengthAttribute.<o:p>

The key and value parameters specify properties for the attributes and their values, for example ErrorMessage.<o:p>

The <code>apply  parameter reverse the logic of applying the validation attribute. For example to apply the validation attributes on all action methods except of <code><code>Index, set Index in actionName and specify apply=false. 

Example 

C#
[ConditionalAttribute("Modify",typeof(RequiredAttribute),"ErrorMessage","{0} is required field")]
public string Address { get; set; }   

This conditional attribute is used to specify a validation attribute of type RequiredAttribute. The property ErrorMessage has the value “{0} is a required field”. The method is applied to the Modify action method.<o:p>

Let’s take at another example: 

C#
[ConditionalAttribute("Index",typeof(RemoteAttribute),Apply=false,ConstructorParam = new object[] {"NameExists", "Home" })]
public string Name {get; set; }  

The statement above applies a RemoteAttribute only when the action method name is not Index. It specifies the constructor parameters Action (NameExists) and Controller (Home).<o:p>

If the controller implements the IConditionalAttribute, the Apply method will be called internally once all the conditions have been evaluated. The method returns a Boolean that will control whether the validation attribute will be applied. 

Multiple conditional attributes can be applied to a property.

C#
public class Person
{
  [ConditionalAttribute(null, typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  [ConditionalAttribute(null, typeof(StringLengthAttribute), 10, "ErrorMessage", "{0} cannot be more than 20 characters")]
  [ConditionalAttribute(null, typeof(RegularExpressionAttribute), @"[A-Za-z0-9]*", "ErrorMessage", "{0} should be alpahnumeric characters only")]
  [ConditionalAttribute("Index", typeof(RemoteAttribute), ConstructorParam = new object[] { "LoginIDExists", "Home" })]
  public string LoginID { get; set; }

  [ConditionalAttribute("Modify", typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  [ConditionalAttribute("Index", typeof(RemoteAttribute), Apply = false, ConstructorParam = new object[] { "NameExists", "Home" })]
  public string Name { get; set; }

  [ConditionalAttribute("Modify", typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  public string Address { get; set; }
}

Project

Download Sample project

The attached sample project contains two action methods. The first one is Index, and in that page the data validation is applied for the login id where as for the Name and Address no validation is applied due to the conditional logic. In the second action method, Modify the Name and Address conditional validation are applied. 

The code file CustomModelValidatorProvider.cs is inside the project App_Start folder .  

Usage 

To start using ConditionalAttribute in your project follow these steps:<o:p>

Copy the CustomModelValidatorProvider.cs in your project – ideally App_Start.<o:p>

In the global.asax.cs file add the CustomerModelValidatorProvider to the validation providers. This is done in the Application_Start method. You will need to add ConditionalAttributes namespace as well.<o:p>

C#
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new CustomModelValidatorProvider()); 

Start annotating the object model with conditional attributes.<o:p>

In order to have a function that’s called during conditional attribute evaluation, inherit the controller from IConditionalAttribute interface and implement the Apply method. 

Enhancements

A couple of enhancements that would be practical. 

Add support for non - validation attributes like read only, display name etc. 

Make the ConditionalAttribute easier to call - right now it requires passing object so there is less type safety. The best option would be to pass an instance of the data annotation validation attribute with the parameters, instead of the type to the ConditionalAttribute constructor. Suggestions are welcome how to do this in a generic fashion.  

History 

First version 6-Feb-2013

License

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


Written By
Software Developer (Senior)
Australia Australia
A seasoned software engineer with over 13 years of commercial experience in software design/development.

Comments and Discussions

 
QuestionAny conflict with Entity Framework? Pin
DestroyToy14-Aug-14 17:55
DestroyToy14-Aug-14 17:55 
AnswerRe: Any conflict with Entity Framework? Pin
Talal Tayyab15-Aug-14 15:48
Talal Tayyab15-Aug-14 15:48 
QuestionFormat problems Pin
Nelek11-Aug-14 2:14
protectorNelek11-Aug-14 2:14 
AnswerRe: Format problems Pin
Talal Tayyab11-Aug-14 12:41
Talal Tayyab11-Aug-14 12:41 
GeneralRe: Format problems Pin
Nelek12-Aug-14 1:25
protectorNelek12-Aug-14 1:25 
QuestionProvide the project Pin
Member 976329211-Oct-13 2:04
Member 976329211-Oct-13 2:04 
AnswerRe: Provide the project Pin
Talal Tayyab11-Aug-14 12:42
Talal Tayyab11-Aug-14 12:42 
QuestionNo Code? Pin
spook11-Jun-13 9:24
spook11-Jun-13 9:24 
Questionno attached projects and samples? Pin
jerry.xie13-Mar-13 17:36
jerry.xie13-Mar-13 17:36 
AnswerRe: no attached projects and samples? Pin
Talal Tayyab11-Aug-14 12:43
Talal Tayyab11-Aug-14 12:43 
GeneralRe: no attached projects and samples? Pin
Nicklez8-Oct-14 0:02
Nicklez8-Oct-14 0:02 
GeneralRe: no attached projects and samples? Pin
Talal Tayyab14-Oct-14 17:28
Talal Tayyab14-Oct-14 17:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.