• (转).NET Core中实现AOP编程


               原文地址:https://www.cnblogs.com/xiandnc/p/10088159.html

    AOP全称Aspect Oriented Progarmming(面向切面编程),其实AOP对ASP.NET程序员来说一点都不神秘,你也许早就通过Filter来完成一些通用的功能,例如你使用Authorization Filter来拦截所有的用户请求,验证Http Header中是否有合法的token。或者使用Exception Filter来处理某种特定的异常。
    你之所以可以拦截所有的用户请求,能够在期望的时机来执行某些通用的行为,是因为ASP.NET Core在框架级别预留了一些钩子,他允许你在特定的时机注入一些行为。对ASP.NET Core应用程序来说,这个时机就是HTTP请求在执行MVC Action的中间件时。

    显然这个时机并不能满足你的所有求,比如你在Repository层有一个读取数据库的方法:

    1
    2
    3
    4
    public void GetUser()
    {
        //Get user from db
    }

    你试图得到该方法执行的时间,首先想到的方式就是在整个方法外面包一层用来计算时间的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void GetUserWithTime()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            //Get user from db
        }
        finally
        {
            stopwatch.Stop();
            Trace.WriteLine("Total" + stopwatch.ElapsedMilliseconds + "ms");
        }
    }

    如果仅仅是为了得到这一个方法的执行时间,这种方式可以满足你的需求。问题在于你有可能还想得到DeleteUser或者UpdateUser等方法的执行时间。修改每一个方法并添加计算时间的代码存在着明显的code smell。
    一个比较优雅的做法是给需要计算时间的方法标记一个Attribute:

    1
    2
    3
    4
    5
    [Time]
    public void GetUser()
    {
        //Get user from db
    }

    你把计算时间这个功能当做一个切面(Aspect)注入到了现有的逻辑中,这是一个AOP的典型应用。

    在C#中使用AOP

    C#中可以用来做AOP的开源类库有若干个,比较流行的:

    这些类库之所以能够实现AOP是因为他们有动态修改IL代码的能力,这种能力又被称为IL weaving。
    还有的类库把AOP和Dependency Injection结合在了一起,通过服务上注册一个拦截器(Interceptor)的方式做达到AOP的目的,例如:

    本文将使用一个C#开源项目aspect-injector来描述AOP的几种常见的场景。
    aspect-injector是一个非常轻量级的AOP类库,麻雀虽小,但是已经能够应对大部分AOP的应用场景:

    • 支持.NET Core
    • 支持对异步方法注入切面
    • 能够把切面注入到方法、属性和事件上
    • 支持Attribute的方式注入切面

    注入计算执行时间的逻辑

    在已有的方法上注入一段逻辑可以分为三种情况:

    1. 在方法执行前注入一段逻辑,例如注入统一的认证逻辑
    2. 在方法执行后注入一段逻辑,例如将结果写入日志
    3. 方法前后同时注入逻辑,例如计算时间,又或者给整个方法内容包裹一个事务
      已知一个计算个数的方法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    public class SampleService
    {
        public int GetCount()
        {
            Thread.Sleep(3000);
            return 10;
        }
    }

    为了将计算时间的逻辑包裹在现有的方法上,我们需要在被注入逻辑的方法上标记InjectAttribute

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class SampleService
    {
        [Inject(typeof(TimeAspect))]
        public int GetCount()
        {
            Thread.Sleep(3000);
            return 10;
        }
    }

    TimeAspect就是我们将要注入的一个切面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [Aspect(Aspect.Scope.Global)]
    public class TimeAspect
      {
          [Advice(Advice.Type.Around, Advice.Target.Method)]
          public object HandleMethod(
          [Advice.Argument(Advice.Argument.Source.Name)] string name,
          [Advice.Argument(Advice.Argument.Source.Arguments)] object[] arguments,
          [Advice.Argument(Advice.Argument.Source.Target)]
          Func<object[], object> method)
          {
              Console.WriteLine($"Executing method {name}");
              var sw = Stopwatch.StartNew();
              var result = method(arguments); //调用被注入切面的方法
              sw.Stop();
              Console.WriteLine($"method {name} in {sw.ElapsedMilliseconds} ms");
              return result;
          }
      }

    大部分代码是非常清晰的,我们只描述几个重要的概念:
    标记了AdviceAttribute的方法就是即将要注入到目标方法的切面逻辑,也就是说HandleMethod描述了如何计算时间。
    Advice.Type.Around描述了同时在目标方法的前后都注入逻辑
    方法参数Func<object[], object> method其实就代表目标方法

    注入认证逻辑

    试想你有如果干个服务,每个服务在执行前都要做安全认证,显然安全认证的逻辑是可重用的,那我们就可以把认证的逻辑提取成一个切面(Aspect)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Inject(typeof(AuthorizationAspect))]
    public class SampleService
    {
        public void MethodA(Guid userId)
        {
            // Do something
        }
     
        public void MethodB(Guid userId)
        {
            // Do something
        }
    }

    AuthorizationAspect就是安全认证的逻辑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [Aspect(Aspect.Scope.Global)]
    public class AuthorizationAspect
    {
        [Advice(Advice.Type.Before, Advice.Target.Method)]
        public void CheckAccess(
        [Advice.Argument(Advice.Argument.Source.Method)] MethodInfo method,
        [Advice.Argument(Advice.Argument.Source.Arguments)] object[] arguments)
        {
            if (arguments.Length == 0 || !(arguments[0] is Guid))
            {
                throw new ArgumentException($"{nameof(AuthorizationAspect)} expects
                every target method to have Guid as the first parameter");
            }
     
            var userId = (Guid)arguments[0];
            if (!_securityService.HasPermission(userId, authorizationAttr.Permission))
            {
                throw new Exception($"User {userId} doesn't have
                permission to execute method {method.Name}");
            }
        }
    }

    Advice.Type.Before描述了该逻辑会在被修改的方法前执行
    通过object[] arguments得到了被修改方法的所有参数

    AOP是面向对象编程中一种用来抽取公用逻辑,简化业务代码的方式,灵活使用AOP可以让你的业务逻辑代码不会过度臃肿,也是除了继承之外另一种可复用代码的方式。

    aspect-injector地址:https://github.com/pamidur/aspect-injector/tree/master/docs#this

  • 相关阅读:
    JNDI 是什么
    RuntimeException和非RuntimeException的区别
    dynamicinsert,dynamicupdate能够性能上的少许提升
    Session,有没有必要使用它?[转]
    c# textbox中光标所在行命令及选中命令移动到最后一行且光标提前[转]
    C#分布式事务(TransactionScope )
    .net中的分布式事务
    大道至简,职场上做人做事做管理[转]
    C#中TreeView的CheckBox的两种级联选择
    C# winform TreeView中关于checkbox选择的完美类[转]
  • 原文地址:https://www.cnblogs.com/hhhh2010/p/11156873.html
Copyright © 2020-2023  润新知