1.AOP的概念
AOP是Aspect Oriented Programing的縮寫,中文翻譯為面向切面編程,是通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
利用AOP可以對業務邏輯的各個部分進行隔離,使業務邏輯各部分之間的耦合度低,提高程序的可重用性,同時提高開發的效率。
2.使用場景
日志功能
驗證功能
異常處理
3.和mvc過濾器(Filter)的不同
Filter 是在HTTP層面的攔截
AOP的動態代理(DynamicProxy):可以用於業務層(server),在Action執行前,執行后攔截處理
4、基於AutoFac使用AOP
上一節已經引入了 Autofac.Extras.DynamicProxy(Autofac的動態代理,它依賴Autofac,所以可以不用單獨引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的擴展)所以就不用引入了
1、在項目中建AOP文件,添加BlogLogAOP類
繼承 IInterceptor 實現接口方法 我的項目server層全部方法是異步的所以用了異步
全部代碼如下:
/// <summary> /// 攔截器BlogLogAOP 繼承IInterceptor接口 /// </summary> public class BlogLogAOP : IInterceptor {
/// <summary> /// 實例化IInterceptor唯一方法 /// </summary> /// <param name="invocation">包含被攔截方法的信息</param> public void Intercept(IInvocation invocation) { var dataIntercept = "";try { //在被攔截的方法執行完畢后 繼續執行當前方法,注意是被攔截的是異步的 invocation.Proceed(); // 異步獲取異常,先執行 if (IsAsyncMethod(invocation.Method)) { //Wait task execution and modify return value if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task)invocation.ReturnValue, ex => { LogEx(ex, ref dataIntercept); }); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, ex => { LogEx(ex, ref dataIntercept); }); } } else {// 同步1 } } catch (Exception ex)// 同步2 { LogEx(ex, ref dataIntercept); } var type = invocation.Method.ReturnType; if (typeof(Task).IsAssignableFrom(type)) { var resultProperty = type.GetProperty("Result"); dataIntercept += ($"【執行完成結果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}"); } else { dataIntercept += ($"【執行完成結果】:{invocation.ReturnValue}"); }
// 記錄你的日志 Console.WriteLine(dataIntercept);
} private void LogEx(Exception ex, ref string dataIntercept) { if (ex != null) { //執行的 service 中,收錄異常 MiniProfiler.Current.CustomTiming("Errors:", ex.Message); //執行的 service 中,捕獲異常 dataIntercept += ($"方法執行中出現異常:{ex.Message + ex.InnerException}\r\n"); } } public static bool IsAsyncMethod(MethodInfo method) { return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) ); } } internal static class InternalAsyncHelper { public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Action<Exception> finalAction) { Exception exception = null; try { await actualReturnValue; } catch (Exception ex) { exception = ex; } finally { finalAction(exception); } } public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; try { var result = await actualReturnValue; await postAction(); return result; } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } } public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Action<Exception> finalAction) { return typeof(InternalAsyncHelper) .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) .MakeGenericMethod(taskReturnType) .Invoke(null, new object[] { actualReturnValue, finalAction }); } }
在 Startup 文件 ConfigureContainer方法添加
//添加 AOP 綁定 var cacheType = new List<Type>(); builder.RegisterType<BlogLogAOP>(); cacheType.Add(typeof(BlogLogAOP)); // 獲取 Service.dll 程序集服務,並注冊
var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerDependency() .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; .InterceptedBy(cacheType.ToArray());//允許將攔截器服務的列表分配給注冊。
注意其中的兩個方法
至此AOP攔截器就完成了,如果你有現成Controller 和業務層 代碼替換后運行就能在Action運行結束后進入LogAOP
下面寫個完整例子:
Controller:
/// <summary> /// 博客API /// </summary> [Route("api/[controller]")] [ApiController] public class BlogArticleController : ControllerBase { readonly IBlogArticleServices _blogArticleServices; public BlogArticleController(IBlogArticleServices blogArticleServices) { _blogArticleServices = blogArticleServices; } /// <summary> /// 獲取博客列表 /// </summary> /// <returns></returns> public async Task<List<BlogArticle>> getBlogs() { var blogList = await _dal.Query(a => a.bID > 0, a => a.bID); return blogList; } }
Iserver接口層
public interface IBlogArticleServices:IBaseServices<BlogArticle> { Task<List<BlogArticle>> getBlogs(); }
接口base
public interface IBaseServices<TEntity> where TEntity : class { }
Server層
public class BlogArticleServices : BaseServices<BlogArticle>, IBlogArticleServices { IBlogArticleRepository _dal; public BlogArticleServices(IBlogArticleRepository dal) { this._dal = dal; base.BaseDal = dal; } /// <summary> /// 獲取博客列表 /// </summary> /// <returns></returns> public async Task<List<BlogArticle>> getBlogs() { var blogList = await _dal.Query(a => a.bID > 0, a => a.bID); return blogList; } }
ServerBase
public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new() { public IBaseRepository<TEntity> BaseDal;//通過在子類的構造函數中注入,這里是基類,不用構造函數 }
public class BlogArticle { /// <summary> /// 主鍵 /// </summary> /// 這里之所以沒用RootEntity,是想保持和之前的數據庫一致,主鍵是bID,不是Id [SugarColumn(IsNullable = false, IsPrimaryKey = true, IsIdentity = true)] public int bID { get; set; } /// <summary> /// 創建人 /// </summary> [SugarColumn(Length = 60, IsNullable = true)] public string bsubmitter { get; set; } /// <summary> /// 標題blog /// </summary> [SugarColumn(Length = 256, IsNullable = true)] public string btitle { get; set; } /// <summary> /// 類別 /// </summary> [SugarColumn(Length = int.MaxValue, IsNullable = true)] public string bcategory { get; set; } /// <summary> /// 內容 /// </summary> [SugarColumn(IsNullable = true, ColumnDataType = "text")] public string bcontent { get; set; } /// <summary> /// 訪問量 /// </summary> public int btraffic { get; set; } /// <summary> /// 評論數量 /// </summary> public int bcommentNum { get; set; } /// <summary> /// 修改時間 /// </summary> public DateTime bUpdateTime { get; set; } /// <summary> /// 創建時間 /// </summary> public System.DateTime bCreateTime { get; set; } /// <summary> /// 備注 /// </summary> [SugarColumn(Length = int.MaxValue, IsNullable = true)] public string bRemark { get; set; } /// <summary> /// 邏輯刪除 /// </summary> [SugarColumn(IsNullable = true)] public bool? IsDeleted { get; set; } }
倉儲層不寫了,請參考大神的 https://www.cnblogs.com/laozhang-is-phi/p/9529480.html 實現,我也是跟着大神學的,自己實現的功能記錄總結一下
BlogLogAOP
/// <summary> /// 攔截器BlogLogAOP 繼承IInterceptor接口 /// </summary> public class BlogLogAOP : IInterceptor { private readonly IHttpContextAccessor _accessor; public BlogLogAOP(IHttpContextAccessor accessor) { _accessor = accessor; } /// <summary> /// 實例化IInterceptor唯一方法 /// </summary> /// <param name="invocation">包含被攔截方法的信息</param> public void Intercept(IInvocation invocation) { string UserName = _accessor.HttpContext.User.Identity.Name; //記錄被攔截方法信息的日志信息 var dataIntercept = "" + $"【當前操作用戶】:{ UserName} \r\n" + $"【當前執行方法】:{ invocation.Method.Name} \r\n" + $"【攜帶的參數有】: {string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray())} \r\n"; try { MiniProfiler.Current.Step($"執行Service方法:{invocation.Method.Name}() -> "); //在被攔截的方法執行完畢后 繼續執行當前方法,注意是被攔截的是異步的 invocation.Proceed(); // 異步獲取異常,先執行 if (IsAsyncMethod(invocation.Method)) { //Wait task execution and modify return value if (invocation.Method.ReturnType == typeof(Task)) { invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithPostActionAndFinally( (Task)invocation.ReturnValue, ex => { LogEx(ex, ref dataIntercept); }); } else //Task<TResult> { invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithPostActionAndFinallyAndGetResult( invocation.Method.ReturnType.GenericTypeArguments[0], invocation.ReturnValue, ex => { LogEx(ex, ref dataIntercept); }); } } else {// 同步1 } } catch (Exception ex)// 同步2 { LogEx(ex, ref dataIntercept); } var type = invocation.Method.ReturnType; if (typeof(Task).IsAssignableFrom(type)) { var resultProperty = type.GetProperty("Result"); dataIntercept += ($"【執行完成結果】:{JsonConvert.SerializeObject(resultProperty.GetValue(invocation.ReturnValue))}"); } else { dataIntercept += ($"【執行完成結果】:{invocation.ReturnValue}"); } // 你的日志記錄 比如log4
Console.WriteLine(dataIntercept);
} private void LogEx(Exception ex, ref string dataIntercept) { if (ex != null) { //執行的 service 中,收錄異常 MiniProfiler.Current.CustomTiming("Errors:", ex.Message); //執行的 service 中,捕獲異常 dataIntercept += ($"方法執行中出現異常:{ex.Message + ex.InnerException}\r\n"); } } public static bool IsAsyncMethod(MethodInfo method) { return ( method.ReturnType == typeof(Task) || (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) ); } } internal static class InternalAsyncHelper { public static async Task AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Action<Exception> finalAction) { Exception exception = null; try { await actualReturnValue; } catch (Exception ex) { exception = ex; } finally { finalAction(exception); } } public static async Task<T> AwaitTaskWithPostActionAndFinallyAndGetResult<T>(Task<T> actualReturnValue, Func<Task> postAction, Action<Exception> finalAction) { Exception exception = null; try { var result = await actualReturnValue; await postAction(); return result; } catch (Exception ex) { exception = ex; throw; } finally { finalAction(exception); } } public static object CallAwaitTaskWithPostActionAndFinallyAndGetResult(Type taskReturnType, object actualReturnValue, Action<Exception> finalAction) { return typeof(InternalAsyncHelper) .GetMethod("AwaitTaskWithPostActionAndFinallyAndGetResult", BindingFlags.Public | BindingFlags.Static) .MakeGenericMethod(taskReturnType) .Invoke(null, new object[] { actualReturnValue, finalAction }); } }
Startup 的 ConfigureContainer方法
public void ConfigureContainer(ContainerBuilder builder) { var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; //注冊要通過反射創建的組件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); #region 帶有接口層的服務注入 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); //服務層 var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); //倉儲層 if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile))) { throw new Exception("Repository.dll和service.dll 丟失,因為項目解耦了,所以需要先F6編譯,再F5運行,請檢查 bin 文件夾,並拷貝。"); } // AOP var cacheType = new List<Type>(); builder.RegisterType<BlogLogAOP>(); cacheType.Add(typeof(BlogLogAOP)); // 獲取 Service.dll 程序集服務,並注冊 var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerDependency() .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; .InterceptedBy(cacheType.ToArray());//允許將攔截器服務的列表分配給注冊。 // 獲取 Repository.dll 程序集服務,並注冊 var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository) .AsImplementedInterfaces() .InstancePerDependency(); #endregion #region 沒有接口層的服務層注入 //因為沒有接口層,所以不能實現解耦,只能用 Load 方法。 //注意如果使用沒有接口的服務,並想對其使用 AOP 攔截,就必須設置為虛方法 //var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); //builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); #endregion #region 沒有接口的單獨類 class 注入 //只能注入該類中的虛方法 //builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) // .EnableClassInterceptors() // .InterceptedBy(cacheType.ToArray()); #endregion }
AOP中使用了 IHttpContextAccessor 所以要先啟動實例化它
建一個
public static class HttpContextSetup { public static void AddHttpContextSetup(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddScoped<IUser, AspNetUser>(); } }
IUser
public interface IUser { string Name { get; } }
AspNetUser
public class AspNetUser : IUser { private readonly IHttpContextAccessor _accessor; public AspNetUser(IHttpContextAccessor accessor) { _accessor = accessor; } public string Name => _accessor.HttpContext.User.Identity.Name; }
然后在 Startup 文件 ConfigureServices方法啟動
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //HttpContext services.AddHttpContextSetup(); }
這就完了,可以運行可以實現了,寫的很爛,稍后吧代碼附上
鏈接: https://pan.baidu.com/s/1Y17z8dQbbw0SYpFnYm0DuQ 提取碼: ppwy