Asp .Net Core 系列:基于 Castle DynamicProxy + Autofac 实践 AOP 以及实现事务、用户填充功能

asp,net,core,castle,dynamicproxy,autofac,aop · 浏览次数 : 10

小编点评

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,以提高代码的模块化性、可维护性和复用性。 .Net Core 中的 AOP 框架有: 1. PostSharp(收费) 2. Castle DynamicProxy 3. AspectCore Framework Castle DynamicProxy 是 Castle 项目的一部分,它允许开发者在运行时动态创建代理类,这些代理类可以拦截对目标对象的调用,并在调用前后执行自定义逻辑。 在 Castle DynamicProxy 基础上实现 AOP 的步骤如下: 1. 安装 Castle.Core NuGet 包 2. 定义接口和类 3. 创建拦截器 4. 创建代理并调用方法 在 IOC(Inversionof Control,控制反转)中使用 Castle DynamicProxy,可以通过以下方式配置拦截器: 1. 使用 InterceptAttribute 特性 2. 使用 InterceptedBy() 方法 事务管理拦截器基类: [Transactional] public class UserService : IUserService { [Transactional] public void Test() { Console.WriteLine("Test"); } } 注入 IOC: builder.Services.AddHttpContextAccessor(); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureContainer(autofacBuilder => { autofacBuilder.RegisterType().As() .SingleInstance().AsImplementedInterfaces() .EnableInterfaceInterceptors() .InterceptedBy(typeof(UserAutoFillInterceptor)); }); 用户自动拦截器: public class UserAutoFillInterceptor : BaseInterceptor { private readonly IHttpContextUser _user; public UserAutoFillInterceptor(ILogger logger, IHttpContextUser user) : base(logger) { _user = user; } public override void InterceptHandle(IInvocation invocation) { // 对当前方法的特性验证 if (Method.Name?.ToLower() == "add" || Method.Name?.ToLower() == "update") { if (invocation.Arguments.Length == 1 && invocation.Arguments[0].GetType().IsClass) { dynamic argModel = invocation.Arguments[0]; var getType = argModel.GetType(); if (Method.Name?.ToLower() == "add") { if (getType.GetProperty("CreateUserId") != null) { argModel.CreateUserId = _user.UserId; } if (getType.GetProperty("CreateUserName") != null) { argModel.CreateUserName = _user.UserName; } if (getType.GetProperty("CreateTime") != null) { argModel.CreateTime = DateTime.Now; } } if (getType.GetProperty("UpdateUserId") != null) { argModel.UpdateUserId = _user.UserId; } if (getType.GetProperty("UpdateUserName") != null) { argModel.UpdateUserName = _user.UserName; } if (getType.GetProperty("UpdateTime") != null) { argModel.UpdateTime = DateTime.Now; } } } invocation.Proceed(); } }

正文

什么是 AOP ?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,以提高代码的模块化性、可维护性和复用性。

在传统的面向对象编程中,我们通常通过类和对象来组织和实现功能。然而,某些功能,如日志记录、事务管理、安全性检查等,可能会跨越多个对象和模块,这种跨越称为横切关注点。AOP 的核心思想是将这些横切关注点从业务逻辑中分离出来,通过特定的机制将它们应用到代码中,而不是通过直接修改业务逻辑来实现。

.Net Core 中 有哪些 AOP 框架?

PostSharp(收费)

PostSharp是一个功能强大的AOP框架,它通过编译器插件的形式集成到Visual Studio中。PostSharp支持编译时AOP(通过C#特性应用切面),并提供了丰富的切面类型,包括方法拦截、属性访问拦截、异常处理等。它还提供了商业支持和丰富的文档。

Castle DynamicProxy

Castle DynamicProxy是Castle项目的一部分,它允许开发者在运行时动态创建代理类,这些代理类可以拦截对目标对象的调用,并在调用前后执行自定义逻辑。虽然它本身不是一个完整的AOP框架,但它经常被用作构建AOP解决方案的基础。

AspectCore Framework

AspectCore 是一个开源的 AOP 框架,专为 .NET Core 设计。它提供了基于动态代理的运行时切面和方法拦截机制,支持常见的切面编程需求,如日志、缓存、事务等。

基于 Castle DynamicProxy 实现 AOP

1. 安装Castle.Core NuGet包

Install-Package Castle.Core

2. 定义接口和类

假设你有一个接口和一个实现了该接口的类,你想要拦截这个类的方法调用。

public interface IMyService  
{  
    void PerformAction();  
}   
public class MyService : IMyService  
{  
    public void PerformAction()  
    {  
        Console.WriteLine("Action performed.");  
    }  
}

3. 创建拦截器

接下来,你需要创建一个拦截器类,该类将实现IInterceptor接口。在这个接口的实现中,你可以定义在调用目标方法之前和之后要执行的逻辑。

using Castle.DynamicProxy;  
  
public class MyInterceptor : IInterceptor  
{  
    public void Intercept(IInvocation invocation)  
    {  
        // 在调用目标方法之前执行的逻辑  
        Console.WriteLine("Before method: " + invocation.Method.Name);  
  
        // 调用目标方法  
        invocation.Proceed();  
  
        // 在调用目标方法之后执行的逻辑  
        Console.WriteLine("After method: " + invocation.Method.Name);  
    }  
}

4. 创建代理并调用方法

最后,你需要使用ProxyGenerator类来创建MyService的代理实例,并指定拦截器。然后,你可以像使用普通对象一样调用代理的方法,但拦截器中的逻辑会在调用发生时执行。

using Castle.DynamicProxy;  
  
public class Program  
{  
    public static void Main(string[] args)  
    {  
        var generator = new ProxyGenerator();  
        var interceptor = new MyInterceptor();  
  
        // 创建MyService的代理实例,并指定拦截器  
        var proxy = generator.CreateInterfaceProxyWithTarget<IMyService>(  
            new MyService(), interceptor);  
  
        // 调用代理的方法,拦截器中的逻辑将被执行  
        proxy.PerformAction();  
    }  
}

注意,上面的示例使用了接口代理(CreateInterfaceProxyWithTarget),这意味着你的目标类必须实现一个或多个接口。如果你想要代理一个类而不是接口,你可以使用CreateClassProxyWithTarget方法(但这通常用于需要代理非虚方法或字段的场景,且要求目标类是可继承的)。

IOC中使用 Castle DynamicProxy

由于IOC容器(如Microsoft的IServiceCollectionIServiceProvider)通常不直接支持AOP,所以用 Autofac
1. 安装必要的 NuGet 包

首先,确保你的项目中安装了以下 NuGet 包:

Install-Package Autofac
Install-Package Autofac.Extensions.DependencyInjection
Install-Package Autofac.Extras.DynamicProxy
Install-Package Castle.Core

2. 创建服务接口和实现类

    public class User
    {
        public long Id { get; set; }

        public string Name { get; set; }

        public long CreateUserId { get; set; }

        public string CreateUserName { get; set; }

        public DateTime CreateTime { get; set; }

        public long UpdateUserId { get; set; }

        public string UpdateUserName { get; set; }

        public DateTime UpdateTime { get; set; }
    }    

    public interface IUserService
    {
        void Test();

        Task<int> TaskTest();

        void Add(User user);

       void Update(User user);
    }


    public class UserService : IUserService
    {
        public void Test()
        {
            Console.WriteLine("Test");
        }

        public async Task<int> TaskTest()
        {
            await Console.Out.WriteLineAsync("TaskTest");
            return 1;
        }

        public void Add(User user)
        {
            Console.WriteLine(user.CreateUserId);
            Console.WriteLine(user.CreateUserName);
        }

        public void Update(User user)
        {
            Console.WriteLine(user.UpdateUserId);
            Console.WriteLine(user.UpdateUserName);
        }
    }

    [ApiController]
    [Route("[controller]")]
    public class UserController : ControllerBase
    {
        readonly IUserService _userService;

        public UserController(IUserService userService)
        {
            _userService = userService;
        }        

        [HttpGet]
        [Route("/taskTest")]
        public async Task<string> TaskTest()
        {
            await _userService.TaskTest();
            return "ok";
        }

        [HttpGet]
        [Route("/test")]
        public string Test()
        {
            _userService.Test();
            return "ok";
        }

        [HttpGet]
        [Route("/add")]
        public string Add()
        {
            _userService.Add(new Model.User { Name = "张三" });
            return "ok";
        }


        [HttpGet]
        [Route("/update")]
        public string Update()
        {
            _userService.Update(new Model.User { Name = "张三" });
            return "ok";
        }
    }

3. 创建拦截器类

创建一个实现 IInterceptor 接口的拦截器类 LoggingInterceptor,用于拦截方法调用并添加日志记录:

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine($"Before executing: {invocation.Method.Name}");

        invocation.Proceed(); // 调用原始方法

        Console.WriteLine($"After executing: {invocation.Method.Name}");
    }
}

4. 配置 Autofac 容器

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) //使用Autofac
                        .ConfigureContainer<ContainerBuilder>(autofacBuilder =>
                        {
                       
                            autofacBuilder.RegisterType<LoggingInterceptor>();
                                             
                            autofacBuilder.RegisterType<UserService>().As<IUserService>    ().SingleInstance().AsImplementedInterfaces()    
                            .EnableInterfaceInterceptors() // 启用接口拦截器
                            .InterceptedBy(typeof(LoggingInterceptor)); //指定拦截器
                        });

与Autofac集成时,配置拦截器主要有两种方式

使用 InterceptAttribute 特性

这种方式通过在接口或类上添加[Intercept(typeof(YourInterceptor))]特性来指定拦截器。然后,在Autofac注册时,启用接口或类的拦截器。(通常不推荐在类上直接添加,因为这会使类与Autofac紧密耦合)

 [Intercept(typeof(UserAutoFillInterceptor))]
 public class UserService : IUserService
{  
     public void Test()
     {
        Console.WriteLine("Test");
     } 
}

autofacBuilder.RegisterType<UserService>().As<IUserService>().EnableInterfaceInterceptors() // 启用接口拦截器

使用 InterceptedBy() 方法

这种方式不依赖于[Intercept]特性,而是在注册服务时直接使用InterceptedBy()方法来指定拦截器。

                            autofacBuilder.RegisterType<UserService>().As<IUserService>()    
                            .EnableInterfaceInterceptors() // 启用接口拦截器
                            .InterceptedBy(typeof(LoggingInterceptor)); //指定拦截器

实现事务管理

拦截器基类

    /// <summary>
    /// 拦截基类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class BaseInterceptor<T> : IInterceptor
    {
        protected readonly ILogger<T> _logger;
        public BaseInterceptor(ILogger<T> logger)
        {
            _logger = logger;
        }

        /// <summary>
        /// 拦截方法
        /// </summary>
        /// <param name="invocation"></param>
        public virtual void Intercept(IInvocation invocation)
        {
            try
            {
                Method = invocation.MethodInvocationTarget ?? invocation.Method;
                InterceptHandle(invocation);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                throw ex;
            }         
        }

        /// <summary>
        /// 拦截处理
        /// </summary>
        /// <param name="invocation"></param>
        public abstract void InterceptHandle(IInvocation invocation);

        protected MethodInfo Method{ get; set; }

        public static bool IsAsyncMethod(MethodInfo method)
        {
            return (method.ReturnType == typeof(Task) ||
                (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
            );
        }
}

事务特性:用来判断是否需要事务管理的

    /// <summary>
    /// 事务特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)]
    public class TransactionalAttribute : Attribute
    {
        public TransactionalAttribute()
        {
            Timeout = 60;
        }

        /// <summary>
        /// 
        /// </summary>
        public int Timeout { get; set; }

        /// <summary>
        /// 事务隔离级别
        /// </summary>
        public IsolationLevel IsolationLevel { get; set; }

        /// <summary>
        /// 事务传播方式
        /// </summary>
        public Propagation Propagation { get; set; }
    }

    /// <summary>
    /// 事务传播方式
    /// </summary>
    public enum Propagation
    {
        /// <summary>
        /// 默认:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中。
        /// </summary>
        Required = 0,

        /// <summary>
        /// 使用当前事务,如果没有当前事务,就抛出异常
        /// </summary>
        Mandatory = 1,

        /// <summary>
        /// 以嵌套事务方式执行
        /// </summary>
        Nested = 2,
    }

事务拦截器:处理事务的

    /// <summary>
    /// 事务拦截器
    /// </summary>
    public class TransactionalInterceptor : BaseInterceptor<TransactionalInterceptor>
    {
        public TransactionalInterceptor(ILogger<TransactionalInterceptor> logger) : base(logger)
        {

        }

        public override void InterceptHandle(IInvocation invocation)
        {
            
            if (Method.GetCustomAttribute<TransactionalAttribute>(true) == null && Method.DeclaringType?.GetCustomAttribute<TransactionalAttribute>(true) == null)
            {
                invocation.Proceed();
            }
            else
            {
                try
                {
                    Console.WriteLine("开启事务");

                    //执行方法
                    invocation.Proceed();

                    // 异步获取异常,先执行
                    if (IsAsyncMethod(invocation.Method))
                    {
                        var result = invocation.ReturnValue;
                        if (result is Task)
                        {
                            Task.WaitAll(result as Task);
                        }
                    }

                    Console.WriteLine("提交事务");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("回滚事务");
                    _logger.LogError(ex, ex.Message);
                    throw ex;
                }
            }
        }
    }

接口上加入事务特性

    //[Transactional]
    public class UserService : IUserService
    {

        [Transactional]
        public void Test()
        {
            Console.WriteLine("Test");
        }
    }

注入IOC

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
                        .ConfigureContainer<ContainerBuilder>(autofacBuilder =>
                        {
                            autofacBuilder.RegisterType<TransactionalInterceptor>();                                      
                            autofacBuilder.RegisterType<UserService>().As<IUserService>().SingleInstance().AsImplementedInterfaces()
                            .EnableInterfaceInterceptors()
                            .InterceptedBy(typeof(TransactionalInterceptor));
                        });

测试

image

实现用户自动填充

上下户用户

    public interface IHttpContextUser
    {
         long UserId { get; }

         string UserName { get;}
    }


    public class HttpContextUser : IHttpContextUser
    {
        private readonly IHttpContextAccessor _accessor;
        public HttpContextUser(IHttpContextAccessor accessor)
        {
            _accessor = accessor;
        }
        public long UserId
        {
            get
            {
                return 1; //这里暂时是写死的
                if (int.TryParse(_accessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Sid), out var userId))
                {
                    return userId;
                }
                return default;
            }
        }

        public string UserName
        {
            get
            {
                return "admin"; //这里暂时是写死的
                return _accessor.HttpContext?.User?.FindFirstValue(ClaimTypes.Name) ?? "";
            }
        }
    }


注入IOC

           builder.Services.AddHttpContextAccessor();
           builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
                        .ConfigureContainer<ContainerBuilder>(autofacBuilder =>
                        {
                       
                            autofacBuilder.RegisterType<HttpContextUser>().As<IHttpContextUser>().SingleInstance().AsImplementedInterfaces();
                            autofacBuilder.RegisterType<UserAutoFillInterceptor>();
                       
                       
                            autofacBuilder.RegisterType<UserService>().As<IUserService>().SingleInstance().AsImplementedInterfaces()
                            .EnableInterfaceInterceptors()
                            .InterceptedBy(typeof(UserAutoFillInterceptor));
                        });

用户自动拦截器:处理用户填充的

    /// <summary>
    /// 用户自动填充拦截器
    /// </summary>
    public class UserAutoFillInterceptor : BaseInterceptor<UserAutoFillInterceptor>
    {
        private readonly IHttpContextUser _user;
        public UserAutoFillInterceptor(ILogger<UserAutoFillInterceptor> logger,IHttpContextUser user) : base(logger)
        {
            _user = user;
        }

        public override void InterceptHandle(IInvocation invocation)
        {
            //对当前方法的特性验证
            if (Method.Name?.ToLower() == "add" || Method.Name?.ToLower() == "update")
            {
                if (invocation.Arguments.Length == 1 && invocation.Arguments[0].GetType().IsClass)
                {
                    dynamic argModel = invocation.Arguments[0];
                    var getType = argModel.GetType();
                    if (Method.Name?.ToLower() == "add")
                    {
                        if (getType.GetProperty("CreateUserId") != null)
                        {
                            argModel.CreateUserId = _user.UserId;
                        }
                        if (getType.GetProperty("CreateUserName") != null)
                        {
                            argModel.CreateUserName = _user.UserName;
                        }
                        if (getType.GetProperty("CreateTime") != null)
                        {
                            argModel.CreateTime = DateTime.Now;
                        }
                     
                    }
                    if (getType.GetProperty("UpdateUserId") != null)
                    {
                        argModel.UpdateUserId = _user.UserId;
                    }
                    if (getType.GetProperty("UpdateUserName") != null)
                    {
                        argModel.UpdateUserName = _user.UserName;
                    }
                    if (getType.GetProperty("UpdateTime") != null)
                    {
                        argModel.UpdateTime = DateTime.Now;
                    }
                   
                    }
                invocation.Proceed();
            }
            else
            {
                invocation.Proceed();
            }
        }
    }

测试

image

与Asp .Net Core 系列:基于 Castle DynamicProxy + Autofac 实践 AOP 以及实现事务、用户填充功能相似的内容:

Asp .Net Core 系列:基于 Castle DynamicProxy + Autofac 实践 AOP 以及实现事务、用户填充功能

目录什么是 AOP ?.Net Core 中 有哪些 AOP 框架?基于 Castle DynamicProxy 实现 AOPIOC中使用 Castle DynamicProxy实现事务管理实现用户自动填充 什么是 AOP ? AOP(Aspect-Oriented Programming,面向切面

Asp .Net Core 系列:基于 T4 模板生成代码

目录简介组成部分分类Visual Studio 中使用T4模板创建T4模板文件2. 编写T4模板3. 转换模板中心控制Manager根据 MySQL 数据生成 实体 简介 T4模板,即Text Template Transformation Toolkit,是微软官方在Visual Studio中引

武装你的WEBAPI-OData使用Endpoint

本文属于 OData 系列文章 Introduction 更新: 由于新版的 OData 已经默认使用了 endpoint 模式(Microsoft.AspNetCore.OData 8.0.0),不再需要额外配置,本文已经过时(asp.net core 3.1)。 最近看 OData 的 devb

Asp .Net Core 系列:详解鉴权(身份验证)以及实现 Cookie、JWT、自定义三种鉴权 (含源码解析)

什么是鉴权(身份验证)? https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/?view=aspnetcore-8.0 定义 鉴权,又称身份验证,是确定用户身份的过程。它验证用户提供的凭据(如用户名和密码)是否有

Asp .Net Core 系列:集成 CAP + RabbitMQ + MySQL(含幂等性)

简介 官网:https://cap.dotnetcore.xyz/ CAP 是什么? 是一个 EventBus,同时也是一个在微服务或者 SOA 系统中解决分布式事务问题的一个框架。它有助于创建可扩展,可靠并且易于更改的微服务系统。 什么是 EventBus? 事件总线是一种机制,它允许不同的组件彼

Asp .Net Core 系列:国际化多语言配置

目录概述术语本地化器IStringLocalizer在服务类中使用本地化IStringLocalizerFactoryIHtmlLocalizerIViewLocalizer资源文件区域性回退配置 CultureProvider内置的 RequestCultureProvider实现自定义 Requ

.NET 8 Preview 5发布,了解一下Webcil 是啥

2023年6月13日 .NET 8 Preview 5,.NET 团队在官方博客发布了系列文章: Announcing .NET 8 Preview 5 ASP.NET Core updates in .NET 8 Preview 5 Announcing .NET MAUI in .NET 8 P

.NET 8 Preview 6发布,支持新的了Blazor呈现方案 和 VS Code .NET MAUI 扩展

2023年7月11日 .NET 8 Preview 6,.NET 团队在官方博客发布了系列文章:Announcing .NET 8 Preview 6[1]ASP.NET Core updates in .NET 8 Preview 6[2]Announcing .NET MAUI in .NET

Asp-Net-Core开发笔记:使用RateLimit中间件实现接口限流

前言 最近一直在忙(2月份沉迷steam,3月开始工作各种忙),好久没更新博客了,不过也积累了一些,忙里偷闲记录一下。 这个需求是这样的,我之前做了个工单系统,现在要对登录、注册、发起工单这些功能做限流,不能让用户请求太频繁。 从 .Net7 开始,已经有内置的限流功能了,但目前我们的项目还在使用

开发日志:Kylin麒麟操作系统部署ASP.NET CORE

需求场景: 我需要部署的项目是在Windows上开发的,目标框架为.net core 6.0 因此我们需要先在kylin上部署项目运行所需要的环境。 借助百度词条,先看看Kylin是什么: 服务器资源: 查看系统版本 cat /etc/kylin-release cat /proc/version