A implementação de Programação Orientada a Aspectos (AOP) no ASP.NET Core é fundamental não apenas para isolar regras de negócio, mas também para manter a arquitetura limpa, estável e o código coeso. Ao separar preocupações tranvsersais (cross-cutting concerns) como log, cache e transações, evitamos a poluição das classes de domínio.
Abordagem Nativa com DispatchProxy
O .NET fornece a classe DispatchProxy no namespace System.Reflection, permitindo a criação de proxies dinâmicos sem dependências externas. Esta é uma solução leeve para cenários simples.
Primeiro, definimos a interface e a implementação do serviço:
public interface IOrderService
{
Task<string> ProcessOrderAsync(int orderId);
}
public class OrderService : IOrderService
{
public async Task<string> ProcessOrderAsync(int orderId)
{
await Task.Delay(10); // Simula processamento
return $"Pedido {orderId} processado com sucesso.";
}
}
Em seguida, criamos a classe de proxy que herda de DispatchProxy. Aqui, interceptamos a chamada para executar lógicas antes e depois do método alvo:
public class LoggingDispatchProxy<T> : DispatchProxy where T : class
{
private T _target;
public static T CreateProxy(T target)
{
var proxy = Create<T, LoggingDispatchProxy<T>>() as LoggingDispatchProxy<T>;
proxy._target = target;
return proxy as T;
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
Console.WriteLine($"[Antes] Executando {targetMethod.Name}");
var result = targetMethod.Invoke(_target, args);
Console.WriteLine($"[Depois] Finalizado {targetMethod.Name}");
return result;
}
}
No controller, aplicamos o proxy dinamicamente e registramos o serviço original no contêiner de injeção de dependência:
// Program.cs
builder.Services.AddScoped<IOrderService, OrderService>();
// OrdersController.cs
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var proxiedService = LoggingDispatchProxy<IOrderService>.CreateProxy(_orderService);
var result = await proxiedService.ProcessOrderAsync(id);
return Ok(result);
}
}
Utilizando AspectCore.DynamicProxy
Para cenários mais complexos que exigem injeção de dependência dentro dos interceptadores e suporte assíncrono robusto, bibliotecas como o AspectCore são mais adequadas. Após instalar o pacote de integração com o DI, configuramos o host:
builder.Host.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
Interceptador de Auditoria
Um exemplo de interceptador para log de execução, utilizando o ILogger injetado:
public class AuditLogInterceptorAttribute : AbstractInterceptorAttribute
{
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
var logger = context.ServiceProvider.GetRequiredService<ILogger<AuditLogInterceptorAttribute>>();
logger.LogInformation("Iniciando execução de {Method}", context.ImplementationMethod.Name);
await next(context);
logger.LogInformation("Execução de {Method} concluída", context.ImplementationMethod.Name);
}
}
Interceptador de Cache em Memória
Podemos criar um atributo para cachear resultados de métodos, lidando com retornos assíncronos e definindo tempo de expiração:
public class MemoryCacheInterceptorAttribute : AbstractInterceptorAttribute
{
public int ExpirationSeconds { get; set; } = 300;
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
var cache = context.ServiceProvider.GetRequiredService<IMemoryCache>();
var cacheKey = $"{context.ImplementationMethod.DeclaringType?.FullName}.{context.ImplementationMethod.Name}";
if (cache.TryGetValue(cacheKey, out object cachedValue))
{
context.ReturnValue = cachedValue;
return;
}
await next(context);
var result = await context.UnwrapAsyncReturnValue();
cache.Set(cacheKey, result, TimeSpan.FromSeconds(ExpirationSeconds));
}
}
Controle Transacional (Unit of Work)
Para gerenciar transações de banco de dados de forma transparente, interceptamos o contexto do Entity Framework Core:
public class TransactionScopeInterceptorAttribute : AbstractInterceptorAttribute
{
public override async Task Invoke(AspectContext context, AspectDelegate next)
{
var dbContext = context.ServiceProvider.GetRequiredService<AppDbContext>();
await using var transaction = await dbContext.Database.BeginTransactionAsync();
try
{
await next(context);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}
public class PaymentService
{
[TransactionScopeInterceptor]
public async Task ProcessPaymentAsync(decimal amount)
{
// Operações de banco de dados
await Task.CompletedTask;
}
}
Outras Alternativas no Ecossistema .NET
Além das abordagens baseadas em proxies dinâmicos, o ecossistema ASP.NET Core oferece diversos mecanismos nativos e de terceiros para aplicar AOP:
- Castle.DynamicProxy: Uma biblioteca clássica e extremamente robusta, frequentemente utilizada sob o capô por frameworks de mocking e injeção de dependência.
- Filtros do ASP.NET Core: ActionFilters, ExceptionFilters e ResultFilters são ideais para interceptar requisições HTTP no pipeline do MVC.
- Interceptors do Entity Framework Core: Permitem interceptar comandos SQL, salvamentos de entidades e consultas diretamente no nível do ORM.
- Pipeline Behaviors do MediatR: Ao utilizar o padrão Mediator, os comportamentos de pipeline (IPipelineBehavior) atuam como interceptadores para requisições e comandos de aplicação.