No desenvolvimento de APIs de alta vazão com ASP.NET Core, a eficiência de execução da requisição HTTP é diretamente proporcional à maturidade da sua pipeline. Cada requisição trafega por uma cadeia bidirecional de componentes encadeados — o pipeline de middlewares — interagindo continuamente com a abstração central do runtime: o objeto HttpContext.
O entendimento detalhado da ordem de execução desses componentes e a utilização adequada do mecanismo de Endpoint Routing deixaram de ser itens opcionais para se tornarem requisitos arquiteturais básicos. A má configuração dessa cadeia gera gargalos de latência, vazamentos de memória (memory leaks) por alocações indevidas na Heap e falhas de segurança críticas quando etapas de autenticação ou auditoria são executadas fora de ordem.
Neste artigo, vamos analisar como construir middlewares customizados focados em baixa alocação e como utilizar o Endpoint Routing para inspecionar metadados antes da execução do código final da sua aplicação.
1. Criando Middlewares Customizados de Alta Performance
Existem duas abordagens para a construção de middlewares no ASP.NET Core: por Convenção e por Interface (IMiddleware).
Middleware por Convenção vs. Baseado em Interface
- Por Convenção (Singleton-like / Injeção por Método): O middleware é instanciado uma única vez no momento da inicialização da aplicação (tempo de vida singleton). Dependências transient ou scoped devem ser injetadas diretamente no método InvokeAsync(HttpContext context, ...) e não no construtor. Essa abordagem é ideal para rotas de tráfego intenso por evitar a criação repetitiva de objetos a cada requisição.
- Baseado em Interface (IMiddleware / Scoped): Requer a implementação da interface IMiddleware e o registro explícito do componente no contêiner de Injeção de Dependência (DI). A instância é resolvida para cada requisição (scoped), facilitando o manuseio de dependências com o mesmo ciclo de vida, porém introduzindo uma sobrecarga sutil de resolução no contêiner IoC.
Exemplo Prático: Middleware de Medição de Latência e Tracing
O código abaixo demonstra a criação de um middleware por convenção otimizado para medir o tempo de execução de requisições HTTP e adicionar identificadores de correlação (Correlation-ID) sem alocações desnecessárias.
C#
namespace MasterStack.Architecture.Web.Middlewares;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
public sealed class RequestTracingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTracingMiddleware> _logger;
private const string CorrelationIdHeader = "X-Correlation-ID";
public RequestTracingMiddleware(RequestDelegate next, ILogger<RequestTracingMiddleware> logger)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task InvokeAsync(HttpContext context)
{
// 1. Extrai ou gera o Correlation ID utilizando tratamento de strings sem alocação pesada
if (!context.Request.Headers.TryGetValue(CorrelationIdHeader, out var correlationId) || string.IsNullOrWhiteSpace(correlationId))
{
correlationId = Guid.NewGuid().ToString("N");
context.Request.Headers[CorrelationIdHeader] = correlationId;
}
// Adiciona o cabeçalho na resposta antes que os cabeçalhos sejam enviados ao cliente
context.Response.OnStarting(() =>
{
context.Response.Headers[CorrelationIdHeader] = correlationId;
return Task.CompletedTask;
});
// 2. Medição precisa de tempo com ValueStopwatch ou Stopwatch de alta precisão
var startTimestamp = Stopwatch.GetTimestamp();
try
{
await _next(context);
}
finally
{
var delta = Stopwatch.GetElapsedTime(startTimestamp);
_logger.LogInformation(
"HTTP {Method} {Path} respondeu {StatusCode} em {ElapsedMs:F2}ms [CorrelationId: {CorrelationId}]",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
delta.TotalMilliseconds,
correlationId.ToString());
}
}
}
// Método de Extensão Idiomático
public static class RequestTracingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestTracing(this IApplicationBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);
return builder.UseMiddleware<RequestTracingMiddleware>();
}
}
2. Dominando o Endpoint Routing no ASP.NET Core
A partir do .NET Core 3.0 e aprimorado no .NET 8 e .NET 9, o Endpoint Routing desassociou a etapa de seleção de rotas da execução dos controladores de API ou Minimal APIs. O processo é dividido em duas fases distintas:
- UseRouting (Matcher): Analisa a requisição HTTP (URL, Method, Headers), encontra a rota correspondente e atribui o objeto Endpoint ao HttpContext.GetEndpoint().
- UseEndpoints ou Minimal APIs (Executor): Executa o delegate configurado para o endpoint selecionado.
Request HTTP ──► [ UseRouting ] ──► (Endpoint Selecionado / Metadados) ──► [ Middlewares Intermediários ] ──► [ Execution Endpoint ]
A grande vantagem arquitetural desse modelo é que middlewares posicionados entre UseRouting e o executor final conseguem inspecionar os metadados do endpoint (EndpointMetadata) antes de processar qualquer lógica pesada.
Exemplo Prático: Restrição de Rota Customizada (IRouteConstraint)
O exemplo a seguir demonstra a criação de uma restrição de rota que valida se o parâmetro fornecido na URL é um identificador no formato CUS (Customer Unique System) válido sem precisar carregar o controlador.
C#
namespace MasterStack.Architecture.Web.Constraints;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
public sealed class CustomerIdRouteConstraint : IRouteConstraint
{
public bool Match(
HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var value) || value is null)
{
return false;
}
var customerId = value.ToString();
// Exemplo: Valida se o ID inicia com "CUS-" seguido por dígitos
if (string.IsNullOrEmpty(customerId) || !customerId.StartsWith("CUS-", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return customerId.Length > 4 && long.TryParse(customerId.AsSpan(4), out _);
}
}
Registro e Uso nas Minimal APIs ou Controllers:
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("validCustomerId", typeof(CustomerIdRouteConstraint));
});
var app = builder.Build();
app.UseRouting();
// O Endpoint Routing rejeitará chamadas que não satisfaçam a restrição com HTTP 404 automaticamente
app.MapGet("/api/customers/{id:validCustomerId}", (string id) =>
{
return Results.Ok(new { CustomerId = id, Status = "Active" });
});
app.Run();
3. Casos de Uso Reais, Cuidados e Performance (FinOps e SRE)
A Ordem Importa: O Impacto na Segurança e Latência
A ordem de registro dos middlewares define o comportamento do sistema. Um erro comum é registrar o tratamento global de exceções após a etapa de autenticação, o que pode fazer com que falhas ocorridas na validação do token retornem respostas sem o devido formato de erro padrão.
✅ Ordem Correta da Pipeline:
1. Exception Handling (Catch global)
2. HSTS / HTTPS Redirection
3. Static Files
4. Routing (UseRouting - Mapeia o endpoint e carrega metadados)
5. CORS
6. Authentication (Verifica a identidade)
7. Authorization (Verifica as permissões com base nos metadados do endpoint)
8. Custom Business Middlewares
9. Endpoint Execution (UseEndpoints / MapEndpoints)
Otimizações de Memória para Baixa Latência
- Evite Leitura Síncrona do Stream do Corpo da Requisição: O acesso ao context.Request.Body sem buffer assíncrono bloqueia threads da ThreadPool. Utilize context.Request.EnableBuffering() apenas quando for estritamente necessário reler o corpo da requisição e utilize rotinas ReadAsync().
- Atenção com Instanciações no Método InvokeAsync: Não instancie coleções, parsers JSON ou objetos de serviço dentro do método InvokeAsync. Mantenha esses objetos estáticos ou reaproveitáveis para evitar a pressão sobre as Gerações 0 e 1 do Garbage Collector (GC).
Onde usar vs. Onde EVITAR
Manipulação do Cabeçalho HTTP (CORS, Correlation-ID)
- Middleware Global: ✅ Sim
- Filter / Endpoint Filter: ❌ Não
Log e Métricas Globais de Latência
- Middleware Global: ✅ Sim
- Filter / Endpoint Filter: ❌ Não
Validação de Modelo de Domínio Específico
- Middleware Global: ❌ Não
- Filter / Endpoint Filter: ✅ Sim
Autorização baseada em Roles específicas da Action
- Middleware Global: ❌ Não
- Filter / Endpoint Filter: ✅ Sim
Conclusão
Dominar a arquitetura da pipeline HTTP e do Endpoint Routing no ASP.NET Core permite construir sistemas resilientes, seguros e de altíssimo desempenho. A aplicação correta de middlewares por convenção e restrições de rota evita o processamento desnecessário na CPU e reduz a alocação de memória no Heap.
👉 Pronto para levar a sua carreira em engenharia .NET para o próximo nível? Explore conteúdos avançados e conheça as melhores vagas para desenvolvedores na MasterStack Jobs. Conectamos talentos seniores às empresas mais inovadoras do mercado!