HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Validation for DTO using DataAnnotations

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
dataannotationsvalidationforusingdto

Problem

I need validation for all DTO objects using System.ComponentModel.DataAnnotations.
You can see how I implemented. Idea is to have one abstract class that will be inherited in all dto classes.
This abstract class have check if object is valid and get all validation results.

Is this good approach?

What do you think?

dto base class :

public abstract class DtoBase : IValidatableObject
    {
        public virtual IEnumerable GetValidationResult()
        {
            return Validate(new ValidationContext(this));
        }

        public bool IsValid()
        {
            return Validate(new ValidationContext(this)).Count() == 0;
        }

        public virtual IEnumerable Validate(ValidationContext validationContext)
        {
            var results = new List();
            Validator.TryValidateObject(this, validationContext, results, true);

            return results;
        }
    }


dto work item :

public class WorkItemDto : DtoBase
    {
        public WorkItemDto()
        {
        }

        public int Id { get; set; }

        [StringLength(500, MinimumLength = 200)]
        public string Description { get; set; }

        [Range(20, 5000)]
        public int ItemNumValue { get; set; }

        public ICollection Usage { get; set; }
    }


Example how to use in wpf or in mvc project (it have to work in any client):

var item = new WorkItemDto();
 item.Description = "my descryption";
 item.ItemNumValue = 5;
 item.Id = 7;

 var isValid = item.IsValid();
 var allResults = item.GetValidationResult();

Solution

What do you think about non static validators? While making validators smarter, we will sooner or later need to wire up their dependencies (like repositories)... Validators can easily do much more while being instantiated by IoC container.

I would suggest to define this interface and class:

interface IModelValidator
{
    IEnumerable Validate(T subject);
}

class ModelValidator : IModelValidator
{
    public ModelValidator(IEnumerable> validators)
    {
        Validators = validators;
    }

    public IEnumerable Validate(T subject) =>
        Validators.SelectMany(v => v.Validate(subject));

    IEnumerable> Validators { get; }
}


Now some demo code; models:

class Order { }
class MailOrder : Order { }


Validators:

class OrderValidator : IModelValidator
{
    public IEnumerable Validate(Order subject)
    {
        yield return "Bad order.";
    }
}

class MailOrderValidator : IModelValidator
{
    public IEnumerable Validate(MailOrder subject)
    {
        yield return "Bad mail order.";
    }
}


Something to check validity of the order:

class Controller
{
    public Controller(ModelValidator validator)
    {
        var order = new MailOrder();
        foreach (var message in validator.Validate(order))
            Console.WriteLine(message);
    }
}


So now it is just about proper registering them in IoC container (here Autofac):

class Program
{
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterSource(new ContravariantRegistrationSource());
        builder.RegisterGeneric(typeof(ModelValidator<>))
            .AsSelf();
        builder.RegisterType()
            .AsImplementedInterfaces();
        builder.RegisterType()
            .AsImplementedInterfaces();
        builder.RegisterType()
            .AsSelf();

        IContainer container = builder.Build();
        var controller = container.Resolve();
    }
}


You could play with it to discover how perfectly contravarians works here. You just declare what to validate - compatible validators will show up. No need to change client code - just register more validators if necessary. One of them might be IModelValidator with attribute checking.

Code Snippets

interface IModelValidator<in T>
{
    IEnumerable<string> Validate(T subject);
}

class ModelValidator<T> : IModelValidator<T>
{
    public ModelValidator(IEnumerable<IModelValidator<T>> validators)
    {
        Validators = validators;
    }

    public IEnumerable<string> Validate(T subject) =>
        Validators.SelectMany(v => v.Validate(subject));

    IEnumerable<IModelValidator<T>> Validators { get; }
}
class Order { }
class MailOrder : Order { }
class OrderValidator : IModelValidator<Order>
{
    public IEnumerable<string> Validate(Order subject)
    {
        yield return "Bad order.";
    }
}

class MailOrderValidator : IModelValidator<MailOrder>
{
    public IEnumerable<string> Validate(MailOrder subject)
    {
        yield return "Bad mail order.";
    }
}
class Controller
{
    public Controller(ModelValidator<MailOrder> validator)
    {
        var order = new MailOrder();
        foreach (var message in validator.Validate(order))
            Console.WriteLine(message);
    }
}
class Program
{
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterSource(new ContravariantRegistrationSource());
        builder.RegisterGeneric(typeof(ModelValidator<>))
            .AsSelf();
        builder.RegisterType<OrderValidator>()
            .AsImplementedInterfaces();
        builder.RegisterType<MailOrderValidator>()
            .AsImplementedInterfaces();
        builder.RegisterType<Controller>()
            .AsSelf();

        IContainer container = builder.Build();
        var controller = container.Resolve<Controller>();
    }
}

Context

StackExchange Code Review Q#112318, answer score: 4

Revisions (0)

No revisions yet.