NET Core3.1 基于AutoFac 的AOP
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