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测试接口:
能够看到返回结果显示出来了,能够打断点看看程序到底怎么经过拦截器的。
这里只是实现了业务层方法拦截,异常日志的捕获,能够作更多例如权限验证的拦截器。
项目完成后,以下图所示