Following my previous post of Introducing CQRS in the architecture, in this post I’m going adding validation using Fluent Validation across the project.
The source code of this post is on GitHub. Because one single post is too long, I have created the following posts:
- Architecting ASP.NET Core applications
- Setting up the application core
- Introducing CQRS in the architecture
- Adding Validation using Fluent
- Creating the infrastructure project
- Adding an API using ASP.NET Core
- How testing the application code
- How adding an UI built in Blazor
- Improving on the application’s behaviour
Table of contents
So, we already got some of the business rules. When adding an event, the requirement is that the new event has at least a the name, price, and date. We have created the entity for event. So, if I ask you to implement these business rules, I think your default solution would be adding attributes on the Event
class.
public class Event: AuditableEntity
{
public Guid EventId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public int Price { get; set; }
}
Data annotations are very often used and are an easy solution. Now, while they work, I would advise against using them. First, for the persistence ignorance principle, let’s try not to go away from POCOs for our domain entities. Also, I believe the data validation code doesn’t really belong there.
Secondly, not every business rule can be solved with just data annotations. Required
string length, they work fine, but if we need to write combined rules, say if the name of the event contains 2020, then the date should not be in 2021, then data annotations simply won’t cut it.
Introducing Fluent Validation
So, we’ll leave our entities alone and go another route for validation. I would like to bring in another commonly‑used framework: Fluent Validation. Again, Fluent validation is an open-source project and free to use. There is a NuGet package to add to our application architecture. Using Fluent Validation will lead to maintainable validation code since it uses code, lambda expressions to be exact, to write our validation logic.
Thus, we can write our validation logic entirely separate from the domain entities just like we want it. We can use fluent validation from our code project as well. We’ll introduce the validation logic in the handle method of the request handler. Also, you’ll see that the validation logic will be specific per Feature folder. Again, this may look a bit weird as I may need to write validation logic for the same entity twice. That’s the price I want to pay for the flexibility I’m getting this way.
Explain an implementation
Like I said, validation rules and fluent validation will be written using lambda expressions. So, your validation logic will be contained typically in a class that inherits from a base class that comes with fluent validation called AbstractValidator
, this is, again, a generic class.
public class CreateEventCommandValidator : AbstractValidator<CreateEventCommand>
{
public CreateEventCommandValidator()
{
RuleFor(p => p.Name)
.NotEmpty().WithMessage("{PropertyName} is required.")
.NotNull()
.MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters.");
}
}
The type parameter is the type you’re writing validation rules for here. Then for each rule we want to create, we write an expression like you see here. Once we have our validator written, we can use it from the request handler code.
var validator = new CreateEventCommandValidator();
var validationResult = await validator.ValidateAsync(request);
Here, I’m instantiating our validator first and then I call ValidateAsync
passing it the object I want to validate. Now, how do we then handle validation errors? This is how do we handle in a clean way, any exceptions that are coming back for our application project. So the consumer, like our API later on, knows what happened and can also return the correct message.
Custom exceptions
For that, I will in the application project create custom exception classes. They are part of the core project and are thus known everywhere. The consumer of the core will get back these exceptions and can handle accordingly. I will include a few exceptions, including the NotFoundException
, the BadRequestException
, and the ValidationException
.
NotFoundException
, I’ll be returning when the request, for example, for a non‑existing event is received. BadRequestException
will be used to let the caller know that the received input, for example, to create a new event, is incorrect. ValidationException
, I think, is quite clear, it used to return, well, validation errors. Note that these are not the built‑in exceptions, they’re custom classes.
public class NotFoundException : ApplicationException
{
public NotFoundException(string name, object key)
: base($"{name} ({key}) is not found")
{
}
}
You can see a sample of one of these exceptions, namely, the NotFoundException
, it is a custom class and I let it inherit from the ApplicationException
base class and that is a built‑in class.
Adding Validation and Custom Exceptions
So, so far we talk about adding validation using Fluent Validation in general. Now, let’s return to Visual Studio once more and explore how I’ll add validation to the application architecture. I’m going to show the validation process, as well as custom exceptions. Also, that is a custom response object that we can also return, useful in combination with validation errors.
Now, we can currently create a new event, but I don’t have any validation logic yet. I have no logic in here that checks that the data coming in via the CreateEventCommand
is actually valid. Now, I want to stay away from going to my domain entity, so to event entity, and start adding attributes. So, what I’m going to do instead is I’m going to bring in fluent validation. The application logic will validate my data inside of my application project.
Then, let’s go back first to the application project file and bring in FluentValidation
. So I’ll bring in FluentValidation. This other package here as well, save that, and now we have support to fluent validation in the application project. So, now I want to validate the CreateEventCommand
from the CreateEventCommandHandler
.
Implement CreateEventCommandValidator
For that, I’m going to do that by writing an extra class that will be the CreateEventCommandValidator
. That will contain the validation logic for the CreateEventCommand
. Now to make this into a validator, I need to inherit from the AbstractValidator
. In CreateEventCommand
, the AbstractValidator
is a class that will be added through FluentValidation. Then, in the constructor I’m going to write the validation logic, and I’m going to do that using lambda expressions.
public class CreateEventCommandValidator
: AbstractValidator<CreateEventCommand>
{
private readonly IEventRepository _eventRepository;
public CreateEventCommandValidator(IEventRepository eventRepository)
{
_eventRepository = eventRepository;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("{PropertyName} is required.")
.NotNull()
.MaximumLength(50).WithMessage("{PropertyName} must not exceed 50 characters.");
RuleFor(p => p.Date)
.NotEmpty().WithMessage("{PropertyName} is required.")
.NotNull()
.GreaterThan(DateTime.Now);
RuleFor(e => e)
.MustAsync(EventNameAndDateUnique)
.WithMessage("An event with the same name and date already exists.");
RuleFor(p => p.Price)
.NotEmpty().WithMessage("{PropertyName} is required.")
.GreaterThan(0);
}
private async Task<bool> EventNameAndDateUnique(CreateEventCommand e,
CancellationToken token)
{
return !(await _eventRepository.IsEventNameAndDateUnique(e.Name, e.Date));
}
}
Code explained
So, I’m going to write a validation RuleFor
, in this case, the name of the event, and I’m going to say that it should not be empty. I don’t want this to be an empty string. As you can see in the code, I’ve added a rule that says the name shouldn’t be empty and if it’s empty I’m going to show this message. It also shouldn’t be null, and I’m also specifying the maximum length again with an error message.
Also, I’ve added a validation rule for the date as well as for the price. Then, I have these validation rules wrapped inside of this class, I also now need to use them. Now for that, I’m going to go back to my CreateEventCommandHandler
.
CreateEventCommandHandler
Before I’m actually going to go to my repository and try to map this, I’m going to validate the incoming requests. I’m going to instantiate the CreateEventCommandValidator
, and then I’m going to call the ValidateAsync
method onto our CreateEventCommand
instance, and this will trigger the validation rules defined in the validator.
public class CreateEventCommandHandler :
IRequestHandler<CreateEventCommand, Guid>
{
private readonly IEventRepository _eventRepository;
private readonly IMapper _mapper;
public CreateEventCommandHandler(IMapper mapper,
IEventRepository eventRepository)
{
_mapper = mapper;
_eventRepository = eventRepository;
}
public async Task<Guid> Handle(CreateEventCommand request,
CancellationToken cancellationToken)
{
var validator = new CreateEventCommandValidator(_eventRepository);
var validationResult = await validator.ValidateAsync(request);
var @event = _mapper.Map<Event>(request);
@event = await _eventRepository.AddAsync(@event);
return @event.EventId;
}
}
Now, just triggering those won’t do anything, we still need to check the result of that validation. And the result will be captured in this validationResult
and that will actually contain the list of errors, if any, that are coming back from that validate. But if there are, what do I then do?
As you can see, adding validation using Fluent Validation could be easy enough but how to raise exceptions?
Exceptions
Now, I don’t want to use the default exceptions. Why that is will become clear later on. But I am going to include some custom exceptions inside of my application project that I’m going to use for my application logic. The consumer of my application logic will then be able to react to these custom exceptions.
So, in my application project I’ve now added an Exceptions folder, which contains a couple of custom exceptions, such as the BadRequestException
, that will be used later on when we, for example, encounter wrong input, an event that is going to be created that basically contains invalid input, it might be null or something.
public class BadRequestException: ApplicationException
{
public BadRequestException(string message): base(message)
{
}
}
NotFoundException
, well, that one will be used to return to the user when you’ve asked me to update an event, for example, but I don’t find that event, that’s a NotFoundException
. It’s a custom exception as well.
public class NotFoundException : ApplicationException
{
public NotFoundException(string name, object key)
: base($"{name} ({key}) is not found")
{
}
}
Validation implementation for Event
Now, we are going to be using the ValidationException
. We’re going to throw this exception if validation rules are not met. So, let’s go back to our handler, and in here I’m going to now check if the validationResult.Errors.Count
is larger than null, that means that validations have been broken and so I’m now going to throw my own validation exception passing in that validationResult
.
public class CreateEventCommandHandler : IRequestHandler<CreateEventCommand, Guid>
{
// ...
public async Task<Guid> Handle(CreateEventCommand request, CancellationToken cancellationToken)
{
var validator = new CreateEventCommandValidator(_eventRepository);
var validationResult = await validator.ValidateAsync(request);
if (validationResult.Errors.Count > 0)
throw new Exceptions.ValidationException(validationResult);
var @event = _mapper.Map<Event>(request);
@event = await _eventRepository.AddAsync(@event);
return @event.EventId;
}
}
Now, we have some basic validations in here. So, if you look back at this validator I could have done it using attributes, and you would be right. But, I can do a lot more with fluent validation, things that I cannot easily do using attributes.
Now, I want to validate if the combination of event name and date is still unique in the database, so I’ll need to bring in a repository, the event repository, that’s what you see here. Then, I use a custom validation rule that will be triggered. If it’s broken, I’m going to show this message here saying that an event with the same name and date already exists.
public interface IEventRepository : IAsyncRepository<Event>
{
Task<bool> IsEventNameAndDateUnique(string name, DateTime eventDate);
}
So, to check this rule, I’ll need to go to my repository, so in here I’ve added this IsEventNameAndDateUnique
method on the repository, on the contract of the repository that is, and this rule will now also be validated when I’m calling the validation on my CreateEventCommand
. In the CreateEventCommandHandler
I don’t need to do anything new, I just still call the ValidateAsync
, which will take into account now that new rule.
Create category
Look at the code and I’ve now nicely separated again my validation rules from my business logic, again nicely applied the separation of concerns as well. Now, the last thing I want to show you here is what I am returning from my CreateEventCommandHandler
. As you see in the code, I’m returning just the EventId
, and while that is okay, it might be so that we also want to return the newly created instance in full. Well, that I’ve also included in the sample application, but for categories.
public class CreateCategoryCommand : IRequest<CreateCategoryCommandResponse>
{
public string Name { get; set; }
}
Now, I’ll show you how I can also return a full response for the consumer of my application logic. Again, I have now added a command to create a category, and I’ll quickly go through the code, but it’s very similar. We have, again, a CreateCategoryCommand
, which is very simple, it just contains the name of the to be created category, but notice now what this IRequest
is going to be returning, not simply the GUID of the newly created category, but a CreateCategoryCommand
response.
public class CreateCategoryCommandHandler
: IRequestHandler<CreateCategoryCommand, CreateCategoryCommandResponse>
{
private readonly IAsyncRepository<Category> _categoryRepository;
private readonly IMapper _mapper;
public CreateCategoryCommandHandler(IMapper mapper,
IAsyncRepository<Category> categoryRepository)
{
_mapper = mapper;
_categoryRepository = categoryRepository;
}
public async Task<CreateCategoryCommandResponse> Handle(CreateCategoryCommand request,
CancellationToken cancellationToken)
{
var createCategoryCommandResponse = new CreateCategoryCommandResponse();
var validator = new CreateCategoryCommandValidator();
var validationResult = await validator.ValidateAsync(request);
if (validationResult.Errors.Count > 0)
{
createCategoryCommandResponse.Success = false;
createCategoryCommandResponse.ValidationErrors = new List<string>();
foreach (var error in validationResult.Errors)
{
createCategoryCommandResponse.ValidationErrors.Add(error.ErrorMessage);
}
}
if (createCategoryCommandResponse.Success)
{
var category = new Category() { Name = request.Name };
category = await _categoryRepository.AddAsync(category);
createCategoryCommandResponse.Category = _mapper.Map<CreateCategoryDto>(category);
}
return createCategoryCommandResponse;
}
}
If we go to the handler, and we take a look at what is being returned here, we indeed see a bit more logic. I’m again doing validations, there is a validator, but notice what I’m returning here if everything goes according to plan.
So, I’m going to create here a new CreateCategoryCommandResponse
, which is again a class, part of the CreateCategory
folder, that inherits from the BaseResponse
.
public class CreateCategoryCommandResponse: BaseResponse
{
public CreateCategoryCommandResponse(): base()
{
}
public CreateCategoryDto Category { get; set; }
}
Now, the BaseResponse
is also included as a BaseResponse
that can be used by commands to return a response to the consumer.
Base Response
public class BaseResponse
{
public BaseResponse()
{
Success = true;
}
public BaseResponse(string message = null)
{
Success = true;
Message = message;
}
public BaseResponse(string message, bool success)
{
Success = success;
Message = message;
}
public bool Success { get; set; }
public string Message { get; set; }
public List<string> ValidationErrors { get; set; }
}
In here, I have basic properties, such as Success
, Message
, and optionally, also the list of ValidationErrors
. In the CreateCategoryCommandResponse
that inherits from that BaseResponse
, I’m also returning the newly created category, so I’m including the data here also in the response.
So, in the handler, if everything goes according to plan, then I’m actually going to now return a custom response type for my creation of the category. So this depends a bit on what you want to offer to the consumer of your application logic. If you say well, the ID is enough to know that we have successfully created a new entity, well then you can just return the ID.
Then, I’ve included a custom type that we then return to the caller that contains not only the data, but also possibly validation errors, if any. And that can allow the client, the consumer of your code, to align on always getting back at BaseResponse
and looking for these specific properties, such as the success property.
7 thoughts on “Adding validation using Fluent Validation”