lundi 26 mars 2018

How to match a model to it's closed generic validator. Is service locator the only answer?

I have a base class of type Car:

public abstract class Car
{
{

And around 50 derived types of Car e.g:

public class CarA : Car
{
}

public class CarB : Car
{
}
// etc ...

To make things trickier the base class has several properties which are also abstract types. These abstract types have varying numbers of derived types:

public abstract class Car
{
    public abstract Engine Engine { get; set; }
    public abstract ICollection<Seat> Seats { get; set; }
}

public class CarA : Car
{
    public Engine Engine { get; set; }
    public ICollection<Seat> Seats { get; set; }

    public CarA()
    {
        // Setting defaults.
        Engine = new EngineA();
        Seats = new List<Seat> { new SeatA(), new SeatA(), new SeatB(), new SeatC() };
    }
} 
// etc ...

These objects are passed into an api and are deserialised at runtime. My controller accepts a base type of Car and what I want to do is validate the car and all of it's properties. In the past I would have added an IsValid() method to each car and called an IsValid() method on the class. This is nice and clean.

public class CarsController : ApiController
{
    [HttpPost]
    public IHttpActionResult AddCar(Car car)
    {
        car.IsValid();
        // Save car to store
    }
}

Now to the problem.

Calling IsValid() doesn't allow me to do dependency injection and I would like to be able to separate out the model from the validation logic.

This introduces lots of validation classes:

public class CarValidator : IValidator<Car>
{
    public bool IsValid(Car car) { // validation logic here }
}

public class CarAValidator : IValidator<CarA>
{
    // The probelm with validating the seats is the same problem as with validating the car.
    // In this case there is only a small number of seat validators so I have injected them all in.
    private readonly ICollection<IValidator<Seat>> seatValidators;

    public CarAValidator(IValidator<Car> baseValidator, ICollection<IValidator<Seat>> seatValidators)
    {
        Include(baseValidator);
        this.seatValidators = seatValidators;
    }

    public bool IsValid(CarA car) { // validation logic here }
}

I would like to be able to get the validator that matches the derived type in a clean way. Some suggestions I have seen online include:

  1. Inject the IOC container into the controller and use the service locator pattern to find the correct validator.
  2. Create a factory that uses the service locator pattern inside it and inject this into your controller.
  3. Inject a collection of all of the validators and search through them to get the correct type. This is the appraoch I have gone with but I have concerns about having to create 50 validators per request when in reality only one is needed. Bear in mind that each validator can have lots of other collections for validating.

Is there a clean pattern I can use to get the correct validator for the correct type or should I just resort to using the Service Locator (anti) pattern?

Aucun commentaire:

Enregistrer un commentaire