Podemos entender um middleware como um componente (nesse caso uma classe) adicionada no pipeline de execução do Asp.Net entre o Request e o Response. Ao fazer uma requisição, é inciado um fluxo que executa cada um dos middlewares que são encontrados no caminho até o momento da volta ou até encontrar uma finalização por um algum middleware. A figura abaixo ilustra bem esse processo de execução dos middlewares.

Um middleware geralmente tem um propósito bem definido, mas vale lembrar que ele pode não executar nenhuma tarefa. No Asp.Net Core temos vários middlewares responsáveis pelas mais variadas tarefas como tratamento de erros, autenticação, manipulação de arquivos estáticos, política de cookies etc.
A ordem
Ao adicionar um middleware, é de extrema importância que prestemos atenção na ordem em que os configuramos. A execução dos middlewares é feita exatamente na ordem que os adicionamos e o retorno segue a mesma sequência na ordem inversa. Essa ordem pode afetar diretamente a nossa aplicação no que tange aspectos como performance, segurança e funcionalidades.
Configurando um middleware
Na classe Startup.cs há um método chamado Configure. Esse método permite a configuração (adição no pipeline) dos middlewares que iremos utilizar. Essa configuração se dá através da instância de IApplicationBuilder.
public class Startup { public Startup() { } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.Run(async (context) => { await context.Response.WriteAsync("My first middleware"); }); } }
As linhas destacadas, representam nosso middleware, nesse caso um middleware anônimo. Note que temos uma instância de IApplicationBuilder chamada app a qual nos permite a adição dos middlewares.
Entendendo Run, Use e Map
Podemos utilizar qualquer um destes três métodos de extensão na utilização dos middlewares. Por serem convenções, é importante manter o padrão quando criamos nossos middlewares.
Run => adiciona um middleware terminal ao pipeline do Asp.Net Core. Isso significa que após sua execução, ocorrerá uma interrupção do fluxo no pipeline causando um curto-circuito e nenhum outro middleware será executado.
Use => adiciona um middleware in-line no pipeline – rimou rsrs-. Isso significa que após a execução desse middleware, ele fará uma chamada para o próximo.
Map => cria uma ramificação do pipeline com base no caminho informado. O exemplo abaixo do Microsoft docs, explica bem esse cenário.
public class Startup { private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } private static void HandleMapTest2(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); }); } }
Os resultados do código acima seriam:
Solicitação | Resposta |
---|---|
localhost:1234 | Saudação do delegate diferente de Map. |
localhost:1234/map1 | Teste de Map 1 |
localhost:1234/map2 | Teste de Map 2 |
localhost:1234/map3 | Saudação do delegate diferente de Map. |
Criando um middleware
Nosso middleware não será um middleware anônimo, faremos o uso de uma classe reutilizável que junto com a classe do middleware, forma o que chamamos de componentes do middleware.
Uma classe middleware deverá atender alguns requisitos:
1 – possuir um construtor público que aceite um argumento do tipo RequestDelegate. Esse atributo – junto com o construtor – fará a manipulação de requisições HTTP e irá chamar o próximo middleware do pipeline;
2 – um método público chamado Invoke ou InvokeAsync onde o primeiro argumento deverá ser do tipo HttpContext e este método deverá retornar uma Task.
Sabendo destes requisitos, vamos criar a nossa classe da seguinte maneira:
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; public class MeuPrimeiroMiddleware { private readonly RequestDelegate _next; public MeuPrimeiroMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { Console.WriteLine("\n\r----- Iniciando meu middleware -----\n\r"); await _next(context); Console.WriteLine("\n\r----- Finalizando meu middleware -----\n\r"); } }
A classe acima já possui todos os critérios para o nosso primeiro middleware. Na verdade essa é a estrutura básica para um middleware, a partir daí poderá ser melhorada conforme seja necessário, mas o “esqueleto” é esse.
O método InvokeAsync irá executar uma tarefa – nesse caso escrever a mensagem “Iniciando meu middleware” – e fazer a chamada para o próximo middleware (await _next(context)). No retorno, irá finalizar o middleware com a mensagem ” Finalizando meu middleware“.
O próximo passo é criar a classe de reutilização que permitirá através de um método de extensão, a configuração do nosso middleware no pipeline do Asp.Net Core.
public static class MeuPrimeiroMiddlewareExtension { public static IApplicationBuilder UseMeuPrimeiroMiddleware(this IApplicationBuilder builder){ return builder.UseMiddleware<MeuPrimeiroMiddleware>(); } }
A classe acima, é uma classe estática bem simples e com um único método estático que adiciona através do método UseMiddleware um tipo, – nesse caso a nossa classe MeuPrimeiroMiddleware – ao pipeline de execução.
Agora no método Configure, basta que adicionemos nosso middleware conforme o código abaixo:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMeuPrimeiroMiddleware(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); }); }
Na linha em destaque (linha 19) temos nosso middleware já adicionado e o resultado da execução podemos conferir na imagem abaixo.

Podemos verificar que após o início do nosso middleware, onde é escrito o texto “Iniciando meu middleware” chamamos o próximo middleware, que também faz uma chamada para o próximo e assim continua até o último, onde inicia o retorno na ordem inversa.
Fonte: Microsoft Docs