How to Build a Web API ASP.NET Core 6 (Part 3)

learnwithandres

Andrés Díaz

Posted on February 16, 2023

How to Build a Web API ASP.NET Core 6 (Part 3)

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();          
    }
}

Enter fullscreen mode Exit fullscreen mode

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();

    }     
}

Enter fullscreen mode Exit fullscreen mode

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);

        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4

And the last step we are implementing in the program class our middleware like this.

app.UseMiddleware<ExceptionFluentValidationMiddleware>();

Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
learnwithandres
Andrés Díaz

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