
在ASP.NET Core中实现动态代理与AOP:告别重复代码,拥抱优雅架构
你好,我是源码库的博主。在多年的ASP.NET Core项目开发中,我发现自己和团队常常陷入一种困境:日志记录、性能监控、异常处理、缓存、事务管理……这些横跨多个业务模块的“通用关注点”代码,像藤蔓一样缠绕在核心业务逻辑上。每次新增一个服务方法,我们都要机械地复制粘贴那几行日志和`try-catch`,不仅代码变得臃肿不堪,更可怕的是,一旦要修改日志格式或异常处理策略,就得满世界搜索替换,简直是维护者的噩梦。
直到我系统性地应用了动态代理与AOP(面向切面编程),才真正将核心业务与这些“切面”逻辑解耦。今天,我就带你手把手实现一套轻量、灵活且适用于现代ASP.NET Core的AOP方案,分享我踩过的坑和最佳实践。
一、核心理念:什么是AOP与动态代理?
简单来说,AOP允许我们将像日志、事务这类“横切关注点”模块化,然后“织入”到目标代码中,而不需要修改目标代码本身。想象一下,你的业务方法就像一块纯净的蛋糕胚,而日志、缓存等就是上面的奶油和水果装饰。AOP就是那个精准的裱花袋,让你在不触碰蛋糕胚的情况下完成装饰。
在C#世界中,实现AOP最常见的手段就是动态代理。它的原理是:在运行时,为目标对象(例如我们的服务类)创建一个“代理”对象。这个代理对象包裹着真实对象,并可以在调用真实对象的方法之前、之后或抛出异常时插入我们自定义的逻辑。.NET中,`Castle DynamicProxy` 库是完成此任务的绝佳利器,它成熟、稳定且性能出色。
二、实战准备:创建项目与引入依赖
首先,我们创建一个新的ASP.NET Core Web API项目。
dotnet new webapi -n AopDemo
cd AopDemo
然后,通过NuGet安装核心库:
dotnet add package Castle.Core
这里我踩过一个坑:确保安装的是稳定版本。某些预览版可能与当前.NET版本存在兼容性问题。
三、定义切面:实现一个日志拦截器
拦截器是实现AOP逻辑的核心。我们创建一个 `LoggingInterceptor.cs`,它需要实现 `Castle.DynamicProxy.IInterceptor` 接口。
using Castle.DynamicProxy;
using System.Diagnostics;
namespace AopDemo.Interceptors;
public class LoggingInterceptor : IInterceptor
{
private readonly ILogger _logger;
public LoggingInterceptor(ILogger logger)
{
_logger = logger;
}
public void Intercept(IInvocation invocation)
{
// 1. 方法调用前:记录开始
var methodName = invocation.Method.Name;
var className = invocation.TargetType.Name;
_logger.LogInformation($"开始执行 {className}.{methodName} 方法。");
var stopwatch = Stopwatch.StartNew();
try
{
// 2. 继续执行原方法
invocation.Proceed();
}
catch (Exception ex)
{
// 3. 异常处理:记录错误并重新抛出
stopwatch.Stop();
_logger.LogError(ex, $"执行 {className}.{methodName} 时发生异常,耗时 {stopwatch.ElapsedMilliseconds}ms。");
throw; // 重要:保持异常传播
}
finally
{
// 4. 方法调用后:记录结束与耗时
if (stopwatch.IsRunning)
{
stopwatch.Stop();
}
_logger.LogInformation($"完成执行 {className}.{methodName} 方法,耗时 {stopwatch.ElapsedMilliseconds}ms。");
}
}
}
这个拦截器清晰地展示了AOP的威力:它完整地包装了一个方法的生命周期,而我们无需在任何业务方法里写一句`Stopwatch`或`LogInformation`。
四、创建服务与代理生成器
我们先定义一个简单的业务接口 `IWeatherService.cs` 及其实现。
// IWeatherService.cs
namespace AopDemo.Services;
public interface IWeatherService
{
IEnumerable GetForecasts();
}
// WeatherService.cs
namespace AopDemo.Services;
public class WeatherService : IWeatherService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
// 注意:这里没有任何日志或监控代码!
public IEnumerable GetForecasts()
{
// 模拟一点业务逻辑
Thread.Sleep(new Random().Next(50, 200));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
接下来是关键一步:创建代理。我习惯在 `Program.cs` 中使用扩展方法或直接注册。这里为了清晰,我们创建一个静态工具类 `ProxyGeneratorHelper.cs`。
using Castle.DynamicProxy;
using AopDemo.Interceptors;
using AopDemo.Services;
namespace AopDemo.Utilities;
public static class ProxyGeneratorHelper
{
private static readonly ProxyGenerator _generator = new ProxyGenerator();
public static T CreateInterfaceProxyWithTarget(T target, params IInterceptor[] interceptors) where T : class
{
return _generator.CreateInterfaceProxyWithTarget(target, interceptors);
}
}
五、依赖注入集成:优雅地织入代理
这是将AOP融入ASP.NET Core生命周期的优雅方式。我们在 `Program.cs` 中配置服务。
using AopDemo.Interceptors;
using AopDemo.Services;
using AopDemo.Utilities;
var builder = WebApplication.CreateBuilder(args);
// 1. 注册拦截器(瞬时或作用域生命周期根据需求定)
builder.Services.AddScoped();
// 2. 注册原始服务实现
builder.Services.AddScoped();
// 3. 注册代理后的接口服务
builder.Services.AddScoped(serviceProvider =>
{
// 获取原始实现和拦截器
var target = serviceProvider.GetRequiredService();
var interceptor = serviceProvider.GetRequiredService();
// 使用动态代理创建包裹了拦截逻辑的实例
var proxy = ProxyGeneratorHelper.CreateInterfaceProxyWithTarget(target, interceptor);
return proxy;
});
// 其他服务注册...
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 配置HTTP管道...
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
这样,当控制器或其他服务通过构造函数请求 `IWeatherService` 时,注入的将是被 `LoggingInterceptor` 包裹的代理对象,所有方法调用都会自动经过日志切面。
六、进阶与踩坑提示
1. 选择性拦截: 你可能不想拦截所有方法。可以为拦截器添加自定义Attribute,比如 `[Log]`,然后在 `Intercept` 方法中判断 `invocation.Method` 是否标记了该特性。这是更精细的控制策略。
2. 异步方法支持: 上面的拦截器对异步方法(`async/await`)处理不够完美。`invocation.Proceed()` 返回的是 `Task`,我们需要特殊处理以确保日志在异步操作完成后才记录。这是一个常见的坑!改进版本需要判断方法的返回类型。
// 简化版的异步支持示例
public async void Intercept(IInvocation invocation)
{
// ... 前处理
invocation.Proceed();
if (invocation.ReturnValue is Task task)
{
await task.ConfigureAwait(false);
}
// ... 后处理(需考虑异步上下文)
}
3. 性能考量: 动态代理在运行时生成类型,首次调用会有开销。对于性能极度敏感的场景,可以考虑编译时织入的方案(如PostSharp或使用Source Generator)。但对于绝大多数应用,Castle DynamicProxy的开销是可接受的。
4. 与内置DI的深度集成: 上述工厂方法模式可行,但项目大了会显繁琐。社区有优秀的库如 `Scrutor`,可以配合实现基于约定的批量代理注册,让代码更加简洁。
七、总结
通过以上步骤,我们成功地在ASP.NET Core中构建了一个基于动态代理的AOP框架。现在,你的业务服务变得无比纯净,而像日志、缓存、审计、性能监控这些横切关注点,都成了可以独立维护、复用和配置的“插件”。
这种架构带来的好处是深远的:代码可读性极大提升,维护成本显著降低,团队可以更专注于业务逻辑本身。当你下次需要为系统统一添加一个“请求指纹追踪”功能时,你只需新建一个拦截器并注册它,而不是修改上百个服务方法——这就是AOP带来的优雅与力量。
希望这篇教程能帮你打开一扇门。动手试试吧,从为一个现有服务添加一个简单的日志切面开始,感受它带来的改变。编码愉快!

评论(0)