RESTful validation with ASP.NET Web API and FluentValidation

Update: Check out a follow-up post on FluentValidation and WebApi.

I wanted to briefly talk about request validation in RESTful APIs today. I've had a lot of luck with FluentValidation in the past few years both in API and MVC worlds so my primary focus here is integrating it into a typical Web API project and configuring a few things to output truly RESTful validation errors. So, let's get to it! What are we trying to achieve?

Let's think of the following request/response scenario. A client issues request to the API to create a new employee, but doesn't specify the department. The API fails the request and presents a client-friendly error message indicating that a department is a required in order to create new employee resource. This scenario is outline below.

// Request
POST http://my-domain/api/employee  
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 400  
{
    "message": "The request is invalid.",
    "errors": {
        "department": [
            "'Department' should not be empty."
        ]
    }
}

To make this happen, we'll start by adding FluentValidation to our project. I am making an assumption that the project is using a Dependency Injection framework of some sort. In my case it's Ninject, but any code snippets here can be easily transferred to other Dependency Injection frameworks of your choosing. We'll begin by writing our employee controller.

[RoutePrefix("api/employee")]
public class EmployeeController : ApiController  
{
    [HttpPost]
    [Route("")]
    [ResponseType(typeof(Employee))]
    public IHttpActionResult Create(Employee employee)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }    

        // Create new employee

        return Created();
    }
}

// Model
public class Employee  
{
    public string Name { get; set; }
    public string Department { get; set; }
    public string Position { get; set; }
}

Next up is the employee validator.

public class EmployeeValidator : AbstractValidator<Employee>  
{
    public EmployeeValidator()
    {
        RuleFor(model => model.Name)
            .NotEmpty();

        RuleFor(model => model.Department)
            .NotEmpty();

        RuleFor(model => model.Position)
            .NotEmpty();            
    }
}

The snippet above is a trivial FluentValidation validator, which will simply check that each property of the employee resource is available on the request. The results of the validation process will be written to ModelState by FluentValidation. The controller action checks whether ModelState is valid and if it isn't, returns it on the response to client with status code of 401. At this point all of the essential pieces are in place and we just need to tie things together.

The beautiful thing about FluentValidation is that it doesn't require any attributes on your model or on controller actions, but only if you're using Dependency Injection. This is the magical part which keeps your models and validators completely decoupled. However, if you aren't using Dependency Injection than you need to add [Validator(typeof(EmployeeValidator))] to your Employee model.

Since I'm using Ninject, we're going to wire up FluentValidation via Dependency Injection. We'll begin by scanning the API assembly (or wherever your validators live) for validators and register them with the Ninject Kernel on per-request basis, meaning a new instance of a validator will be created on each HTTP request. To do this I'm using AssemblyScanner which ships with FluentValidation. You're going to want to run this wherever you register all of your dependencies with Ninject.

private static void RegisterValidators(IKernel kernel)  
{
    AssemblyScanner
        .FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
        .ForEach(result => kernel.Bind(result.InterfaceType)
        .To(result.ValidatorType)
        .InRequestScope());
}

Now that we have all validators in the Ninject Kernel, we need a way to tell FluentValidation how to retrieve them when needed. To do this we'll inherit from ValidatorFactoryBase in FluentValidation to spin up new validators from the Ninject Kernel.

public class NinjectValidatorFactory : ValidatorFactoryBase  
{
    public NinjectValidatorFactory(IKernel kernel)
    {
        Kernel = kernel;
    }

    public IKernel Kernel
    {
        get;
        set;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        if (((IList<IBinding>)Kernel.GetBindings(validatorType)).Count == 0)
        {
            return null;
        }

        return Kernel.Get(validatorType) as IValidator;
    }
}

Lastly, we'll need to configure FluentValidation to use our custom validator factory. Once again, since this will need an instance of the Ninject Kernel, you'll probably want to run this somewhere where you're registering your dependencies.

FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, provider => provider.ValidatorFactory = new NinjectValidatorFactory(kernel));  

At this point, we can POST a request to our API and get a response that looks like the one below.

// Request
POST http://my-domain/api/employee  
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 400  
{
    "message": "The request is invalid.",
    "modelState": {
        "employee.Department": [
            "'Department' should not be empty."
        ]
    }
}

This is great, but what's this modelState business? Sigh... This is our serialized ModelState object. Sounds like we'll have to do some tweaks to get this to output as errors array instead. We'll use an action filter for this.

public class RestfulModelStateFilterAttribute : ActionFilterAttribute  
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateResponse(
                HttpStatusCode.BadRequest, new ApiResourceValidationErrorWrapper(actionContext.ModelState));
        }
    }
}

So, what's happening here is we're grabbing the ModelState before the controller action is executed and after FluentValidation validator runs and writes the results to ModelState. If validation fails we pass ModelState to ApiResourceValidationErrorWrapper, which serializes it just the way we want before putting it back on the response for calling client. Here is how this part looks like.

public class ApiResourceValidationErrorWrapper  
{
    private const string ErrorMessage = "The request is invalid.";

    private const string MissingPropertyError = "Undefined error.";

    public ApiResourceValidationErrorWrapper(ModelStateDictionary modelState)
    {
        Message = ErrorMessage;
        SerializeModelState(modelState);
    }

    public ApiResourceValidationErrorWrapper(string message, ModelStateDictionary modelState)
    {
        Message = message;
        SerializeModelState(modelState);
    }

    public string Message { get; private set; }

    public IDictionary<string, IEnumerable<string>> Errors { get; private set; }

    private void SerializeModelState(ModelStateDictionary modelState)
    {
        Errors = new Dictionary<string, IEnumerable<string>>();

        foreach (var keyModelStatePair in modelState)
        {
            var key = keyModelStatePair.Key;

            var errors = keyModelStatePair.Value.Errors;

            if (errors != null && errors.Count > 0)
            {
                IEnumerable<string> errorMessages = errors.Select(
                    error => string.IsNullOrEmpty(error.ErrorMessage)
                                 ? MissingPropertyError
                                 : error.ErrorMessage).ToArray();

                Errors.Add(key, errorMessages);
            }
        }
    }
}

We can now remove the explicit ModelState check in the controller action because it's already handled for us in the action filter! The only thing left is to register the action filter with the global filter collection in your Web API config.

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

With all of these changes in place, we finally have our validation response looking how we want! Yippee!

// Request
POST http://my-domain/api/employee  
{
    "name": "Sergey",
    "position": "Software Engineer"
}

// Response
HTTP/1.1 400  
{
    "message": "The request is invalid.",
    "errors": {
        "employee.Department": [
            "'Department' should not be empty."
        ]
    }
}