5
\$\begingroup\$

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<ValidationResult> GetValidationResult()
        {
            return Validate(new ValidationContext(this));
        }

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

        public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var results = new List<ValidationResult>();
            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<ItemUsageDto> 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();
\$\endgroup\$
3
  • \$\begingroup\$ Real code of course. Except example "how to use" \$\endgroup\$ Nov 30, 2015 at 13:00
  • \$\begingroup\$ To be honest, I don't like logic in a DTO (or superclass, etc.); just data. I'd recommend having extension methods (in another class, obviously) provide the validation logic. \$\endgroup\$ Nov 30, 2015 at 18:31
  • \$\begingroup\$ What happens with these DTO's? Do you store the data through some data layer? If so, maybe the validation should be in the data layer. \$\endgroup\$ Jan 29, 2016 at 22:15

2 Answers 2

4
\$\begingroup\$

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<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; }
}

Now some demo code; models:

class Order { }
class MailOrder : Order { }

Validators:

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.";
    }
}

Something to check validity of the order:

class Controller
{
    public Controller(ModelValidator<MailOrder> 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<OrderValidator>()
            .AsImplementedInterfaces();
        builder.RegisterType<MailOrderValidator>()
            .AsImplementedInterfaces();
        builder.RegisterType<Controller>()
            .AsSelf();

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

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<DtoBase> with attribute checking.

\$\endgroup\$
1
  • \$\begingroup\$ It looks like a very nice solution. If you must use DataAnnotations, then this isn't the solution, but I've never been a fan of DataAnnotations anyways, so I much prefer this approach. \$\endgroup\$
    – JohnnyO
    Feb 1, 2016 at 20:06
1
\$\begingroup\$

Not bad, but I'm not too fond of how the Validate instance method is just a call to a Validator static class passing in this. Just call the static method directly. If you're worried about the duplication of instantiating the results list, the TryValidateObject code should be able to be refactored so that results is internal to the method and returned (instantiate the list inside the method but outside the try/catch, and return the results; don't use the try pattern unless your result can be null). There shouldn't be a need to pass in the list.

In general, if you're just writing methods that pass this to static methods, it's a superfluous layer and calling the static methods directly will suffice. If doing so means you will not be able to mock validations in unit testing, make Validator's validation method non-static and inject the Validator into the constructor of code that needs to call it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.