Implementação de Programação Orientada a Aspectos (AOP) no ASP.NET Core

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.

Tags: ASPNETCore AOP dispatchproxy aspectcore entityframeworkcore

Publicado em 8-8 17:26