Entendendo o que são middlewares em uma aplicação ASP.NET
Published: 2024-01-02 05:19:35

## O que é um middleware?

Um middleware nada mais é do que um componente que está contido em uma pipeline de uma aplicação capaz de lidar com requests e responses de uma solicitação HTTP, ou seja, todo o percurso que uma request percorre dentro da aplicação passa por um middleware, desde a recepção até sua resposta.

Quando criamos um middleware em .NET estamos criando nada mais nada menos do que um delegate, ou seja, estamos criando um ponteiro para uma função de forma segura.


---

## Como funcionam os middlewares em uma aplicação WEB?

Uma aplicação [ASP.NET](http://ASP.NET) contem uma sequencia de delegates ou melhor dizendo middlewares que são chamados um após o outro.

Um diagrama clássico que sempre vemos quando falamos de middleware é este aqui:


![Diagrama mostrando o fluxo de uma request com vários middlewares configurados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lm3eb3byn3di3vf2nrkw.png)

A seta preta indica o caminho que ocorre o processamento, cada middleware pode executar operações antes ou depois do próximo middleware.

É aqui que as coisas começam a ficar legais, dependendo do tipo de processamento que queremos realizar, podemos configurar um middleware no começo da pipeline ou no final, tudo depende da funcionalidade que estamos criando.

Uma coisa importante que devemos nos atentar quando estamos trabalhando com middlewares em uma aplicação [ASP.NET](http://ASP.NET) é a ordem de configuração dos middlewares.

A ordem que configuramos os middlewares é extremamente importante é ela quem define a ordem em que eles serão executados na request e consequentemente a ordem inversa em sua response.

Um exemplo clássico de middleware seria um handler de erro global, nesse caso o recomendado é que esse middleware de erro seja o primeiro a ser executado, dessa forma, conseguiremos capturar todos os erros que ocorrem na aplicação.

Hummmmm, serio Willian? Porque? 

Pensa comigo e vamos olhar o diagrama novamente, o primeiro middleware do pipeline pode ou não executar alguma ação, depois disso ele é encarregado de chamar o próximo middleware através do metodo next(), dessa forma ele fica esperando o próximo middleware encerrar a sua execução e retornar alguma resposta. 

O próximo middleware pode chamar outro e o próximo chamar outro e por ai vai…

Deu para perceber porque um middleware de erro deve ser o primeiro da pipeline? Quando todos os middleware forem executados o nosso middleware de erro vai ser o ultimo da cadeia de execução, dessa forma ele vai ser capas de “capturar”, todas as exceções que ocorrerem durante a pipeline de execução da request.

---

## Aprendendo a criar middlewares

Vamos começar criando nossa aplicação. Acessando a sua IDE favorita (Aqui eu uso o JetBrains Rider) crie uma aplicação ASP.NET Web API.


![Tela do jetbrains rider criando uma aplicação asp.net web api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmd59de5ac40pxlzebt3.png)

Com o projeto aberto vamos criar nosso middleware de erro customizado. Essa classe é igual a qualquer outra classe em C#, mas para ela funcionar corretamente precisamos nos atentar a alguns nomes e propriedades, vou explicar cada uma a frente.

Segue classe do nosso middleware de tratamento de erros.

```csharp
using System.Net;

namespace Middlewares;

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            await HandlerErrorAsync(httpContext, ex);
        }
    }
    
    private async Task HandlerErrorAsync(HttpContext context, Exception ex)
    {
        context.Response.ContentType = "application/json";
        string mensagemErro;

        switch (ex)
        {
            case ArgumentException or ArgumentNullException:
                context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
                mensagemErro = "Argumentos inválidos";
                break;
            default:
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                mensagemErro = "Opa, um erro inesperado aconteceu no servidor";
                break;
        }

        await context.Response.WriteAsync(mensagemErro);
    }
```

Nesta classe temos duas coisas extremamente importantes, a propriedade RequestDelegate e o método InvokeAsync.

**RequestDelegate:** Essa propriedade é responsável por armazenar uma referencia para o próximo middleware. Em todo middleware que criamos, temos a opção de chamar o proximo middleware após executar uma operação ou não. A propriedade RequestDelegate é gerenciada pelo próprio framework que fornece toda a estrutura de pipeline de middlewares.

**InvokeAsync:** Método principal que é responsável por executar o processamento do middleware. Este método é chamado automaticamente pelo ASP.NET quando uma solicitação HTTP é executada, ou seja, não precisamos chamar esse método de forma explicita, ele será invocado no momento necessário durante o processamento de uma pipeline de middlewares.

Repare tambem que colocamos um trycat quando chamamos o próximo middleware, fazendo isso conseguimos garantir que qualquer exception que acontecer nos próximos middleware vamos conseguir capturar.

Mas Willian, o que ganhamos com isso? 

Já viu aqueles códigos onde existem vários trycat para tratar cenário de erros? 

Muitas vezes fazer os tratamentos de forma individual em cada parte do código torna a nossa aplicação muito complexa, com baixa legibilidade e possivelmente com varios pontos de manutenção.

Criando um middleware para tratamento de excessões, todo esse código pode ficar centralizado em uma unica classe responsável por tratar erros, com isso temos muito mais semântica, uma legibilidade melhor, menos complexidade ciclomática e por ai vai... O código fica muito melhor.

Mas cuidado hein não é bala de prata, analise seu caso e se fizer sentido, acredito que esse post te ajude a implementar.

---

É muito comum quando se criar um middleware em uma aplicação ASP.NET criar um método de extensão para podermos configurar o middleware, isso garante um pouco mais de organização e legibilidade de código na nossa classe Program e alguns outros benefícios quando temos que configurar mais algumas coisas, vamos implementar essa classe.

```csharp
public static class MiddlewareExtension
{
    public static IApplicationBuilder UseGlobalErrorHandler(this IApplicationBuilder app)
    {
        return app.UseMiddleware<ErrorHandlerMiddleware>();
    }
}
```

Após isso basta chamar esse método de extensão na classe Program. Lembre-se a ordem que configuramos um middleware é extremamente importante e para esse nosso cenário de erro ele deve estar configurado como primeiro middleware do pipeline de execução.

Segue a implementação na classe Program:

```csharp
using Middlewares;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

app.UseGlobalErrorHandler();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/middleware/erro", () => { throw new ArgumentException(); })
    .WithOpenApi();

app.MapGet("/middleware", () => "Sucesso")
    .WithOpenApi();


app.Run();
```

Repare tambem que criamos dois endpoint com minimal API para testar os cenários de erro e de sucesso para a execução dos nossos testes.

## Executando a aplicação

Vamos executar a aplicação e reparar o que acontece com o middleware que criamos e configuramos em nossa aplicação.

Ao inicar a aplicação sera aberta uma pagina do swagger com dois endpoints configurados:


![Imagem do swagger com dois endpoints do tipo GET criados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dkwd7swzaqimsbq1tpx1.png)

Vamos abrir nossa IDE e colocar um breakpoint para visualizar o que acontece com a aplicação quando realizamos uma chamada HTTP.

Abra o nosso middleware customizado e coloque o breakpoint na linha 22 onde capturamos a exception no catch do metodo InvokeAsync.


![Código demonstrando o breakpoint na linha 22](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v83gy6our1cs4xfrxdz0.png)


Reparem que ao chamar o endpoint com a rota "middleware" ele apenas executa e retorna sucesso na requisição, ou seja, nenhuma exception foi lançada na nossa aplicação, o middleware de erro que criamos apenas chamou o proximo e na resposta da nossa solicitação ele apenas retornou a informção, sem executar processamento algum nesse cenário de sucesso. 

Agora reparem que ao executar o endpoint "middleware/erro" uma excessão é lançada:


![Excessão de argumentexception](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/acvu5iv5yvpopnaeomhm.png)

Como não criamos nenhum trycat nesse ponto, essa execessão vai percorrer a aplicação até encontrar um trycat que possa captura-lá. No nosso caso esse tryecat esta na nossa classe de midleware de erro.


![Excessão capturada no trycat](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djgxsuowl9jx1vz2mjsa.png)

Repare que a execução esta pausada no método "HandlerErrorAsync", método que criamos para tratar as excessões da nossa aplicação.

Ao executar a aplicação passo a passo, vamos reparar que a excessão será tratada como uma "ArgumentException" e retornar um erro 400 com uma mensagem "Argumentos Inválidos".


![Erro 400 com mensagem de erro "argumentos inválidos"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egy8y17yxvivprs0c3dm.png)

## Encerrando e considerações finais

Ao longo desse post conseguimos entender o que é um middleware em ASP.NET, como criar um middleware, quais os pontos que devemos ter atenção no momento de criação e de quebra vimos uma solução simples, mas muito funcional sobre tratamento de erro de forma global.

Claro que a implementação foi bem simples afim de facilitar o entendimento dos conceitos que foram abordados, mas com certeza podemos evoluir esse post em vários outros assuntos.

---

Aqui vai um desafio, eu tenho vários vídeos sobre injeção de dependencia em .NET no meu canal do [youtube](https://www.youtube.com/playlist?list=PLelKrgw43Tu1jVd6Hr9coPT-AYMC32il1), um middleware em ASP.NET ele tem o tempo de vida da aplicação, se injetar uma dependencia configurada como sccoped em um middleware, como essa dependecia irá se comportar? 

Deixa ai nos comentários o que vocês acham que vai acontecer, mais pra frente vamos abordar essa assunto com um post mais detalhado.

Não se esqueçam de me seguir em minhas redes sociais:

- [youtube](https://www.youtube.com/channel/UC0Vo6yL26XaraIjak87jDww)
- [instagram](https://instagram.com/wmscode/)
- [github](https://github.com/willianmenezes)

Qualquer dúvida é só deixar nos comentários, abraços e até a próxima.