Dando continuidade ao artigo anterior (documentação, requests e responses) sobre padrões e boas práticas de web api, hoje venho discorrer sobre o tema middlewares.
Request Pipeline
Um middleware nada mais é do que um trecho de código adicionado ao pipeline do request através de delegates. Você sabia que é possível executar ações antes da execução do request e também ações depois da execução do request? É isso mesmo. Quando você executa o request é possível escrever um código global (dentro do escopo web-api) para interceptar o momento que o request chega ou mesmo o momento posterior ao processamento do request, quando a response já começa a ser escrita.
É possível configurar diversos middlewares em um mesmo pipeline request, inclusive a ordem que os middlewares são registrados será a ordem na qual serão executados. Lembre -se que os middlewares serão ativados a cada request e não somente uma vez.
Middleware
Se você estiver construindo uma aplicação que precisa de rastreabilidade, você poderia criar um middleware com ações de pre request para logar as informações do request atual, informações como o payload ou qualquer outro dado.
Aqui vou demonstrar como criar um middleware para tratativa de erros. Chega daquele cenário de colocar try catch em todos os seus métodos. Vamos criar um middleware para tratar exceptions (de qualquer tipo) em um lugar só da aplicação. Um interceptor global de erros.
Continuaremos a partir do código da API que criamos no artigo anterior.
Ainda na ProductController, adicione o seguinte método para que possamos testar a utilidade do ErrorHandlerMiddleware:
/// <summary>
/// Complex method
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("Exception")]
[ProducesResponseType(500)]
public IActionResult ComplexMethod()
{
throw new HttpRequestException();
}
Um método simples somente para exemplificar que em um cenário inesperado é possível que sua aplicação retorne exceções não tratadas. Neste caso, veja o que acontece quando ocorre uma exceção não tratada no método ComplexMethod.
Pois é! O que você acaba de ver é uma response HTML em uma API Rest. Isso é tão ruim que chega a ser um anti-pattern. Veja como você pode transformar essa response em um belo e tratado JSON utilizando um middleware. Para isto, vamos primeiramente padronizar as responses de exceptions!
using System;
namespace Patterns.WebAPI.Middleware
{
public class ExceptionResponse
{
public string Source { get; private set; }
public string Message { get; private set; }
public string StackTrace { get; private set; }
public ExceptionResponse(Exception exception)
{
Source = exception.Source;
Message= exception.Message;
StackTrace = exception.StackTrace;
}
}
}
A implementação do middleware é realmente simples. A única coisa que você precisa fazer é implementar a interface IMiddleware para que posteriormente possa utilizar o método UseMiddleware para registrar o ErrorHandlerMiddleware no pipeline do request.
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace Patterns.WebAPI.Middleware
{
public class ErrorHandlerMiddleware: IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception exception)
{
await HandleExceptionAsync(context, exception);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var exceptionObject = new ExceptionResponse(exception);
var exceptionSerialized = JsonConvert.SerializeObject(exceptionObject);
if (!context.Response.HasStarted)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
await context.Response.WriteAsync(exceptionSerialized);
}
}
}
}
Já com o middleware criado, basta registrá-lo em seu container de dependências. Se você estiver utilizando o modelo de injeção de dependência nativa do Asp.Net Core, veja abaixo como fazer:
using ...
namespace Patterns.WebAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
...
/* Registration */
services.AddTransient<ErrorHandlerMiddleware>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
/* Usage */
app.UseMiddleware<ErrorHandlerMiddleware>();
app.UseMvc();
}
}
}
Agora, aprecie o novo resultado de um request com exceção.
Nota: Você pode adicionar uma tratativa diferente para cada tipo de exception dentro do seu middleware. É só utilizar a imaginação.
Por hoje é isso, galerinha! Espero que eu tenha ajudado de alguma maneira! Até mais e um grande abraço!
Quer saber mais sobre boas práticas de web api, docker, docker compose, testes unitários e de integração? Baixe meu e-book free!