Previously we’ve been talking about DTO, mostly in WebAPI controllers, but it can be used everywhere. Today last of basic topics that every developer should know about, input validation. I’m not going to try to convince you that validation is important in places where external client will put his data. It should be obvious.
What’s important during validation?
The most important is to block unwanted calls, we can achieve it by throwing exception whenever something in user data is unacceptable. Tools for creating validators will take care of it. What you should keep in mind is to let user know what happened. Personally, I can’t stand when I fill some form and then error page is presented to me without any information. What happened? No one knows, you have to try and change something or fill every field and have faith. For me it’s deal breaker, that’s why it’s important to let your user know what was wrong. Fortunately if you’ll use Fluent Validation it will do that for you as well.
What’s Fluent Validation
Fluent Validation is nuget package that will let you create easily validator for your models. I’ll show you how to use it in controllers, but you can use it everywhere, if you need to validate something. It’s especially good with controllers input validation, because you can configure FV so you won’t even know it’s there. Let’s start from beginning.
Setup Fluent Validation
I must say, configuring FV was one of the most difficult thing when I started. We want to plug FV in ASP.NET model validation. Additionally we need way to create validator on the fly so they can be injected into controllers. By plugging FV into model validation we won’t need to call validation ourselves.
At the beginning make sure you’ve got FluentValidation.AspNetCore
installed in WebAPI project. After than we’ll plug in FV, by adding
services
.AddMvc()
.AddFluentValidation();
to public void ConfigureServices(IServiceCollection services)
. Make sure that AddFluentValidation method is called after AddMvc. It will change some stuff in Mvc configuration so it will override some of these options. Now FV will be called when you call ModelState.IsValid
.
Previously it needed more work to work this way. DTOs that we wanted to validate had to implement IValidatableObject interface and there we had to create validator and call validation, finally we had to return errors. I’m just telling it to convince you that you shouldn’t depend on one solution for problem created years ago. It will work, but most likely it will force you to do more work.
public class StudentUpdateRequestDataTransferObject : IValidatableObject
{
public int Id { get; set; }
public int CourseId { get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validator = new StudentUpdateRequestDtoValidator();
var result = validator.Validate(this);
return result.Errors.Select(item => new ValidationResult(item.ErrorMessage, new[] { item.PropertyName }));
}
}
It’s nothing fancy, but it’s still more code that you have to copy over to every object you want to validate.
Next step is creating validators. You should check project’s github documentation, because FV has great documentation that presents examples for every function. You can find documentation here.
public class StudentCreateRequestDtoValidator: AbstractValidator<StudentCreateRequestDataTransferObject>
{
public StudentCreateRequestDtoValidator()
{
RuleFor(student => student.Name).NotEmpty().WithMessage("Name cannot be empty");
RuleFor(student => student.CourseId).NotNull().WithMessage("CourseId cannot be null");
RuleFor(student => student.CourseId).GreaterThan(0).WithMessage("Id value must be greater than 0");
}
}
Above you can see example validator. As you can see it’s clean, and you can read validators like sentence, fluently. That’s why it’s called fluent validation. You can see that I’ve added WithMessage("")
method, but it’s not needed, unless you want to create some super custom message. FV will return generic information with property name, so even if I would remove WithMessage()
methods, user would know what happened, because FV will return almost identical error message. What’s better, FV will return localized error message. That means for US client it will return english text, for Poland client, polish etc. You need to check which languages are supported currently.
Now we have only one problem, how to inject validator. Unfortunately you’ll have to create more code. We need way to create correct validator, that means we need to implement factory for validators. It’s common code you can find everywhere.
public class ValidatorFactory : IValidatorFactory
{
private readonly IComponentContext container;
public ValidatorFactory(IComponentContext container)
{
this.container = container;
}
public IValidator<T> GetValidator<T>()
{
return (IValidator<T>)GetValidator(typeof(T));
}
public IValidator GetValidator(Type type)
{
var genericType = typeof(IValidator<>).MakeGenericType(type);
if (container.TryResolve(genericType, out object validator))
{
return (IValidator)validator;
}
return null;
}
}
Now we only need to configure DI Container. We’ll use Autofac in out example. Mind that our factory is registered as Singleton.
builder.RegisterType<ResetPasswordValidator>().As<IValidator<ResetPasswordDto>>();
builder.RegisterType<ClaimValidator>().As<IValidator<ClaimDto>>();
builder.RegisterType<ValidatorFactory>().As<IValidatorFactory>().SingleInstance();
With that FV is configured and ready to use. As you can see it’s much harder than AutoMapper, but when you configure it this way you won’t even know that FV is somewhere in there, that’s because our newly created validator will be called on ModelState.IsValid
and thanks to Net.Core 2.1 we have [ApiController]
attribute which will call ModelState.IsValid
with error handling behind the scene.
Using outside Asp.Net Core controllers
Example above showed how to use FV inside controllers, you can use FV whenever you want thou. Using it outside FV is also easy, but it will require call from you. You’ll need to create validator and then call validate method on object that you want to validate. Consider example below.
var student= new Student {Name = "Bob"};
var validator = new StudentValidator();
var result = validator.Validate(student);
It’s pretty easy. You’ll have to call method by yourself, but that’s the biggest problem. Apart from that, it’s nice and clean solution.
Outro
All you’ve seen here can be done without 3rd party library, but why shouldn’t you waste time to reinvent wheel. That would be more code to test, while you’ve got tested library here. Additionally FV helps you test validators, because it contains methods just for it.