FluentValidation – wstrzykiwanie zależności

Przedstawione przeze mnie do tej pory przykłady walidacji były zdecydowanie prostymi walidacjami – wszystkie dane zawierały się w modelu walidowanym. Niekiedy jednak zachodzi potrzeba sięgnięcia do zewnętrznych zasobów – bazy danych / cache / serwisów zewnętrznych. Mając w pamięci 5 zasadę SOLID’a warto by takie zależności wstrzykiwać do walidatora, zamiast tworzyć je bezpośrednio w konstruktorze. Kod będzie lepiej utrzymywalny i testowalny. Ja w moim przykładzie chciałbym wam pokazać wstrzykiwanie zależności do walidatorów dla ASP.NET MVC i ASP.NET WebAPI.

Na samym początku stwórzmy repozytorium użytkowników, które będziemy wstrzykiwać do walidatora. Ponieważ w przykładzie nie używam bazy danych, repozytorium będzie zwracało domyślną listę obiektów:

public class UserRepository:IUserRepository
{
    public IEnumerable<User> GetAll()
    {
        return new User[]
        {
            new User() {Id = 1, Email = "john@gmail.com", UserName = "john", Password = "123456"},
            new User() {Id = 2, Email = "rick@gmail.com", UserName = "rick", Password = "654321"},
        };
    }
}

public interface IUserRepository
{
    IEnumerable<User> GetAll();
}

Następnie do walidatora UserViewModel wstrzyknijmy powyższe repozytorium i dodajmy regułę walidacji o unikalnym emailu.

public class UserViewModelValidator : AbstractValidator<UserViewModel>
{
    private IUserRepository userRepository;

    public UserViewModelValidator(IUserRepository userRepository)
    {
        this.userRepository = userRepository;

        this.RuleFor(r => r.UserName).NotEmpty().Length(0, 50);

        this.RuleFor(r => r.Email).NotEmpty().EmailAddress().Length(0, 100)
            .Must(BeUnique).WithMessage("Email must be unique.");

        this.RuleFor(r => r.Password).NotEmpty().Length(6, 50);
    }

    private bool BeUnique(string email)
    {
        var emailFound = userRepository.GetAll().Any(u => u.Email == email);
        return !emailFound;
    }
}

Teraz część najważniejsza – połączenie naszego kontenera z FluentValidation. Ja mój przykład oparłem o kontener Autofac, ale analogiczny kod można stworzyć dla każdego używanego przez nas kontenera. Poniższe 2 listy kodu znajdują się w konfiguracji kontenera, u mnie w pliku Global.asax.cs.

Najpierw przeskanujmy nasz projekt w poszukiwaniu walidatorów,aby połączyć je z interfejsami.

AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
    .ForEach(match =>
    {
        builder.RegisterType(match.ValidatorType).As(match.InterfaceType);
    });

Kolejnym krokiem będzie poinformowanie FluentValidation, że walidatory powinny być instancjonowane przez Autofac. Robi się to przez rozwinięcie istniejącej już konfiguracji o informacje o fabryce walidatorów. Taką konfigurację trzeba dokonać osobno dla ASP.NET MVC i ASP.NET WebAPI.

FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(
    p => p.ValidatorFactory = new AutofacValidatorFactory(container));

FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, 
    p => p.ValidatorFactory = new AutofacValidatorFactory(container));

Poniżej przedstawiam przykładowy kod fabryki walidatorów. Wyszukuje ona w kontenerze walidator wymaganego typu.

public class AutofacValidatorFactory : ValidatorFactoryBase
{
    private readonly IContainer container;

    public AutofacValidatorFactory(IContainer container)
    {
        this.container = container;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        IValidator validator = (IValidator)container.Resolve(validatorType);
        return validator;
    }
}

I to wszystko – wstrzykiwanie zależności do walidatorów powinno działać. Nie potrzeba dodatkowej konfiguracji w samym MVC / WebAPI FluentValidation przez konfigurację fabryki wszystkim się zajmuje. By być całkowicie pewnym dokonajmy prostych testów tej funkcjonalności dokonując zapytań z poziomu przeglądarki i Postmana.
chrome_2016-04-18_23-26-54
chrome_2016-04-18_23-27-12

Widzimy, że zarówno dla ASP.NET MVC jak i ASP.NET WebAPI walidacja zadziałała i dostajemy komunikat o wymaganym unikalnym mailu.

Jeśli macie jeszcze jakieś pomysły na post o FluentValidation to będę wdzięczny za podrzucenie ich – na razie mam jeszcze pomysł na posty o lokalizacji komunikatów, warunkowej walidacji i walidacji dynamicznego modelu 🙂

Standardowo, wszystkie pokazane tutaj przykłady są na GitHubie.