How to Build a Web API ASP.NET Core 6 (Part 3)
Andrés Díaz
Posted on February 16, 2023
In the previous post, we discussed how to build a web API applying useful patterns like CQRS and Mediator Pattern using this package MediatR.
At this time, we are ending this serie with Fluent Validations and Middlewares.
Before starting with visual studio, we explain what kind of problems resolve Fluent Validations and Middlewares.
What is FluentValidations
It is a popular Net Library used for building strict validation rules fluently. Allow easily configure rules using lambda expression and furthermore unlike the data annotations provided in .net allow decoupling the data validations and logic from your models reducing the complexity to create rules.
Why to use Fluent Validations
In this case we want to use to define rules for validate our commands using validators and throws our validations using pipelines behaviors.
Middlewares
The middleware controls how our application responds to http requests , and allows us to change the behavior when our application throws errors.
Get more details here.
Why to use Middlewares
In this case we have to use middlewares to handle validation exceptions a take the control of responses when the failures occurred.
Step 1
We are building our validators for the commands models.
Before to start Apply those packages:
*FluentValidation
*FluentValidation.DependencyInjectionExtensions
The next step is to start defining the validator for the command models.
In this case we have this command model:
*CreateNewCustomerCommand
Create a folder Validators and Create the class CreateNewCustomerCommandValidator for the command CreateNewCustomerCommand like this.
CreateNewCustomerCommandValidator
using FluentValidation;
using WebApiCustomers.Commands;
namespace WebApiCustomers.Validators;
public class CreateNewCustomerCommandValidator : AbstractValidator<CreateNewCustomerCommand>
{
public CreateNewCustomerCommandValidator()
{
RuleFor(x => x.customerToCreate.FirstName)
.NotEmpty().WithMessage("{PropertyName} no puede estar en blanco");
RuleFor(x => x.customerToCreate.LastName).NotEmpty();
RuleFor(x => x.customerToCreate.EmailAddress).NotEmpty();
}
}
In this case for the field FirstName we are adding a custom message in spanish.
Step 2
The next step is to create our pipeline behavior to get all validation failures and throw a validation exception.
using AutoMapper.Configuration;
using FluentValidation;
using MediatR;
namespace WebApiCustomers.PipelineBehavoirs;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators.Select(v => v.Validate(context))
.SelectMany(e => e.Errors)
.Where(x => x != null)
.ToList();
if(failures.Any())
{
throw new ValidationException(failures);
}
return next();
}
}
Step 3
The next step is to create our middleware for catch all exceptions and format the validation exceptions using the interface IMiddleware.
using FluentValidation;
using FluentValidation.Results;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace WebApiCustomers.Middlewares
{
internal sealed class ExceptionFluentValidationMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleException(context, ex);
}
}
private static async Task HandleException(HttpContext context, Exception? exception)
{
string jsonContent = String.Empty;
if (!(exception is ValidationException validationException))
{
jsonContent = JsonSerializer.Serialize(new {
Errors = new { Detail = "Internal error"}
});
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
else
{
var errors = validationException.Errors.Select(err => new
{
ErrorMessage = err.ErrorMessage,
PropertyName = err.PropertyName.Split(".")[1].ToString()
});
jsonContent = JsonSerializer.Serialize(errors);
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(jsonContent, encoding: Encoding.UTF8);
}
}
}
Step 4
And the last step we are implementing in the program class our middleware like this.
app.UseMiddleware<ExceptionFluentValidationMiddleware>();
Recaps
On these topics , we are implementing the validation on elegant way for our command models with one of the best validation library Fluent Validations.
On the other hand we learned how to use middleware to handle response for handle global exceptions.
In the end, with learned about these topics:
*Fluent Validations with Pipeline Behaviors
*Middlewares on Net 6
If you enjoyed this article, please subscribe and follow this series about building clean web API Net Core 6.
Posted on February 16, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024