如何使用AOP改进.NET应用程序

目录

什么是AOP

示例1:Automatic 控制器记录

拦截器

示例2:低代码查询实现

步骤1:我们想要的

步骤2:代理

步骤3:注释(Annotation)

步骤4:实际操作

带什么回家

参考文献


其原理是AOP(面向切面的编程)。该技术在Java中得到了广泛使用,并有助于轻松地保持高质量的标准。今天,我们将学习如何在.NET Core项目中轻松使用它。在简要的理论解释之后,您将找到两个示例(所有代码都在我的GitHub profile中)。

什么是AOP

让我们从Wikipedia定义开始:

引用:

在计算中,面向切面的编程(AOP)是一种编程范例,旨在通过允许跨领域关注点的分离来提高模块化。通过在不修改代码本身的情况下向现有代码添加其他行为(建议)来做到这一点[…]这允许将对业务逻辑不重要的行为(例如日志记录)添加到程序中,而这是功能的核心。 https://en.wikipedia.org/wiki/Aspect-oriented_programming

这个概念很简单,可以用一句话来概括。

                                                                           使事情发生而无需编写代码。

这适用于所需的所有代码,但不引入任何业务逻辑。

下面是一些有关AOP如何更改我们的代码的示例。第一个是关于日志记录。

public void SaveData(InputClass input)
{
  Stopwatch timer= new Stopwatch();
  timer.Start();
  logger.Info("enterd SaveData method");

  if(logger.LogLevel==LoggingLeve.Debug)
  {
      logger.Debug(MyJsonTool.ConvertToJson(input);
  }

  dataRepositoryInstance.Save(input);
  timer.End();
  logger.Info($"enterd SaveData method in {timer.ElapsedMilliseconds}ms");
}

如果我告诉你所有这些代码仅通过编写就可以产生相同的输出怎么办?

public virtual void SaveData(InputClass input)
{
  dataRepositoryInstance.Save(input);
}

因此,所有工作都只需在方法中添加一个virtual关键字,那太好了!稍后我们将返回virtual关键字,以了解它与AOP的关系。

如果您不相信AOP的强大功能,请看看如何简化数据获取代码,如下所示:

[Fetch("SELECT * FROM customers WHERE name=?")]
public List<MyDTO> GetByName(string name)
{
   return new List<MyDTO>();
}

我希望您现在已经确信AOP可以在许多情况下提供帮助并且可以成为强大的盟友。让我们看看它是如何工作的以及如何将其集成到.NET Core应用程序中。

本文包含两个示例:

  1. 使用DinamyProxyAutofact拦截日志的简单案例
  2. 一个非常好的AOP技术的深入介绍,展示了如何实现AOP引擎

示例1Automatic 控制器记录

在此示例中,我们将配置一个拦截器来记录所有传入的请求。可以将其扩展到我们应用程序的所有其他层,仅作为概念证明。

拦截器

拦截器的解剖非常简单。在方法执行之前和之后记录它。在此示例中,我使用GUID一起引用事件日志,但是可以进行许多改进。

public class AutofacModule : Module
  {
      protected override void Load(ContainerBuilder builder)
      {
          // Register the interceptor
          builder.Register(c => new CallLogger())
         .Named<IInterceptor>("log-calls");
     
          builder.Register(c => new ValuesService(c.Resolve<ILogger<ValuesService>>()))
              .As<IValuesService>()
              .InstancePerLifetimeScope()                
              .EnableInterfaceInterceptors();
      }
  }  
  
  public class CallLogger : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var executionId = Guid.NewGuid().ToString();

            Debug.WriteLine("{0} - Calling method {1} with parameters {2}... ", 
                executionId,
               invocation.Method.Name,
               JsonConvert.SerializeObject(invocation.Arguments));

            invocation.Proceed();

            Debug.WriteLine("{0} - Done: result was {1}.", 
                executionId, 
               JsonConvert.SerializeObject( invocation.ReturnValue));
        }
    }

我们可以讨论直到明天为止的愚蠢行为,例如如何将数据愚蠢地转储到日志中,或者我们可以改进该系统以使用更好的日志记录系统,以及一种巧妙的方式来跟踪输入,时序和输出。

如您所见,方法的执行过程带有时序。开箱即用地在ASP.NET Web API应用程序的所有控制器上或业务逻辑上的每个服务方法中对其进行镜像。好吗?它节省了大量的代码行。

示例2:低代码查询实现

本示例说明如何通过添加一些注释将默认行为添加到方法。该示例从头开始实现,没有使用任何库,以突出显示它在后台的工作方式。

要创建的基类是DispatcherProxy。此类为通用类型实现代理,该通用类型拦截方法调用并返回自定义对象。这就是我们需要用有效的方法替换空方法的方法。

无论如何,要实现通用引擎,我们还需要更多。我创建了一个名为AOPAttribute通用属性,带有很多幻想。继承此注释的每个注释都需要实现执行。使用此模式,所有实现都委托给该属性,并且我们的代理引擎与许多实现完全脱钩。

您可以在下面的代码段中检查代码的相关部分。仅使用几行代码,我们就能实现一个非常强大的引擎,但这只是一个例子。您可以想象可以为您解决多少个用例。

我告诉你太快了吗?只需逐步开始。

步骤1:我们想要的

首先,我们要实现一种机制,该机制允许自动实现方法。在C#中,我们不能在DispatcherProxy类上使用,而只能在接口上使用,因此我们将需要始终从具有所有方法声明的接口开始。无论如何,我们也想手动实现一些方法,因此我们还需要一个具体的类。现在有一个棘手的问题。如果我们让类从逻辑上的接口继承,我们将被迫实现所有方法,因为这是编译器所需要的。我采用的技巧是简单地忘记继承。类和接口之间的关系将在以后的DI中定义。

这是FruitRepository的片段。该接口包含将自动实现的方法和手动实现的InitDB方法。

public interface IFruitRepository
{
    [Query("SELECT * FROM fruits where family={0}")]
    List<Fruit> GetFruits(string tree);

    [Query("SELECT * FROM fruits where family='Citrus'")]
    List<Fruit> GetCitrus(string tree);

    public void InitDB();
}

  public  class FruitRepositoryImpl
  {
      public void InitDB()
      {
          using (var db = new FruitDB())
          {
              db.Database.EnsureCreated();

              var count = db.Fruits.Count();
              if (count == 0)
              {
                  db.Fruits.Add(new Fruit()
                  {
                      Name = "Lemon",
                      Family = "Citrus"
                  });

                 //... all the fruit of the world here

                  db.SaveChanges();
              }
          }
      }
  }

步骤2:代理

现在,我们需要创建一个代理,该代理将维护接口和已实现的类之间的关系,并基于注释为方法提供服务。此代码非常简单,请参见下面的代码段:

public class ProxyFacotory<T> : DispatchProxy
{
    private T _decorated;

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {        
        //Find an annotation on the interface
        var annotations = targetMethod.GetCustomAttributes(true);
        var aopAttr = annotations.FirstOrDefault
        (x => typeof(AOPAttribute).IsAssignableFrom(x.GetType())) as AOPAttribute;
        
        //in case the method has an AOP implementation, this is executed
        if (aopAttr != null)
        {
            return aopAttr.Execute(targetMethod, args, annotations);
        }

        //otherwise, the manual implementation on class is triggered
        var inherithedMethod=interfaceMethods.FirstOrDefault
                             (x => x.Name == targetMethod.Name);
        var result = inherithedMethod.Invoke(_decorated, args);
        return result;
    }

    public static T Create<T,TProxy>(TProxy instance) where T : class where TProxy:class
    {
        object proxy = Create<T, ProxyFacotory<TProxy>>();
        ((ProxyFacotory<TProxy>)proxy).SetParameters(instance);
        return (T)proxy;
    }

    private void SetParameters(T decorated)
    {
        _decorated = decorated;
    }
}

用法非常简单,并使用常规的.NET Core依赖项注入。

//the proxy instance return an instance based on the concrete implementation
var instance=ProxyFacotory<IFruitRepository>.Create<IFruitRepository,FruitRepositoryImpl>
                                             (new FruitRepositoryImpl());
var serviceProvider = new ServiceCollection()
                .AddSingleton<IFruitRepository>(instance)
                .BuildServiceProvider();

步骤3:注释(Annotation)

所有的基本注释是AOPAnnotation。这是一个包含替换了通常方法主体的Execute方法的abstract类。然后,我们获得了Query注释(annotation),在本例中,该批注使用了从开发人员传递来的查询模板来获取数据。

public abstract class AOPAttribute: Attribute
  {
      public abstract object Execute
             (MethodInfo targetMethod, object[] args, object[] annotations);
  }
    
 public class QueryAttribute : AOPAttribute
  {
      public string Template { get; set; }
      public   QueryAttribute(string template)
      {
          this.Template = template;
      }

      public override object Execute
             (MethodInfo targetMethod, object[] args, object[] annotations)
      {
          using (var data = new FruitDB())
          {
              return data.Fruits
              .FromSqlRaw<Fruit>(this.Template,args ).ToList(); //Dataset can be 
                                                                //taken by target field
          }
      }
  }

步骤4:实际操作

放在一起很简单。只需使用我们手动编写的存储库即可。

var fruitRepository = serviceProvider.GetService<IFruitRepository>();
//This calls the manual method and fill the database
fruitRepository.InitDB();
//This uses a dynamic definition
var fruits=fruitRepository.GetFruits("Citrus");

带什么回家

由于自动化的代码编写,AOP是一种非常有趣的模式。它非常强大,但是有两个缺点:

  1. 性能:深入使用反射和精心制作的其他步骤,可能会增加计算时间
  2. 失去控制:更多的系统为您服务,更多的您不知道如何解决

现代工具和框架无需使用代码即可减少代码,因此并非总是必要的。无论如何,在设计框架或大型基础架构时了解这一点非常重要,因为这可能是赢得战争的正确武器。例如,当我设计RawCMS开源的无头CMS的体系结构,它是一个很好的联盟。

关于性能或稳定性,只需记住Java Spring Framework。它使用它作为一切的基础,如今已成为企业应用程序的最佳选择之一。

所有源代码都在我的GitHub profile

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值