Asp.net Core 3.1基于AspectCore实现AOP实现事务、缓存拦截器功能

 更新时间:2021年9月22日 09:59  点击:1764

最近想给我的框架加一种功能,就是比如给一个方法加一个事务的特性Attribute,那这个方法就会启用事务处理。给一个方法加一个缓存特性,那这个方法就会进行缓存。

这个也是网上说的面向切面编程AOP。

AOP的概念也很好理解,跟中间件差不多,说白了,就是我可以任意地在方法的前面或后面添加代码,这很适合用于缓存、日志等处理。

在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore。

用起来非常非常的简单,但一开始还是走了一点弯路,主要是网上都是net core3以下的教程,3以下的使用方法跟之前有一些不同。

先安装NuGet包,包名:AspectCore.Extensions.DependencyInjection

然后在Program.cs类中增加一行代码,这是net core 3的不同之处,这句添加的代码,意思就是用AspectCore的IOC容器替换内置的。因为AOP需要依靠IOC实现,所以必须得替换掉内置的IOC。

public class Program
  {
    public static void Main(string[] args)
    {
      CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
      Host.CreateDefaultBuilder(args)
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      })
      //用AspectCore替换默认的IOC容器
      .UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
}

然后在Startup.cs类中的ConfigureServices中添加代码。(其实这个加不加都可以,如果需要配置就加,例如全局的拦截器、只拦截哪些匹配的服务,因为我只用特性进行拦截,所以我就什么也没配置)

services.ConfigureDynamicProxy(o=> { 
   //添加AOP的配置
});

这样AOP就配置好了,是不是很简单。

当然使用方面也需要注意一下,可以在接口、接口的方法、类,类的virtual方法上进行拦截。还有如果你想拦截控制器的action的话,那需要在ConfigureService里AddControllerAsServices

services.AddControllers()
//把控制器当成服务
.AddControllersAsServices()

下面我列出我的事务拦截器代码,如果是特性拦截,就继承AbstractInterceptorAttribute,如果要写一个全局拦截器,就AbstractInterceptor,然后在ConfigureDynamicProxy中进行配置,这个我就不介绍了

如果你的拦截器是放在其他项目的,那要记得添加AspectCore.Core包,不要只添加AspectCore.Abstractions,我一开始就只添加了AspectCore.Abstractions,一直没发现IsAsync、UnwrapAsyncReturnValue等一些扩展方法。

public class TransactionInterceptorAttribute : AbstractInterceptorAttribute
  {
    public async override Task Invoke(AspectContext context, AspectDelegate next)
    {
      var dbContext = context.ServiceProvider.GetService<AppDbContext>();
      //先判断是否已经启用了事务
      if (dbContext.Database.CurrentTransaction == null)
      {
        await dbContext.Database.BeginTransactionAsync();
        try
        {
          await next(context);
          dbContext.Database.CommitTransaction();
        }
        catch (Exception ex)
        {
          dbContext.Database.RollbackTransaction();
          throw ex;
        }
      }
      else
      {
        await next(context);
      }
    }
  }

然后我就可以这么优雅地使用事务了

我再列出我的缓存拦截器,(感谢网友的提醒,我做了一下修改,针对异步方法返回值的处理),对了,下面的ICacheHelper是我定义的一个缓存助手接口,用的是redis,我会在后面写一篇博客

public class CacheInterceptorAttribute : AbstractInterceptorAttribute
  {
    /// <summary>
    /// 缓存秒数
    /// </summary>
    public int ExpireSeconds { get; set; }

    public async override Task Invoke(AspectContext context, AspectDelegate next)
    {
      //判断是否是异步方法
      bool isAsync = context.IsAsync();
      //if (context.ImplementationMethod.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null)
      //{
      //  isAsync = true;
      //}
      //先判断方法是否有返回值,无就不进行缓存判断
      var methodReturnType = context.GetReturnParameter().Type;
      if (methodReturnType == typeof(void) || methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask))
      {
        await next(context);
        return;
      }
      var returnType = methodReturnType;
      if (isAsync)
      {
        //取得异步返回的类型
        returnType = returnType.GenericTypeArguments.FirstOrDefault();
      }
      //获取方法参数名
      string param = CommonHelper.ObjectToJsonString(context.Parameters);
      //获取方法名称,也就是缓存key值
      string key = "Methods:" + context.ImplementationMethod.DeclaringType.FullName + "." + context.ImplementationMethod.Name;
      var cache = context.ServiceProvider.GetService<ICacheHelper>();
      //如果缓存有值,那就直接返回缓存值
      if (cache.HashExists(key, param))
      {
        //反射获取缓存值,相当于cache.HashGet<>(key,param)
        var value = typeof(ICacheHelper).GetMethod(nameof(ICacheHelper.HashGet)).MakeGenericMethod(returnType).Invoke(cache, new[] { key, param });
        if (isAsync)
        {
          //判断是Task还是ValueTask
          if (methodReturnType == typeof(Task<>).MakeGenericType(returnType))
          {
            //反射获取Task<>类型的返回值,相当于Task.FromResult(value)
            context.ReturnValue = typeof(Task).GetMethod(nameof(Task.FromResult)).MakeGenericMethod(returnType).Invoke(null, new[] { value });
          }
          else if (methodReturnType == typeof(ValueTask<>).MakeGenericType(returnType))
          {
            //反射构建ValueTask<>类型的返回值,相当于new ValueTask(value)
            context.ReturnValue = Activator.CreateInstance(typeof(ValueTask<>).MakeGenericType(returnType), value);
          }
        }
        else
        {
          context.ReturnValue = value;
        }
        return;
      }
      await next(context);
      object returnValue;
      if (isAsync)
      {
        returnValue = await context.UnwrapAsyncReturnValue();
        //反射获取异步结果的值,相当于(context.ReturnValue as Task<>).Result
        //returnValue = typeof(Task<>).MakeGenericType(returnType).GetProperty(nameof(Task<object>.Result)).GetValue(context.ReturnValue);

      }
      else
      {
        returnValue = context.ReturnValue;
      }
      cache.HashSet(key, param, returnValue);
      if (ExpireSeconds > 0)
      {
        cache.SetExpire(key, TimeSpan.FromSeconds(ExpireSeconds));
      }

    }
  }

我还弄了一个缓存删除拦截器,作用就是带有这个特性的方法执行后,会删除相关缓存值

为什么有这个设计呢,比如说我给一个方法 GetUserList 加了缓存,那我数据改变了怎么办,我想在User数据改变时,把这个缓存删除掉,那我就可以在SaveUser方法上加上我这个缓存删除拦截器,那这个方法执行后,就会把相关的缓存删除掉了

public class CacheDeleteInterceptorAttribute : AbstractInterceptorAttribute
{
  private readonly Type[] _types;
  private readonly string[] _methods;

  /// <summary>
  /// 需传入相同数量的Types跟Methods,同样位置的Type跟Method会组合成一个缓存key,进行删除
  /// </summary>
  /// <param name="Types">传入要删除缓存的类</param>
  /// <param name="Methods">传入要删除缓存的方法名称,必须与Types数组对应</param>
  public CacheDeleteInterceptorAttribute(Type[] Types, string[] Methods)
  {
    if (Types.Length != Methods.Length)
    {
      throw new ApiFailException(ApiFailCode.OPERATION_FAIL, "Types必须跟Methods数量一致");
    }
    _types = Types;
    _methods = Methods;
  }

  public async override Task Invoke(AspectContext context, AspectDelegate next)
  {
    var cache = context.ServiceProvider.GetService<ICacheHelper>();
    await next(context);
    for (int i = 0; i < _types.Length; i++)
    {
      var type = _types[i];
      var method = _methods[i];
      string key = "Methods:" + type.FullName + "." + method;
      cache.Delete(key);
    }
  }
}

AOP的实现原理我也想象了一下:

要实现AOP,需要依靠IOC容器,因为它是我们类的管家,那能被拦截的类必须是IOC注入的,自己new出来的是不受拦截的。如果我想在A方法前面添加点代码,那我告诉IOC,把代码给它,那IOC在注入A方法所在类时,会继承它生成一个派生类,然后重写A方法,所以拦截方法必须得为virtual,然后A方法里写上我要添加的代码,再base.A()这样。

到此这篇关于Asp.net Core 3.1基于AspectCore实现AOP实现事务、缓存拦截器功能的文章就介绍到这了,更多相关Asp.net Core 3.1实现事务、缓存拦截器内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • ASP.NET购物车实现过程详解

    这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • .NET Core下使用Kafka的方法步骤

    这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 在ASP.NET 2.0中操作数据之七十二:调试存储过程

    在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
  • 详解.NET Core 3.0 里新的JSON API

    这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • MYSQL事务回滚的2个问题分析

    因此,正确的原子操作是真正被执行过的。是物理执行。在当前事务中确实能看到插入的记录。最后只不过删除了。但是AUTO_INCREMENT不会应删除而改变值。1、为什么auto_increament没有回滚?因为innodb的auto_increament的...2014-05-31
  • ASP.NET Core根据环境变量支持多个 appsettings.json配置文件

    这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 记一次EFCore类型转换错误及解决方案

    这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
  • 解决@Transactional注解事务不回滚不起作用的问题

    这篇文章主要介绍了解决@Transactional注解事务不回滚不起作用的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-23
  • 详解ASP.NET Core 中基于工厂的中间件激活的实现方法

    这篇文章主要介绍了ASP.NET Core 中基于工厂的中间件激活的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • asp.net通过消息队列处理高并发请求(以抢小米手机为例)

    这篇文章主要介绍了asp.net通过消息队列处理高并发请求(以抢小米手机为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表

    在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有...2016-05-19
  • ASP.NET单选按钮控件RadioButton常用属性和方法介绍

    RadioButton又称单选按钮,其在工具箱中的图标为 ,单选按钮通常成组出现,用于提供两个或多个互斥选项,即在一组单选钮中只能选择一个...2021-09-22
  • 详解.NET Core 使用HttpClient SSL请求出错的解决办法

    这篇文章主要介绍了.NET Core 使用HttpClient SSL请求出错的解决办法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-09-22
  • 基于Postgresql 事务的提交与回滚解析

    这篇文章主要介绍了基于Postgresql 事务的提交与回滚解析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-18
  • ASP.NET中iframe框架点击左边页面链接 右边显示链接页面内容

    这篇文章主要介绍了ASP.NET中iframe框架点击左边页面链接,右边显示链接页面内容的实现代码,感兴趣的小伙伴们可以参考一下...2021-09-22
  • 创建一个完整的ASP.NET Web API项目

    ASP.NET Web API具有与ASP.NET MVC类似的编程方式,ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。下面创建一个简单的Web API项目,需要的朋友可以参考下...2021-09-22
  • ASP.NET连接MySql数据库的2个方法及示例

    这篇文章主要介绍了ASP.NET连接MySql数据库的2个方法及示例,使用的是MySQL官方组件和ODBC.NET,需要的朋友可以参考下...2021-09-22
  • Spring异常捕获且回滚事务解决方案

    这篇文章主要介绍了Spring异常捕获且回滚事务解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-03
  • mysql事务对效率的影响分析总结

    在本篇文章里小编给大家分享的是一篇关于mysql事务对效率的影响分析总结内容,有需要的朋友们可以跟着学习下。...2021-10-24