No dinâmico mundo do desenvolvimento web, a performance é um fator crucial para o sucesso de qualquer aplicação. Usuários esperam respostas rápidas e eficientes, e um servidor sobrecarregado pode levar a uma experiência frustrante. Para desenvolvedores .NET, a Minimal API do C# oferece uma maneira concisa e poderosa de construir APIs. E agora, com a introdução do Output Caching, otimizar a performance dessas APIs se tornou ainda mais acessível.
O que é Output Caching?
O Output Caching é um mecanismo de cache do lado do servidor que armazena a resposta HTTP completa gerada por um endpoint. Em vez de reexecutar o endpoint para cada requisição, o servidor simplesmente entrega a resposta armazenada em cache, resultando em uma significativa melhora na performance e redução da carga do servidor. Imagine um endpoint que gera um relatório complexo. Sem Output Caching, esse relatório seria gerado a cada requisição, consumindo recursos valiosos. Com Output Caching, o relatório é gerado apenas uma vez e servido a todos os usuários dentro de um período de tempo definido.
Implementado como um middleware, o Output Caching intercepta as requisições, verifica se existe uma resposta em cache e, se existir, a retorna imediatamente. Caso contrário, a requisição é processada normalmente, e a resposta é armazenada em cache para requisições futuras. Por padrão, o Output Caching utiliza o armazenamento em memória, mas pode ser configurado para usar armazenamentos distribuídos como Redis para maior escalabilidade e resiliência.
Benefícios do Output Caching
A utilização do Output Caching oferece uma série de vantagens para suas aplicações web:
- Melhora na performance: Reduz o tempo de resposta, proporcionando uma experiência mais rápida e fluida para o usuário.
- Redução da carga do servidor: Diminui o consumo de recursos, permitindo que o servidor atenda a mais requisições simultaneamente.
- Escalabilidade: Facilita a escalabilidade da aplicação, pois o servidor gasta menos tempo processando requisições repetitivas.
- Otimização de custos: Reduz os custos de infraestrutura, pois menos recursos são necessários para atender ao mesmo número de usuários.
Como Implementar Output Caching em C# Minimal API
A implementação do Output Caching em C# Minimal API é relativamente simples e direta. O processo envolve três etapas principais:
- Registrar o serviço Output Caching: Utilize o método
AddOutputCache()para registrar o serviço Output Caching no container de injeção de dependência. - Definir políticas de cache: Crie uma ou mais políticas de cache que definem o comportamento do caching, como o tempo de expiração e as condições para invalidar o cache.
- Aplicar as políticas aos endpoints: Utilize o método
CacheOutput()para aplicar as políticas de cache aos endpoints desejados. - Adicionar o middleware Output Caching: Adicione o middleware
UseOutputCache()ao pipeline de requisições.
Exemplo de Código
O exemplo abaixo demonstra como implementar o Output Caching em uma aplicação C# Minimal API:
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddOutputCache(options =>
{
// Adiciona a política padrão
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
// Adiciona uma política específica
options.AddPolicy("OutputCache20Seconds", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
});
...
// Usa a política padrão
app.MapGet("/default-cache-policy", () => { return new[] { "someresponse2" }; })
.CacheOutput();
// Usa uma política específica
app.MapGet("/custom-cache-policy", () => { return new[] { "someresponse" }; })
.CacheOutput("OutputCache20Seconds");
...
// Adiciona o middleware
app.UseOutputCache();
Importante: Em aplicações que utilizam o middleware CORS, o UseOutputCache() deve ser chamado após o UseCors().
Output Caching com Header de Autorização
Por padrão, o Output Caching não armazena em cache respostas quando um header de Autorização está presente. Isso é uma medida de segurança para evitar o fornecimento acidental de conteúdo autenticado para o usuário errado. No entanto, em alguns cenários, pode ser necessário desabilitar esse comportamento e armazenar em cache respostas mesmo com o header de Autorização presente.
Para isso, é preciso criar uma política de Output Caching customizada. O exemplo abaixo mostra como criar uma política que ignora o header de Autorização:
internal static class CustomOutputCachingPolicyFactory
{
internal static CustomOutputCachingPolicy CreateOutputCachingPolicy(TimeSpan timeSpan)
=> new(timeSpan);
}
internal sealed class CustomOutputCachingPolicy : IOutputCachePolicy
{
private readonly TimeSpan _responseExpirationTime;
internal CustomOutputCachingPolicy(TimeSpan responseExpirationTime)
{
_responseExpirationTime = responseExpirationTime;
}
ValueTask IOutputCachePolicy.CacheRequestAsync(
OutputCacheContext context,
CancellationToken cancellation)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = attemptOutputCaching;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
context.ResponseExpirationTimeSpan = _responseExpirationTime;
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
ValueTask IOutputCachePolicy.ServeFromCacheAsync
(OutputCacheContext context, CancellationToken cancellation)
=> ValueTask.CompletedTask;
ValueTask IOutputCachePolicy.ServeResponseAsync
(OutputCacheContext context, CancellationToken cancellation)
{
var response = context.HttpContext.Response;
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
if (response.StatusCode is not (StatusCodes.Status200OK or StatusCodes.Status301MovedPermanently))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
var request = context.HttpContext.Request;
return HttpMethods.IsGet(request.Method) ||
HttpMethods.IsHead(request.Method);
}
Extensões de Conveniência para Minimal APIs
Para simplificar ainda mais o uso do Output Caching em Minimal APIs, é possível criar métodos de extensão:
public static class OutputCachingExtensions
{
public static void AddCustomOutputCachingPolicy(this OutputCacheOptions options, params (string name, TimeSpan timeSpan)[] policies)
{
foreach (var (name, timeSpan) in policies)
{
options.AddPolicy(name, CustomOutputCachingPolicyFactory.CreateOutputCachingPolicy(timeSpan));
}
}
public static IServiceCollection AddOutputCacheWithCustomPolicy(
this IServiceCollection serviceCollection,
params (string name, TimeSpan timeSpan)[] policies)
=> serviceCollection.AddOutputCache(options =>
{
options.AddCustomOutputCachingPolicy(policies);
});
public static IServiceCollection AddOutputCacheWithCustomPolicy(
this IServiceCollection serviceCollection,
Action<OutputCacheOptions> configureOptions,
params (string name, TimeSpan timeSpan)[] policies)
=> serviceCollection.AddOutputCache(options =>
{
options.AddCustomOutputCachingPolicy(policies);
configureOptions.Invoke(options);
});
public static RouteHandlerBuilder CustomCacheOutput(this RouteHandlerBuilder routeHandlerBuilder, string name)
=> routeHandlerBuilder.CacheOutput(name);
}
Quando Utilizar Output Caching?
O Output Caching é mais adequado para cenários onde as respostas são custosas de gerar e não mudam frequentemente. Alguns exemplos incluem:
- Computações complexas do lado do servidor: Cálculos que exigem muitos recursos de processamento.
- Respostas frequentemente requisitadas com alto custo de execução: Geração de relatórios, renderização de perfis de usuário, etc.
Importante: Não utilize Output Caching para endpoints autenticados ou específicos do usuário, a menos que a resposta seja idêntica para todos os usuários. Isso poderia comprometer a segurança e privacidade dos dados.
Conclusão
O Output Caching é uma ferramenta poderosa para otimizar a performance de aplicações C# Minimal API. Ao armazenar em cache as respostas HTTP, é possível reduzir a carga do servidor, melhorar o tempo de resposta e proporcionar uma experiência mais fluida para o usuário. Com a facilidade de implementação e a flexibilidade de configuração, o Output Caching se torna um aliado indispensável para desenvolvedores que buscam construir aplicações web de alto desempenho.
No futuro, podemos esperar que o Output Caching se torne ainda mais inteligente e adaptável, com recursos como invalidation strategies mais sofisticadas e integração com outras tecnologias de caching. A busca por performance e otimização continuará sendo uma prioridade no desenvolvimento web, e o Output Caching certamente desempenhará um papel fundamental nesse cenário.