FluentValidation – integracja z ASP.NET Web API

W poprzednim poście z cyklu FluentValidation pokazałem jak dodać FluentValidation do ASP.NET MVC.  W takim razie pora na pokaz integracji z ASP.NET Web API.

Zaczynamy od dodania do naszego projektu biblioteki łączącej FluentValidation z WebApi.

Install-Package FluentValidation.WebAPI

Następnie w miejscu startu aplikacji (u mnie jest to Application_Start Global.asax) dodajemy kod odpowiedzialny za połączenie FluentValidation z domyślną walidacją WebAPI.

FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration);

W przypadku używania FluentValidation także z MVC może wystąpić problem z identycznymi nazwami klas przez co trzeba się posiłkować dodaniem przedrostka przestrzeni nazw.

Do walidacji użyjemy tego samego modelu i walidatora, co w poprzednim poście:

[Validator(typeof(UserViewModelValidator))]
public class UserViewModel
{
    public string UserName { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

public class UserViewModelValidator: AbstractValidator<UserViewModel>
{
    public UserViewModelValidator()
    {
        this.RuleFor(r => r.UserName).NotEmpty().Length(0, 50);

        this.RuleFor(r => r.Email).NotEmpty().EmailAddress().Length(0, 100);

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

Następnie stwórzmy prosty kontroler zwracający informacje o nieudanej walidacji użytkownika, w przypadku błędów walidacji:

public class UserController : ApiController
{
    public HttpResponseMessage PostUser(UserViewModel user)
    {
        if (!ModelState.IsValid)
        {
            return new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.BadRequest,
                    Content = new StringContent("Validation failed.")
                };
        }
            
        // create user

        return this.Request.CreateResponse(HttpStatusCode.Created);
    }
}

Uruchamiając Postmana i wysyłając proste żądanie dostajemy poprawną odpowiedź:
chrome_2016-04-12_22-04-22

Czyli nasza walidacja działa – dostajemy błąd HTTP 400 i komunikat błędu.

Filtr walidacyjny

Aby uprościć sobie życie i nie dodawać za każdym razem sprawdzania walidacji w akcji kontrolera proponowałbym stworzenie prostego filtra walidacyjnego. Miałby on za zadanie podpięcie się przed wykonaniem metody i sprawdzenie czy model jest poprawny. Kiedy zachodzi problem walidacji modelu, zwracamy odpowiednią dla nas wiadomość – może to być prosta lista błędów, ale nic nie szkodzi nam na przeszkodzie by pogrupować te błędy i zwrócić bardziej skomplikowany obiekt. Moja implementacja filtra:

public class ModelStateFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            var errors = actionContext.ModelState
                         .Values.SelectMany(v => v.Errors)
                         .Select(e => e.ErrorMessage);

            actionContext.Response =
                          actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
        }
    }
}

Postawiłem na prostotę. Dziedziczę po klasie ActionFilterAttribute, która umożliwia podpięcie się pod akcje kontroleraW metodzie OnActionExecuting sprawdzam, czy ModelState jest prawidłowy. Jeśli nie, to iteruję po wszystkich błędach i zwracam je jako listę do użytkownika. W takim przypadku metoda kontrolera się bardzo upraszcza:

public HttpResponseMessage PostUser(UserViewModel user)
{       
    // create user

    return this.Request.CreateResponse(HttpStatusCode.Created);
}

Nie potrzebujemy dodatkowego kodu walidacyjnego – wszystko załatwia nam filtr. Zostaje tylko podpiąć nasz filtr do WebAPI – u mnie dzieje się to w pliku WebApiConfig.

config.Filters.Add(new ModelStateFilterAttribute());

Dzięki temu, przy identycznym żądaniu z Postmana, dostajemy listę błędów modelu.
chrome_2016-04-12_22-27-31

Podsumowanie

Dodanie FluentValidation do ASP.NET WebAPI nie różni się skomplikowaniem od dodania integracji do ASP.NET MVC  – jest równie proste. Z pomocą filtrów jesteśmy w stanie uprościć proces walidacji, przez co nie dodajemy dodatkowego kodu w naszych kontrolerach. Kod jest o wiele czystszy i czytelniejszy.

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