ASP.NET Core搭建多层网站架构【9.2-使用Castle.Core实现动态代理拦截器】
2020/01/31, ASP.NET Core 3.1, VS2019, Autofac.Extras.DynamicProxy 4.5.0, Castle.Core.AsyncInterceptor 1.7.0
摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构【9.2-使用Castle.Core实现动态代理拦截器】
介绍了如何对业务层方法进行拦截,捕获业务方法发生的错误,然后统一进行日志记录,避免在每个业务方法中进行try catch捕获异常
本章节介绍了如何对业务层方法进行拦截,捕获业务方法发生的错误,然后统一进行日志记录,避免在每个业务方法中进行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>
- Autofac.Extras.DynamicProxy包和Autofac.Extensions.DependencyInjection包中的autofac版本最好一致,否则可能会出现代理拦截器注册失败的情况
- 可以先按我的包版本使用,确保拦截器无问题,再把包更新到最新版
- Autofac.Extras.DynamicProxy包是使用Autofac和Castle.Core实现动态代理拦截器
- Castle.Core.AsyncInterceptor包是实现异步拦截器(否则只能对同步方法进行拦截)
MS.Component.Aop
类库要依赖MS.WebCore
类库。
编写服务拦截器
在MS.Component.Aop
类库中添加LogAop文件夹,在该文件夹下新建AopHandledException.cs
、LogInterceptor.cs
、LogInterceptorAsync.cs
AopHandledException.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异常类型,目的是为了:
- 拦截器捕获异常后已经进行了日志记录,这个异常可能需要继续抛出去,此时用AopHandledException异常类型包一层异常,这样后面再捕获到的异常就能判断是从Aop中抛出来的异常了,不再重复记录日志
LogInterceptor.cs
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中再去区分是异步方法还是同步方法
LogInterceptorAsync.cs
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
}
}
这里是对方法拦截的主要实现:
- InterceptSynchronous是同步方法拦截时使用
- InterceptAsynchronous是异步方法返回Task时使用
- InterceptAsynchronous是异步方法返回Task(包含返回值)时使用
invocation.Proceed();
这句话即是调用真正的业务方法,所以在该方法之前可以做一些权限判断的内容,在该方法之后可以获取记录业务返回结果- 可以在invocation参数中获取方法名称、传递的参数等信息
- 如果做了用户登录,可以通过构造函数依赖注入IHttpContextAccessor以拿到执行业务者的信息(其实使用NLog记录日志时,NLog也能获取到登录用户的信息,就是NetUserIdentity这个参数)
- 在拦截器中,我对每个业务方法都以Debug的日志等级记录了调用业务方法的返回结果
至此对业务方法进行拦截,以Debug的日志等级记录了调用业务方法的返回结果,并捕获所有业务方法的异常已经完成。
封装Ioc注册
在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))]加在类上,但是上面的方法比较好,没有侵入性
}
}
}
说明:
- 在AddAopService方法中,传递了业务层程序集名称,使用Autofac统一注册业务层所有的接口和实现(这样就不用每个接口都写一次注册了),注册类型为InstancePerLifetimeScope
- 注册业务的同时,开启了代理拦截器:EnableInterfaceInterceptors
- 开启代理拦截器的同时,InterceptedBy传递了要使用哪些拦截器进行拦截,这里只有同步的,因为异步方法拦截器还是先走同步拦截器
- 业务层注册拦截器也可以使用[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进行批量的注册接口和实现。
注册Aop服务
在MS.WebApi
应用程序中,Startup.cs:
在ConfigureContainer方法里删掉原先对IBaseService、IRoleService的接口注册,使用刚写的Aop注册,给业务层批量注册同时开启代理拦截:
//using MS.Services;
//以上代码添加至using
//注册aop拦截器
//将业务层程序集名称传了进去,给业务层接口和实现做了注册,也给业务层各方法开启了代理
builder.AddAopService(ServiceExtensions.GetAssemblyName());
完成后,代码如下图所示
至此,业务层方法的代理拦截都完成了,执行业务时,会在控制台显示每次业务的调用返回结果,如果遇到异常,会统一捕获并记录日志然后把异常包装一次再抛出去
启动项目,打开Postman测试接口:
可以看到返回结果显示出来了,可以打断点看看程序到底怎么通过拦截器的。
这里只是实现了业务层方法拦截,异常日志的捕获,可以做更多例如权限验证的拦截器。
项目完成后,如下图所示
ASP.NET Core搭建多层网站架构【9.2-使用Castle.Core实现动态代理拦截器】的更多相关文章
- ASP.NET Core搭建多层网站架构【0-前言】
2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构 目录 0-前言 1-项目结构分层建立 2-公共基 ...
- ASP.NET Core搭建多层网站架构【1-项目结构分层建立】
2020/01/26, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[1-项目结构分层建立] 文章目录 此分支项目代码 ...
- ASP.NET Core搭建多层网站架构【2-公共基础库】
2020/01/28, ASP.NET Core 3.1, VS2019,Newtonsoft.Json 12.0.3, Microsoft.AspNetCore.Cryptography.KeyDe ...
- ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】
2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...
- ASP.NET Core搭建多层网站架构【4-工作单元和仓储设计】
2020/01/28, ASP.NET Core 3.1, VS2019, Microsoft.EntityFrameworkCore.Relational 3.1.1 摘要:基于ASP.NET Co ...
- ASP.NET Core搭建多层网站架构【5-网站数据库实体设计及映射配置】
2020/01/29, ASP.NET Core 3.1, VS2019, EntityFrameworkCore 3.1.1, Microsoft.Extensions.Logging.Consol ...
- ASP.NET Core搭建多层网站架构【6-注册跨域、网站核心配置】
2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...
- ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】
2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...
- ASP.NET Core搭建多层网站架构【8.1-使用ViewModel注解验证】
2020/01/29, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[8.1-使用ViewModel注解验证] 使用V ...
随机推荐
- Dockerfile深度剖析
Dockerfile主要是用来定制镜像 Dockerfile指令集 FROM ###指定基础镜像第一条指令.scratch是虚拟的镜像,表示一个空白的镜像. FROM centos:7.5 MAI ...
- 使用c#做前台页面
1.有很多组件,组件右属性,事件 2.在table中,操作用的是图片 3.打开dialog时,其他窗体不能使用 4.在子窗体编辑完,对后台操作后,在父窗体加载一下数据
- java程序启动参数
例如 启动进程如下 /home/work/noah/ccs/jc-controller/jdk1.7.0_55/bin/java -Xmx4096m -Xms4096m -Xmn1024m -XX:+ ...
- 【Python】计算圆的面积
代码: r=29 area = 3.1415*r*r print(area) print("{:.2f}".format(area)) 结果:
- C语言究竟是一门怎样的语言?
对于大部分程序员,C语言是学习编程的第一门语言,很少有不了解C的程序员. C语言除了能让你了解编程的相关概念,带你走进编程的大门,还能让你明白程序的运行原理,比如,计算机的各个部件是如何交互的,程序在 ...
- 如何预测股票分析--先知(Prophet)
在上一篇中,我们探讨了自动ARIMA,但是好像表现的还是不够完善,接下来看看先知的力量! 先知(Prophet) 有许多时间序列技术可以用在股票预测数据集上,但是大多数技术在拟合模型之前需要大量的数据 ...
- 主席树 hdu 4417
求一个区间内小于等于limit的数: 主席树模板题. 求出每一个节点的sum: #include<cstdio> #include<algorithm> #include< ...
- Go性能调优
文章引用自 Go性能调优 在计算机性能调试领域里,profiling 是指对应用程序的画像,画像就是应用程序使用 CPU 和内存的情况. Go语言是一个对性能特别看重的语言,因此语言中自带了 pr ...
- 2019冬季PAT甲级第二题
#define HAVE_STRUCT_TIMESPEC #include<bits/stdc++.h> using namespace std; typedef struct{ int ...
- CSS学习(9)块盒模型应用
1.改变宽高范围 默认情况下,width和height设置的是内容盒的宽高 页面重构师:将psd文件(设计稿)制作为静态页面 衡量设计稿尺寸的时候,往往使用的是边框盒 CSS3中 box-sizing ...