文章目錄
- 什么是 AOP ?
- .Net Core 中 有哪些 AOP 框架?
- 基于 Castle DynamicProxy 實現 AOP
- IOC中使用 Castle DynamicProxy
- 實現事務管理
- 實現用戶自動填充
什么是 AOP ?
AOP(Aspect-Oriented Programming,面向切面編程)是一種編程范式,旨在通過將橫切關注點(cross-cutting concerns)從主要業務邏輯中分離出來,以提高代碼的模塊化性、可維護性和復用性。
在傳統的面向對象編程中,我們通常通過類和對象來組織和實現功能。然而,某些功能,如日志記錄、事務管理、安全性檢查等,可能會跨越多個對象和模塊,這種跨越稱為橫切關注點。AOP 的核心思想是將這些橫切關注點從業務邏輯中分離出來,通過特定的機制將它們應用到代碼中,而不是通過直接修改業務邏輯來實現。
.Net Core 中 有哪些 AOP 框架?
PostSharp(收費)
PostSharp是一個功能強大的AOP框架,它通過編譯器插件的形式集成到Visual Studio中。PostSharp支持編譯時AOP(通過C#特性應用切面),并提供了豐富的切面類型,包括方法攔截、屬性訪問攔截、異常處理等。它還提供了商業支持和豐富的文檔。
Castle DynamicProxy
Castle DynamicProxy是Castle項目的一部分,它允許開發者在運行時動態創建代理類,這些代理類可以攔截對目標對象的調用,并在調用前后執行自定義邏輯。雖然它本身不是一個完整的AOP框架,但它經常被用作構建AOP解決方案的基礎。
AspectCore Framework
AspectCore 是一個開源的 AOP 框架,專為 .NET Core 設計。它提供了基于動態代理的運行時切面和方法攔截機制,支持常見的切面編程需求,如日志、緩存、事務等。
基于 Castle DynamicProxy 實現 AOP
1. 安裝Castle.Core NuGet包
Install-Package Castle.Core
2. 定義接口和類
假設你有一個接口和一個實現了該接口的類,你想要攔截這個類的方法調用。
public interface IMyService
{ void PerformAction();
}
public class MyService : IMyService
{ public void PerformAction() { Console.WriteLine("Action performed."); }
}
3. 創建攔截器
接下來,你需要創建一個攔截器類,該類將實現IInterceptor
接口。在這個接口的實現中,你可以定義在調用目標方法之前和之后要執行的邏輯。
using Castle.DynamicProxy; public class MyInterceptor : IInterceptor
{ public void Intercept(IInvocation invocation) { // 在調用目標方法之前執行的邏輯 Console.WriteLine("Before method: " + invocation.Method.Name); // 調用目標方法 invocation.Proceed(); // 在調用目標方法之后執行的邏輯 Console.WriteLine("After method: " + invocation.Method.Name); }
}
4. 創建代理并調用方法
最后,你需要使用ProxyGenerator
類來創建MyService
的代理實例,并指定攔截器。然后,你可以像使用普通對象一樣調用代理的方法,但攔截器中的邏輯會在調用發生時執行。
using Castle.DynamicProxy; public class Program
{ public static void Main(string[] args) { var generator = new ProxyGenerator(); var interceptor = new MyInterceptor(); // 創建MyService的代理實例,并指定攔截器 var proxy = generator.CreateInterfaceProxyWithTarget<IMyService>( new MyService(), interceptor); // 調用代理的方法,攔截器中的邏輯將被執行 proxy.PerformAction(); }
}
注意,上面的示例使用了接口代理(CreateInterfaceProxyWithTarget
),這意味著你的目標類必須實現一個或多個接口。如果你想要代理一個類而不是接口,你可以使用CreateClassProxyWithTarget
方法(但這通常用于需要代理非虛方法或字段的場景,且要求目標類是可繼承的)。
IOC中使用 Castle DynamicProxy
由于IOC容器(如Microsoft的IServiceCollection
和IServiceProvider
)通常不直接支持AOP,所以用 Autofac
1. 安裝必要的 NuGet 包
首先,確保你的項目中安裝了以下 NuGet 包:
Install-Package Autofac
Install-Package Autofac.Extensions.DependencyInjection
Install-Package Autofac.Extras.DynamicProxy
Install-Package Castle.Core
2. 創建服務接口和實現類
public class User{public long Id { get; set; }public string Name { get; set; }public long CreateUserId { get; set; }public string CreateUserName { get; set; }public DateTime CreateTime { get; set; }public long UpdateUserId { get; set; }public string UpdateUserName { get; set; }public DateTime UpdateTime { get; set; }} public interface IUserService{void Test();Task<int> TaskTest();void Add(User user);void Update(User user);}public class UserService : IUserService{public void Test(){Console.WriteLine("Test");}public async Task<int> TaskTest(){await Console.Out.WriteLineAsync("TaskTest");return 1;}public void Add(User user){Console.WriteLine(user.CreateUserId);Console.WriteLine(user.CreateUserName);}public void Update(User user){Console.WriteLine(user.UpdateUserId);Console.WriteLine(user.UpdateUserName);}}[ApiController][Route("[controller]")]public class UserController : ControllerBase{readonly IUserService _userService;public UserController(IUserService userService){_userService = userService;} [HttpGet][Route("/taskTest")]public async Task<string> TaskTest(){await _userService.TaskTest();return "ok";}[HttpGet][Route("/test")]public string Test(){_userService.Test();return "ok";}[HttpGet][Route("/add")]public string Add(){_userService.Add(new Model.User { Name = "張三" });return "ok";}[HttpGet][Route("/update")]public string Update(){_userService.Update(new Model.User { Name = "張三" });return "ok";}}
3. 創建攔截器類
創建一個實現 IInterceptor
接口的攔截器類 LoggingInterceptor
,用于攔截方法調用并添加日志記錄:
public class LoggingInterceptor : IInterceptor
{public void Intercept(IInvocation invocation){Console.WriteLine($"Before executing: {invocation.Method.Name}");invocation.Proceed(); // 調用原始方法Console.WriteLine($"After executing: {invocation.Method.Name}");}
}
4. 配置 Autofac 容器
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //使用Autofac.ConfigureContainer<ContainerBuilder>(autofacBuilder =>{autofacBuilder.RegisterType<LoggingInterceptor>();autofacBuilder.RegisterType<UserService>().As<IUserService> ().SingleInstance().AsImplementedInterfaces() .EnableInterfaceInterceptors() // 啟用接口攔截器.InterceptedBy(typeof(LoggingInterceptor)); //指定攔截器});
與Autofac集成時,配置攔截器主要有兩種方式
使用 InterceptAttribute
特性
這種方式通過在接口或類上添加[Intercept(typeof(YourInterceptor))]
特性來指定攔截器。然后,在Autofac注冊時,啟用接口或類的攔截器。(通常不推薦在類上直接添加,因為這會使類與Autofac緊密耦合)
[Intercept(typeof(UserAutoFillInterceptor))]public class UserService : IUserService
{ public void Test(){Console.WriteLine("Test");}
}autofacBuilder.RegisterType<UserService>().As<IUserService>().EnableInterfaceInterceptors() // 啟用接口攔截器
使用 InterceptedBy()
方法
這種方式不依賴于[Intercept]
特性,而是在注冊服務時直接使用InterceptedBy()
方法來指定攔截器。
autofacBuilder.RegisterType<UserService>().As<IUserService>() .EnableInterfaceInterceptors() // 啟用接口攔截器.InterceptedBy(typeof(LoggingInterceptor)); //指定攔截器
實現事務管理
攔截器基類
/// <summary>/// 攔截基類/// </summary>/// <typeparam name="T"></typeparam>public abstract class BaseInterceptor<T> : IInterceptor{protected readonly ILogger<T> _logger;public BaseInterceptor(ILogger<T> logger){_logger = logger;}/// <summary>/// 攔截方法/// </summary>/// <param name="invocation"></param>public virtual void Intercept(IInvocation invocation){try{Method = invocation.MethodInvocationTarget ?? invocation.Method;InterceptHandle(invocation);}catch (Exception ex){_logger.LogError(ex, ex.Message);throw ex;} }/// <summary>/// 攔截處理/// </summary>/// <param name="invocation"></param>public abstract void InterceptHandle(IInvocation invocation);protected MethodInfo Method{ get; set; }public static bool IsAsyncMethod(MethodInfo method){return (method.ReturnType == typeof(Task) ||(method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)));}
}
事務特性:用來判斷是否需要事務管理的
/// <summary>/// 事務特性/// </summary>[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]public class TransactionalAttribute : Attribute{public TransactionalAttribute(){Timeout = 60;}/// <summary>/// /// </summary>public int Timeout { get; set; }/// <summary>/// 事務隔離級別/// </summary>public IsolationLevel IsolationLevel { get; set; }/// <summary>/// 事務傳播方式/// </summary>public Propagation Propagation { get; set; }}/// <summary>/// 事務傳播方式/// </summary>public enum Propagation{/// <summary>/// 默認:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中。/// </summary>Required = 0,/// <summary>/// 使用當前事務,如果沒有當前事務,就拋出異常/// </summary>Mandatory = 1,/// <summary>/// 以嵌套事務方式執行/// </summary>Nested = 2,}
事務攔截器:處理事務的
/// <summary>/// 事務攔截器/// </summary>public class TransactionalInterceptor : BaseInterceptor<TransactionalInterceptor>{public TransactionalInterceptor(ILogger<TransactionalInterceptor> logger) : base(logger){}public override void InterceptHandle(IInvocation invocation){if (Method.GetCustomAttribute<TransactionalAttribute>(true) == null && Method.DeclaringType?.GetCustomAttribute<TransactionalAttribute>(true) == null){invocation.Proceed();}else{try{Console.WriteLine("開啟事務");//執行方法invocation.Proceed();// 異步獲取異常,先執行if (IsAsyncMethod(invocation.Method)){var result = invocation.ReturnValue;if (result is Task){Task.WaitAll(result as Task);}}Console.WriteLine("提交事務");}catch (Exception ex){Console.WriteLine("回滾事務");_logger.LogError(ex, ex.Message);throw ex;}}}}
接口上加入事務特性
//[Transactional]public class UserService : IUserService{[Transactional]public void Test(){Console.WriteLine("Test");}}
注入IOC
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(autofacBuilder =>{autofacBuilder.RegisterType<TransactionalInterceptor>(); autofacBuilder.RegisterType<UserService>().As<IUserService>().SingleInstance().AsImplementedInterfaces().EnableInterfaceInterceptors().InterceptedBy(typeof(TransactionalInterceptor));});
測試
實現用戶自動填充
上下戶用戶
public interface IHttpContextUser{long UserId { get; }string UserName { get;}}public class HttpContextUser : IHttpContextUser{private readonly IHttpContextAccessor _accessor;public HttpContextUser(IHttpContextAccessor accessor){_accessor = accessor;}public long UserId{get{return 1; //這里暫時是寫死的if (int.TryParse(_accessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Sid), out var userId)){return userId;}return default;}}public string UserName{get{return "admin"; //這里暫時是寫死的return _accessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Name) ?? "";}}}
注入IOC
builder.Services.AddHttpContextAccessor();builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(autofacBuilder =>{autofacBuilder.RegisterType<HttpContextUser>().As<IHttpContextUser>().SingleInstance().AsImplementedInterfaces();autofacBuilder.RegisterType<UserAutoFillInterceptor>();autofacBuilder.RegisterType<UserService>().As<IUserService>().SingleInstance().AsImplementedInterfaces().EnableInterfaceInterceptors().InterceptedBy(typeof(UserAutoFillInterceptor));});
用戶自動攔截器:處理用戶填充的
/// <summary>/// 用戶自動填充攔截器/// </summary>public class UserAutoFillInterceptor : BaseInterceptor<UserAutoFillInterceptor>{private readonly IHttpContextUser _user;public UserAutoFillInterceptor(ILogger<UserAutoFillInterceptor> logger,IHttpContextUser user) : base(logger){_user = user;}public override void InterceptHandle(IInvocation invocation){//對當前方法的特性驗證if (Method.Name?.ToLower() == "add" || Method.Name?.ToLower() == "update"){if (invocation.Arguments.Length == 1 && invocation.Arguments[0].GetType().IsClass){dynamic argModel = invocation.Arguments[0];var getType = argModel.GetType();if (Method.Name?.ToLower() == "add"){if (getType.GetProperty("CreateUserId") != null){argModel.CreateUserId = _user.UserId;}if (getType.GetProperty("CreateUserName") != null){argModel.CreateUserName = _user.UserName;}if (getType.GetProperty("CreateTime") != null){argModel.CreateTime = DateTime.Now;}}if (getType.GetProperty("UpdateUserId") != null){argModel.UpdateUserId = _user.UserId;}if (getType.GetProperty("UpdateUserName") != null){argModel.UpdateUserName = _user.UserName;}if (getType.GetProperty("UpdateTime") != null){argModel.UpdateTime = DateTime.Now;}}invocation.Proceed();}else{invocation.Proceed();}}}
測試