2020/01/31, ASP.NET Core 3.1, VS2019, Autofac.Extras.DynamicProxy 4.5.0, Castle.Core.AsyncInterceptor 1.7.0html
摘要:基於ASP.NET Core 3.1 WebApi搭建後端多層網站架構【9.2-使用Castle.Core實現動態代理攔截器】
介紹瞭如何對業務層方法進行攔截,捕獲業務方法發生的錯誤,而後統一進行日誌記錄,避免在每一個業務方法中進行try catch捕獲異常git
文章目錄程序員
此分支項目代碼github
本章節介紹瞭如何對業務層方法進行攔截,捕獲業務方法發生的錯誤,而後統一進行日誌記錄,避免在每一個業務方法中進行try catch捕獲異常。藉助Autofac和Castle.Core實現動態代理攔截器,其中使用Castle.Core.AsyncInterceptor包實現異步攔截器。後端
向MS.Component.Aop
類庫中添加如下包引用:架構
<ItemGroup> <PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" /> <PackageReference Include="Castle.Core.AsyncInterceptor" Version="1.7.0" /> </ItemGroup>
MS.Component.Aop
類庫要依賴MS.WebCore
類庫。在MS.Component.Aop
類庫中添加LogAop文件夾,在該文件夾下新建AopHandledException.cs
、LogInterceptor.cs
、LogInterceptorAsync.cs
異步
using System; namespace MS.Component.Aop { /// <summary> /// 使用自定義的Exception,用於在aop中已經處理過的異常,在其餘地方不用重複記錄日誌 /// </summary> public class AopHandledException : ApplicationException { public string ErrorMessage { get; private set; } public Exception InnerHandledException { get; private set; } //無參數構造函數 public AopHandledException() { } //帶一個字符串參數的構造函數,做用:當程序員用Exception類獲取異常信息而非 MyException時把自定義異常信息傳遞過去 public AopHandledException(string msg) : base(msg) { this.ErrorMessage = msg; } //帶有一個字符串參數和一個內部異常信息參數的構造函數 public AopHandledException(string msg, Exception innerException) : base(msg) { this.InnerHandledException = innerException; this.ErrorMessage = msg; } public string GetError() { return ErrorMessage; } } }
這裏自定義了一個AopHandledException異常類型,目的是爲了:async
using Castle.DynamicProxy; namespace MS.Component.Aop { public class LogInterceptor : IInterceptor { private readonly LogInterceptorAsync _logInterceptorAsync; public LogInterceptor(LogInterceptorAsync logInterceptorAsync) { _logInterceptorAsync = logInterceptorAsync; } public void Intercept(IInvocation invocation) { _logInterceptorAsync.ToInterceptor().Intercept(invocation); } } }
這個是主攔截器,繼承自IInterceptor,無論是同步方法仍是異步方法,都將會走其中的Intercept方法,而後會在LogInterceptorAsync中再去區分是異步方法仍是同步方法函數
using Castle.DynamicProxy; using Microsoft.Extensions.Logging; using MS.Common.Extensions; using System; using System.Linq; using System.Threading.Tasks; namespace MS.Component.Aop { public class LogInterceptorAsync : IAsyncInterceptor { private readonly ILogger<LogInterceptorAsync> _logger; public LogInterceptorAsync(ILogger<LogInterceptorAsync> logger) { _logger = logger; } /// <summary> /// 同步方法攔截時使用 /// </summary> /// <param name="invocation"></param> public void InterceptSynchronous(IInvocation invocation) { try { //調用業務方法 invocation.Proceed(); LogExecuteInfo(invocation, invocation.ReturnValue.ToJsonString());//記錄日誌 } catch (Exception ex) { LogExecuteError(ex, invocation); throw new AopHandledException(); } } /// <summary> /// 異步方法返回Task時使用 /// </summary> /// <param name="invocation"></param> public void InterceptAsynchronous(IInvocation invocation) { try { //調用業務方法 invocation.Proceed(); LogExecuteInfo(invocation, invocation.ReturnValue.ToJsonString());//記錄日誌 } catch (Exception ex) { LogExecuteError(ex, invocation); throw new AopHandledException(); } } /// <summary> /// 異步方法返回Task<T>時使用 /// </summary> /// <typeparam name="TResult"></typeparam> /// <param name="invocation"></param> public void InterceptAsynchronous<TResult>(IInvocation invocation) { //調用業務方法 invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation); } private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) { try { //調用業務方法 invocation.Proceed(); Task<TResult> task = (Task<TResult>)invocation.ReturnValue; TResult result = await task;//得到返回結果 LogExecuteInfo(invocation, result.ToJsonString()); return result; } catch (Exception ex) { LogExecuteError(ex, invocation); throw new AopHandledException(); } } #region helpMethod /// <summary> /// 獲取攔截方法信息(類名、方法名、參數) /// </summary> /// <param name="invocation"></param> /// <returns></returns> private string GetMethodInfo(IInvocation invocation) { //方法類名 string className = invocation.Method.DeclaringType.Name; //方法名 string methodName = invocation.Method.Name; //參數 string args = string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()); if (string.IsNullOrWhiteSpace(args)) { return $"{className}.{methodName}"; } else { return $"{className}.{methodName}:{args}"; } } private void LogExecuteInfo(IInvocation invocation, string result) { _logger.LogDebug("方法{0},返回值{1}", GetMethodInfo(invocation), result); } private void LogExecuteError(Exception ex, IInvocation invocation) { _logger.LogError(ex, "執行{0}時發生錯誤!", GetMethodInfo(invocation)); } #endregion } }
這裏是對方法攔截的主要實現:測試
invocation.Proceed();
這句話便是調用真正的業務方法,因此在該方法以前能夠作一些權限判斷的內容,在該方法以後能夠獲取記錄業務返回結果至此對業務方法進行攔截,以Debug的日誌等級記錄了調用業務方法的返回結果,並捕獲全部業務方法的異常已經完成。
在MS.Component.Aop
類庫中添加AopServiceExtensions.cs
類:
using Autofac; using Autofac.Extras.DynamicProxy; using System; using System.Reflection; namespace MS.Component.Aop { public static class AopServiceExtension { /// <summary> /// 註冊aop服務攔截器 /// 同時註冊了各業務層接口與實現 /// </summary> /// <param name="builder"></param> /// <param name="serviceAssemblyName">業務層程序集名稱</param> public static void AddAopService(this ContainerBuilder builder, string serviceAssemblyName) { //註冊攔截器,同步異步都要 builder.RegisterType<LogInterceptor>().AsSelf(); builder.RegisterType<LogInterceptorAsync>().AsSelf(); //註冊業務層,同時對業務層的方法進行攔截 builder.RegisterAssemblyTypes(Assembly.Load(serviceAssemblyName)) .AsImplementedInterfaces().InstancePerLifetimeScope() .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy; .InterceptedBy(new Type[] { typeof(LogInterceptor) })//這裏只有同步的,由於異步方法攔截器仍是先走同步攔截器 ; //業務層註冊攔截器也可使用[Intercept(typeof(LogInterceptor))]加在類上,可是上面的方法比較好,沒有侵入性 } } }
說明:
在MS.Services
中添加ServiceExtensions.cs
類:
using System.Reflection; namespace MS.Services { public static class ServiceExtensions { /// <summary> /// 獲取程序集名稱 /// </summary> /// <returns></returns> public static string GetAssemblyName() { return Assembly.GetExecutingAssembly().GetName().Name; } } }
用於獲取業務層的程序集名稱,提供給Autofac進行批量的註冊接口和實現。
在MS.WebApi
應用程序中,Startup.cs:
在ConfigureContainer方法裏刪掉原先對IBaseService、IRoleService的接口註冊,使用剛寫的Aop註冊,給業務層批量註冊同時開啓代理攔截:
//using MS.Services; //以上代碼添加至using //註冊aop攔截器 //將業務層程序集名稱傳了進去,給業務層接口和實現作了註冊,也給業務層各方法開啓了代理 builder.AddAopService(ServiceExtensions.GetAssemblyName());
完成後,代碼以下圖所示
至此,業務層方法的代理攔截都完成了,執行業務時,會在控制檯顯示每次業務的調用返回結果,若是遇到異常,會統一捕獲並記錄日誌而後把異常包裝一次再拋出去
啓動項目,打開Postman測試接口:
能夠看到返回結果顯示出來了,能夠打斷點看看程序到底怎麼經過攔截器的。
這裏只是實現了業務層方法攔截,異常日誌的捕獲,能夠作更多例如權限驗證的攔截器。
項目完成後,以下圖所示