Fluent validation with ASP.NET Web API.

Let’s say you have a Web API controller and you want to validate Create(POST)and Update(PUT) operations.
You also don’t want to pollute your action methods with validation logic and encapsulate validation mechanism.

public class UsersController : ApiController
{
    readonly IUserService _userService;

    public UsersController(IUserService userService)
    {
        if (userService == null) throw new ArgumentNullException("userService");
        _userService = userService;
    }

    public HttpResponseMessage Get()
    {
        var list = _userService.GetAll();
        return Request.CreateResponse(HttpStatusCode.OK, list);
    }
    
    public HttpResponseMessage Get(int id)
    {
        var user = _userService.GetById(id);
        return Request.CreateResponse(HttpStatusCode.OK, user);
    }
     
    public HttpResponseMessage Post([FromBody]UserDto user)
    {
        return Request.CreateResponse(HttpStatusCode.Created, _userService.Create(user));
    }

    public HttpResponseMessage Put([FromBody]UserDto user)
    {
        _userService.Update(user);
        return Request.CreateResponse(HttpStatusCode.OK);
    }
}

I prefer to use Fluent Validation for business objects validation. It is a library for .NET that uses a fluent interface and lambda expressions for building validation rules for your objects. Using this library you can encapsulate your validation logic in separate class, for example:

public class UserValidator : AbstractValidator<UserDto>
{
    public readonly IRepositoryAsync<User> _userRepository;

    public UserValidator(IRepositoryAsync<User> userRepository)
    {
        _userRepository = userRepository;

        RuleSet("Post", () =>
        {
            RuleFor(item => item.Password)
                .NotEmpty()
                .WithMessage("Password should not be empty");

            RuleFor(item => item.ConfirmPassword)
                .NotEmpty()
                .WithMessage("Confirm Password should not be empty");

            Custom(item =>
            {
                if (_userRepository.Queryable().Any(x => x.UserNo == item.UserNo))
                {
                    decimal freeNumber = _userRepository.GetAvaliableUserNumber();
                    return new JsonValidationFailure((int) UserValidationMessageTypes.UserNumberAlreadyExists,
                        string.Format("Cannot save this User. User already in use. Free number: {0}",
                            freeNumber));

                }

                if (item.Password != item.ConfirmPassword)
                {
                    return new JsonValidationFailure((int) UserValidationMessageTypes.UserPasswordsShouldMatch,
                        "User Password and Confirm Password should match");
                }

                return null;
            });
            CommonRules();
        });

        RuleSet("Put", () =>
        {
            RuleFor(item => item.UserId)
                .NotEqual(0)
                .WithMessage("Id should not be null");

            Custom(item =>
            {
                if (_userRepository.Queryable().Any(x => x.UserNo == item.UserNo && x.UserId != item.UserId))
                {
                    decimal freeNumber = _userRepository.GetAvaliableUserNumber();
                    return new JsonValidationFailure((int) UserValidationMessageTypes.UserNumberAlreadyExists,
                        string.Format("Cannot save this User. User already in use. Free number: {0}",
                            freeNumber));
                }

                return null;
            });
            CommonRules();
        });
        CommonRules();
    }

    private void CommonRules()
    {
        RuleFor(item => item.UserName)
            .NotEmpty()
            .WithMessage("User name should not be null");

        RuleFor(item => item.UserName)
            .Length(1, 30)
            .WithMessage("User name should be less then 30 characters");

        RuleFor(item => item.UserNo)
            .NotEqual(0)
            .WithMessage("User number should not be null");

    }
}

In this piece of code you can see that we create custom validator UserValidator and derived if from AbstractValidator where UserDto entity that mapped to existed table in database. The most important is to notice that we have specific RuleSet‘s for named “Post” and “Put” with some logic described inside.

We can specify rules for each property of entity that we want to validate. We can use RuleFor for it with predefined number of extension methods like .NotEmpty(), .NotNull(), etc. In addition we can specify error message for each of these rules with method .WithMessage().

But what if we need to implement custom validation, for example query the database in order to check whether the specific entity is OK for update or insert. In this case we are using Custom section and query the database with the help of repository that we injected in
constructor of our validator.

I used custom JsonValidationFailure object derived from the default ValidationFailure of the FluentValidation:

public class JsonValidationFailure : ValidationFailure
{
    public JsonValidationFailure(string propertyName, string error) : base(propertyName, error)
    {
    }

    public JsonValidationFailure(string propertyName, string error, object attemptedValue) : base(propertyName, error, attemptedValue)
    {
    }

    public JsonValidationFailure(int validationType, object repsonse):base(JsonConvert.SerializeObject(validationType), JsonConvert.SerializeObject(repsonse))
    {

    }
}

OK, so validator is created and now we need to use it in our Web API controller. We can basically inject validator in the constructor of the controller, but I prefer another solution.

We will create ActionFiler that will validate incoming object on specific action – in our cases Put and Post actions.

So let’s implement this filter:

public class ValidationResponseFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var actionArgument = GetTheActionArgument(actionContext);
        if (actionArgument == null) throw new HttpBadRequestResponseException("Element is null");
       
        Type validatorType = GetValidatorType(actionArgument.GetType());
        IValidator validator =
            (IValidator) actionContext.Request.GetConfiguration().DependencyResolver.GetService(validatorType);
        if (validator == null) throw new HttpBadRequestResponseException("Validator is not specified");

        string actionName = actionContext.ActionDescriptor.ActionName;

        var validationResult =
            validator.Validate(new ValidationContext(actionArgument, new PropertyChain(),
                new RulesetValidatorSelector(actionName)));
        if (validationResult.IsValid) return;

        ResponseMessageHelper.CreateBadRequestResponse(validationResult);
    }

    private static object GetTheActionArgument(HttpActionContext actionContext)
    {
        return actionContext.ActionArguments.SingleOrDefault().Value;
    }

    private Type GetValidatorType(Type type)
    {
        if (type == null) return null;

        var abstractValidatorType = typeof (AbstractValidator<>);
        var validatorForType = abstractValidatorType.MakeGenericType(type);

        var types = AllClasses.FromLoadedAssemblies().
            Where(t => validatorForType.IsAssignableFrom(t)).ToList();

        if (types.Any())
        {
            return types.FirstOrDefault();
        }

        return null;
    }
}

In the OnActionExecuting method we are doing following things:

  • Get action argument. It is entity that we trying to save or udpate
  • Get the type of this entity and resolve validator for it (this step might be improved, cause getting service right from dependency resolver is not good approach)
  • Get the name/type of the action(“Put or “Post”) and execute method Validate using appropriate RuleSet for this action.
  • If entity is valid we continue executing the body of our action and call Update or Create method of our service layer.
  • If entity is not valid we throw HttpBadRequestResponseException  and pass error message to the client with HttpStatusCode.BadRequest status.

Code of the helper that generates bad request:

public class ResponseMessageHelper
{
    public static string CreateBadRequestResponse(ValidationResult validationResult)
    {
        var validationErrors =
           validationResult.Errors.Select(e => new KeyValuePair<string, string>(e.PropertyName, e.ErrorMessage));
        throw new HttpBadRequestResponseException(JsonConvert.SerializeObject(validationErrors));
    }
}

And the class of the exception:

public sealed class HttpBadRequestResponseException : HttpResponseException
{
    public HttpBadRequestResponseException()
        : this(string.Empty)
    {
    }

    public HttpBadRequestResponseException(string message)
        : base(new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(message) })
    {
    }
}

There are also couple things to do. First, we need to mark our Put/Post actions with ValidationResponseFilter attribute:

[ValidationResponseFilter]
public HttpResponseMessage Post([FromBody]UserDto user)
{
    return Request.CreateResponse(HttpStatusCode.Created, _userService.Create(user));
}

[ValidationResponseFilter]
public HttpResponseMessage Put([FromBody]UserDto user)
{
    _userService.Update(user);
    return Request.CreateResponse(HttpStatusCode.OK);
}

One other thing is that we need to add Validators into the dependency injection container. If you haven’t implemented dependency injection container for your application – you’d better start doing it. :) Since it is really important and useful mechanism.
So, in my case I map my UserValidator to IValidator, where IValidator interface from Fluent Validation library.

container.RegisterType<IValidator<UserDto>, UserValidator>();

In next post I’ll describe how to use Owin(The Open Web Interface for .NET) with Katana project and set up your application configuration.

Share this post:Tweet about this on TwitterShare on Facebook0Share on LinkedIn0Share on Google+0Share on Reddit0Email this to someoneDigg this
  • Alex -

    Thanks for this post. It has been helpful with implementing validation on our APIs.

    Could provide your implementation of the JsonValidationFailure class?

    Thanks.

    Jason

  • AlexP

    Hi Jason,

    Thank you for the feedback. I’ve added JsonValidationFailure object below the UserValidator.

    Alex

  • Joao

    Great post, Alex!
    I’m having a hard time getting this to work though. When I use RuleFor validation works and I can see my model is not valid (I’ve made a simpler example but in the end is like yours). But when I use RuleSet, the ModelState is always valid or, it seems like the validation is not working. I’ve searched a lot and all I can find is that RuleSet is only available for MVC projects. Do you have any ideas?