Partial Validation With Data Annotations

By | September 8, 2013

Data Annotations provide a very nice way to validate the entities. But under some circumstances, you may don’t want to validate all properties. For example, a property like “createddate” we don’t want to validate in the client layer, because the field is not set at this point. In this situation it would be good to skip the validation for “createddate”.
I wrote a Nuget packages which allows you exclude specific validations.

NugGet https://www.nuget.org/packages/DataAnnotations.PartialValidation
GitHub https://github.com/dmicic/Dmicic.DataAnnotations.PartialValidation

Here is a simple demo. Let’s say, we have an application structured in 3 layers.
Partial Validation - Demo
The Person entity has following property definitions. (See code block)
I prefer the define the “technology neutral” validation attributes in the Business layer. And all other attributes (MVC attributes, EF attributes) I place in the appropriate layer.

public class Person : EntityBase
{
    [Required]
    public string Firstname { get; set; }

    [Required]
    public string Lastname { get; set; }

    [Required]
    public string EMail { get; set; }

    [Required]
    [CreditCardNumber]
    public string CreditCardNumber { get; set; }
}

public abstract class EntityBase
{
    [Required]
    public Guid Id { get; set; }

    [Required]
    [StringLength(10)]
    public string CreatedBy { get; set; }

    [Required]
    public DateTime? CreatedDate { get; set; }

    public string ModifiedBy { get; set; }

    public DateTime ModifiedDate { get; set; }
}

Now when I create a new object of type Person in my UI layer and try to validate it, I will always get validation errors because fields like Id, CreatedBy, CreatedDate are not set. In the UI layer, I only want to validate the properties which can be mutated by the user.
Now I can use the “Partial Validation” framework and define layer specific validation requirements.

var p1 = new Person();
p1.CreditCardNumber = "23434";
p1.EMail = "wrong mail address";

Configuration.Configure()
    .For("UI")
            .Configure<Person>()
                .Property(p => p.CreatedBy).ExcludeAll() // Exclude all validations for CreatedBy
                .Property(p => p.CreatedDate).ExcludeAll() // Exclude all validations for CreatedDate
                .Property(p => p.EMail).Include(new EmailAttribute()) // Include the Email validation for EMail
                .Property(p => p.CreditCardNumber).Exclude<CreditCardNumberAttribute>(); // Exclude the credit card validation for CreditCardNumber

Configuration.Configure()
    .For("Business")
        .Configure<Person>()
                .Property(p => p.CreatedBy).ExcludeAll() // Exclude all validations for CreatedBy
                .Property(p => p.CreatedDate).ExcludeAll(); // Exclude all validations fro CreatedDate


Configuration.Configure()
    .For("DataAccess")
        .Configure<Person>()
            .Property(p => p.CreditCardNumber).ExcludeAllBut<RequiredAttribute>(); // Exclude all except the required validation

var UiResult = PartialValidator.Validate(p1, "UI");
var BusinessResult = PartialValidator.Validate(p1, "Business");
var DataAccessResult = PartialValidator.Validate(p1, "DataAccess");

Here are the result of these layer specific validations.
Partial Validation - Validation Result

This is it! 😉

Here is an example which covers all Partial Validation functions:

Configuration.Configure()
    .For("Layer/Environment")
        .Exclude<MyFirstValidationAttribute>()                  // Exclutions for all entities in this environment
        .Exclude<MySecondValidationAttribute>()
        .Configure<EntityOne>()                                 // Specification for "EntityOne"
            .Property(p => p.Property1)
                .ExcludeAll()                                   // Exlcude all validations for "Property1"
                .Include(new MyFirstValidationAttribute())      // Include specific validation attribute
        .Configure<EntityTwo>()                                 // Specification for "EntityTwo"
            .Property(p => p.Property1)                         // Exclude Required and StringLength validation attribute for "Property1"
                .Exclude<RequiredAttribute>()
                .Exclude<StringLengthAttribute>()
        .Configure<EntityThree>()                               // Specification for "EntityThree"
            .Property(p => p.Property1)                         // Exclude all except "Required" for "Property1"
                .ExcludeAllBut<RequiredAttribute>();