ASP.NET MVC, TDD and Fluent Validation

Yesterday I wrote about ASP.NET MVC, TDD and AutoMapper, and how you can use them together in a DDD application. Today I thought I would follow up and explain how to apply these techniques to another important (but boring) part of any web application: user input validation.

To achieve this, we are using Fluent Validation, a validation framework that lets you easily set up validation rules using a fluent syntax:

public class UserRegistrationFormValidator : AbstractValidator<UserRegistrationForm>
{
    public UserRegistrationFormValidator()
    {
        RuleFor(f => f.Username).NotEmpty()
            .WithMessage("You must choose a username!");

        RuleFor(f => f.Email).EmailAddress()
            .When(f => !String.IsNullOrEmpty(f.Email))
            .WithMessage("This doesn't look like a valid e-mail address!");

        RuleFor(f => f.Url).MustSatisfy(new ValidWebsiteUrlSpecification())
            .When(f => !String.IsNullOrEmpty(f.Url))
            .WithMessage("This doesn't look like a valid URL!");
    }
}

If you think about it, validation and view model mapping have similar footprints in the application. They both:

  • Live in the application services layer
  • May invoke domain services
  • Use third-party libraries
  • Have standalone fluent configurations
  • Have standalone tests
  • Are injected into the application services

Let’s see how it all fits together starting at the outermost layer, the controller.

public class AccountController : Controller
{
    readonly IUserRegistrationService registrationService;
    readonly IFormsAuthentication formsAuth;

    ...

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Register(UserRegistrationForm user)
    {
        if (user == null)
            throw new ArgumentNullException("user");

        try
        {
            this.registrationService.RegisterNewUser(user);
            this.formsAuth.SignIn(user.Username, false);
            return RedirectToAction("Index", "Home");
        }
        catch (ValidationException e)
        {
            e.Result.AddToModelState(this.ModelState, "user");
            return View("Register", user);
        }
    }

    ...
}

As usual, the controller is pretty thin, delegating all responsibility (including performing any required validation) to an application service that handles new user registration. If validation fails, all our controller has to do is catch an exception and append the validation messages contained within to the model state to tell the user any mistakes they made.

The UserRegistrationForm validator is injected into the application service along with any others. Just like AutoMapper, we can now test both the controller, validator and application service separately.

public class UserRegistrationService : IUserRegistrationService
{
    readonly IUserRepository users;
    readonly IValidator<UserRegistrationForm> validator;

    ...

    public void RegisterNewUser(UserRegistrationForm form)
    {
        if (form == null)
            throw new ArgumentNullException("form");

        this.validator.ValidateAndThrow(form);

        User user = new UserBuilder()
            .WithUsername(form.Username)
            .WithAbout(form.About)
            .WithEmail(form.Email)
            .WithLocation(form.Location)
            .WithOpenId(form.OpenId)
            .WithUrl(form.Url);

        this.users.Save(user);
    }
}

Testing the user registration form validation rules

Fluent Validation has some nifty helper extensions that make unit testing a breeze:

[TestFixture]
public class When_validating_a_new_user_form
{
    IValidator<UserRegistrationForm> validator = new UserRegistrationFormValidator();

    [Test]
    public void The_username_cannot_be_empty()
    {
        validator.ShouldHaveValidationErrorFor(f => f.Username, "");
    }

    [Test]
    public void A_valid_email_address_must_be_provided()
    {
        validator.ShouldHaveValidationErrorFor(f => f.Email, "");
    }

    [Test]
    public void The_url_must_be_valid()
    {
        validator.ShouldNotHaveValidationErrorFor(f => f.Url, "http://foo.bar");
    }
}

You can even inject dependencies into the validator and mock them out for testing. For example, in this app the validator calls an IUsernameAvailabilityService to make sure the chosen username is still available.

Testing the user registration service

This validation code is now completely isolated, and we can mock out the entire thing when testing the application service:

[TestFixture]
public class When_registering_a_new_user
{
    IUserRegistrationService registrationService;
    Mock<IUserRepository> repository;
    Mock<IValidator<UserRegistrationForm>> validator;

    [Test, ExpectedException(typeof(ValidationException))]
    public void Should_throw_a_validation_exception_if_the_form_is_invalid()
    {
        validator.Setup(v => v.Validate(It.IsAny<UserRegistrationForm>()))
            .Returns(ObjectMother.GetFailingValidationResult());

        service.RegisterNewUser(ObjectMother.GetNewUserForm());
    }

    [Test]
    public void Should_add_the_new_user_to_the_repository()
    {
        var form = ObjectMother.GetNewUserForm();

        registrationService.RegisterNewUser(form);

        service.Verify(
            r => r.Save(It.Is<User>(u => u.Username.Equals(form.Username))));
    }
}

Testing the accounts controller

With validation out of the way, all we have to test on the controller is whether or not it appends the validation errors to the model state. Here are the fixtures for the success/failure scenarios:

[TestFixture]
public class When_successfully_registering_a_new_user : AccountControllerTestContext
{
    [SetUp]
    public override void SetUp()
    {
        ... 
        result = controller.Register(form);
    }

    [Test]
    public void Should_register_the_new_user()
    {
        registrationService.Verify(s => s.RegisterNewUser(form), Times.Exactly(1));
    }

    [Test]
    public void Should_sign_in()
    {
        formsAuth.Verify(a => a.SignIn(user.Username, false));
    }
}

[TestFixture]
public class When_registering_an_invalid_user :  AccountControllerTestContext
{
    [SetUp]
    public override void SetUp()
    {
        ...
        
        registrationService.Setup(s => s.RegisterNewUser(form)).Throws(
            new ValidationException(
                ObjectMother.GetFailingValidationResult()));
        
        result = controller.Register(form);
    }

    [Test]
    public void Should_not_sign_in()
    {
        formsAuth.Verify(a => a.SignIn(It.IsAny<string>(),
            It.IsAny<bool>()), Times.Never());
    }

    [Test]
    public void Should_redirect_back_to_the_register_view_with_the_form_contents()
    {
        result.AssertViewRendered().ForView("Register")
            .WithViewData<UserRegistrationForm>().ShouldEqual(form);
    }
}

This post has been a bit heavier on code than usual, but hopefully it is enough to get an idea of how easy it is to implement Fluent Validation in your ASP.NET MVC application.

19 thoughts on “ASP.NET MVC, TDD and Fluent Validation

  1. Hi Richard,

    Nice post. I’ve added a link to it from my blog and the FluentValidation wiki.

    Also, the link in the post to the FV test extensions doesn’t seem to work.

    Cheers,

    Jeremy

  2. Also I should probably note that ValidationException and ValidateAndThrow() are not included with FluentValidation (in case you were wondering why you can’t find them). Here’s the code for them:

    public class ValidationException : Exception
    {
        public ValidationResult Result { get; protected set; }
    
        public ValidationException(ValidationResult result)
        {
            Result = result;
        }
    }
    
    public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance)
    {
        ValidationResult result = validator.Validate(instance);
        if (!result.IsValid)
            throw new ValidationException(result);
    }
  3. Cool to find other kiwis who are into this! Whats up with the Fluent interface for building a user seems like overkill and it would possibly allow the user object to be invalid before saving it to the repository? Does the repository validate, or at this point is it up to Nhibernate to kick in with mappings and constraints?

    Thanks for the article
    Jake

  4. Yeah sounds good, it seems there is more happening in welly than auckland in terms of awesome .net development. I will be reading your blog so cheers for the links.

    Have you checked out this blog => http://weblogs.asp.net/rashid/default.aspx
    If your doing mvc I recommend using the telerik asset management stuff for js + css compression and combining.

    Cheers
    Jake

  5. good work Richard!
    I’m in minority using FluentValidation, but I prefer it by all means!

    Too bad all the other frameworks, even Microsoft (in upcoming MVC v2) is using attribute-based validation, which is forcing me to use partial classes = no go.

  6. Also attribute based validation is horrible when you want to anything that is not constant based. For example a simple “Date cannot be in the past” is impossible to do with attributes.

  7. It’s amazing. I’ve just implementing like this in my app last night :) wow, wow, wow :)

    Difference is with creating domain model. I’m using AutoMaper instead of builders and form data (ViewModel) is passed to service via interface (IUserRegistrationForm instead of UserRegistrationForm).

    Do you have any possibility to view my desktop? ;)

  8. Dario – we have tried using automapper in the reverse direction (i.e. updating domain model from the view edit model) but found it doesn’t really work because the domain model doesn’t have setters.

  9. Your example is in this post exactly suits to use AutoMapper but when you don’t have setters AutoMapper is (almost) not usable.

    AutoMapper in my project is something like utility, not required convention. It helps me with simple models. :)

    PS
    I’m glad to see, that my ideas are so good (I think ;)).

  10. I agree with the overall flow, but I’m not convinced whether your ViewModels (UserRegistrationForm) should be in the ApplicationServices (UserRegistrationService) layer.

    What if your ApplicationServices layer is shared by more than one client? For example, in addition to your Web MVC client, you want to add a Win console app. If this were the case, you ApplicationServices layer would have to know about ViewModels from both client apps, which seems smelly to me.

    What do you think?

    In any case, thank you for the informative post!

  11. Martin – good question. In that case you would put a formal API in place that returns API-specific DTOs (effectively the same as a view model, without any specific UI in mind). For example, Twitter’s “get user” method might return the user’s details combined with their last ten tweets.

    Then each client queries/send commands to the API, and maps its responses to their own UI-specific view models.

    There should hopefully be a blog post about this (API-driven applications) in the next month or three :)

  12. You don’t consider throwing exceptions for validation a bit heavy handed? Considering exceptions should really only occur in “exceptional” circumstances and the cost associated with throwing and catching them?

Comments are closed.