Complex validation in MVC and web apps, using FluentValidation

Complex validation in MVC and web apps, using FluentValidation

Most projects require some sort of form input. And additionally, those form inputs need to be validated. For the validation, ASP.NET developers have a lot of options, but the default one is pretty good. That is the DataAnnotations. Those give you the option of specifying a lot of the validation requirements declaratively:

[Required(ErrorMessage = "A Name is required.")]
[StringLength(100)]
public string Name { get; set; }

The upside of this is that you get out of the box support for a lot of common checks, but also stuff like client-side validation. But sometimes you need a bit more. Specifically, you validation logic might need to go into the database (through your service layer, of course), and make sure that no user has already registered with that exact email address. That type of validation was a requirement in most all of the (serious) projects I did in the last couple of years. And when that is the case, I turn to FluentValidation.

Quick Introduction to FluentValidation

What FluentValidation does is it allows you to use a fluent interface and lambda expressions for building validation rules. That means that you can now express your validation rules like this:

public class CashierValidator : AbstractValidator<CashierViewModel>
{
    private readonly IPrincipalService _principalService;


    public CashierValidator(IPrincipalService principalService)
    {
        _principalService = principalService;
        RuleFor(x => x.PinCode)
            .Must(BeUniqueWithinTenant);
    }


    private bool BeUniqueWithinTenant(CashierViewModel model, string pinCode)
    {
        if (!TenantTicket.Instance.TenantKey.HasValue)
        {
            Log.As.Error("There was no tenant key defined, yet the cashier validator was called.");
            return false;
        }


        var idTenant = _principalService.GetTenant(TenantTicket.Instance.TenantKey.Value);
        var cashier = _principalService.GetCashierByPinCode(pinCode, idTenant.Id);


        if (cashier == null) return true;


        if (model.Id.HasValue)
            return model.Id == cashier.IdCashier; // it's the same user, so we're OK


        return false;
    }
}

What the above code does, is attach rules to the CashierViewModel (this tells it so: AbstractValidator<CashierViewModel> ) and it defines one specific rule, that the PIN code must be unique across the tenant - so, no two cashiers within a single tenant can have the same PIN code. It's a business requirement. But our validator is in the presentation layer, and to make sure this rule is obeyed, we need to go down to the database layer across the service layer. Because of Dependency Injection (more on that soon) our validators are instantiated with all the dependencies injected into them (CashierValidator(IPrincipalService principalService) ).

How does that work with the Data Annotations attributes?

It doesn't. For one, using both causes problems. Specifically, if you submit a form with both validations, one of those sets of rules will get evaluated first, and if there is an error, the other set won't even be checked. That means the user will be presented with an incomplete list of validation errors.

Another great point is that I want my code & architecture to be consistent. That means that I don't really want some part of my validation logic to be written in the Validator classes, and another one on the models.

Hammering the point home is that sometimes, I don't use ViewModels (I have a fairly pragmatic approach there, if I don't need it, I don't add it) - which means that I would have to decorate my "domain models" or worse, my database objects with some validation logic that will be used on the UI, or implement validation using these annotations on the bottom layers. It makes much more sense to reuse the validation logic across the layers. FluentValidation allows you to do that as well and the code actually makes sense.

Integrating FluentValidation into the UI...

If you're anything like me, you want as much as possible done behind the scenes, automagically. One such thing for me, is drawing the required asterisk next to required fields. Like this:

Screen Shot 2015-09-08 at 21.28.44

 

So, to do this, you can basically put something like this in your Editor Templates:

@using Saopnet.Web.Infrastructure.Extensions
@{
    var validation = String.Empty;
    if (ViewData.ModelMetadata.IsRequired)
    {
        validation = "required";
    }
}
<div class="form-group @validation">
    @Html.LabelFor(x => x, Html.GetString(ViewData.ModelMetadata.DisplayName).ToString())
    @Html.TextBoxFor(x => x, ViewData)
    @Html.ValidationMessageFor(x => x)
</div>

So now, every time you render an editor for a model property (@Html.EditorFor(x => x.Name)) you get a nice input box with all the proper attached styles. The required class in our case renders the asterisk:

.required {
        label:after {
            .padding-left(5px);
            content: "*";
            display: inline;


            color: @color-button-3C;
        }
    }

The above code is LESS. If you're not familiar with it and you're doing any front-end, go look at it, now!

However, if you don't have a [Required]  attribute on your model property, the ModelMetadata.IsRequred  won't get set by default. Unless, you trick it and write your own ModelMetadataProvider. In that, you can actually look inside the rule for that particular model and force it to set that. Here's my code for it:

public class CustomUiHintModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private readonly Startup.ModelValidatorFactory _factory;


    public CustomUiHintModelMetadataProvider(Startup.ModelValidatorFactory factory)
            : base()
    {
        _factory = factory;
    }


    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
        {
            var b = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);


            if (containerType != null)
            {
                var validator = _factory.GetValidator(containerType);


                if (validator != null)
                {
                    var validators = validator.CreateDescriptor().GetValidatorsForMember(propertyName);
                    b.IsRequired = validators.OfType<FluentValidation.Validators.NotEmptyValidator>().Any() ||
                                   validators.OfType<FluentValidation.Validators.NotNullValidator>().Any();
                }
            }


            return b;
        }
}

The above code is not perfect, far from it. For example it has a double iteration over the validators, etc., it also only supports the IsRequired  part, but you can get the general idea from it.

So there you go, that's how I use FluentValidation to replace DataAnnotations on my ViewModels and still manage to use the underlying functionality (ModelMetadata) to get as much of the awesome done automagically.

Show Comments
Mastodon Verification