Dora.Interception, 为.NET Core度身打造的AOP框架[4]:演示几个典型应用

为了帮助大家更深刻地认识Dora.Interception,并更好地将它应用到你的项目中,我们通过如下几个简单的实例来演示几个常见的AOP应用在Dora.Interception下的实现。对于下面演示的实例,它们仅仅是具有指导性质的应用,所以我会尽可能地简化,如果大家需要将相应的应用场景移植到具体的项目开发中,需要做更多的优化。源代码从这里下载。

目录
一、对输入参数的格式化
二、对参数的自动化验证
三、对方法的返回值进行自动缓存

一、对输入参数的格式化

我们有一些方法对输入参数在格式上由一些要求,但是我们有不希望对原始传入的参数做过多的限制,那么我们可以通过AOP的方式对输入参数进行格式化。以如下这段代码为例,Demo的Invoke方法有一个字符串类型的参数input,我们希望该值总是以大写的形式存储下来,但是有希望原始的输入不区分大小写,于是我们按照如下的方式在参数上标注一个UpperCaseAttribute。这种类型的格式转换是通过我们自定义的一个名为ArgumentConversionInterceptor的Interceptor实现的,标准在方法上的ConvertArgumentsAttribute就是它对应的InterceptorAttribute。在Main方法中,我们按照DI的形式创建Demo对象(实际上是Demo代理对象),并调用其Invoke方法,那么以小写格式传入的参数将自动转换成大写形式。

  1 class Program
  2 {
  3     static void Main(string[] args)
  4     {
  5         var demo = new ServiceCollection()
  6                 .AddSingleton<Demo, Demo>()
  7                 .BuildInterceptableServiceProvider()
  8                 .GetRequiredService<Demo>();
  9         Debug.Assert(demo.Invoke("foobar") == "FOOBAR");
 10     }
 11 }
 12 public class Demo
 13 {
 14     [ConvertArguments]
 15     public virtual string Invoke([UpperCase]string input)
 16     {
 17         return input;
 18     }
 19 }

接下来我们就利用Dora.Intercecption来实现这个应用场景。对应标注在参数input上的UpperCaseAttribute用于注册一个对应的ArgumentConvertor,因为它的本质工作是进行参数的转换,抽象的ArgumentConvertor通过如下这个接口来表示。IArgumentConvertor具有一个唯一的方式Convert来完成针对参数的转化,该方法的输入是一个ArgumentConveresionContext对象,通过这个上下文对象我们可以获取代表当前参数的ParameterInfo对象和参数值。

  1 public interface IArgumentConvertor
  2 {
  3     object Convert(ArgumentConveresionContext context);
  4 }
  5
  6 public class ArgumentConveresionContext
  7 {
  8     public ParameterInfo ParameterInfo { get; }
  9     public object Value { get; }
 10
 11     public ArgumentConveresionContext(ParameterInfo parameterInfo, object valule)
 12     {
 13         this.ParameterInfo = parameterInfo;
 14         this.Value = valule;
 15     }
 16 }

就像Dora.Interception将Interceptor和Interceptor的提供刻意分开一样,我们同样将提供ArgumentConvertor的ArgumentConvertorProvider通过如下这个接口来表示。

  1 public interface IArgumentConvertorProvider
  2 {
  3     IArgumentConvertor GetArgumentConvertor();
  4 }

简单起见,我们让UpperCaseAttribute同时实现IArgumentConvertor和IArgumentConvertorProvider接口。在实现的Convert方法中,我们将输入的参数转换成大写形式,至于实现的另一个方法GetArgumentConvertor,只需要返回它自己就可以了。

  1 [AttributeUsage(AttributeTargets.Parameter)]
  2 public class UpperCaseAttribute : Attribute, IArgumentConvertor, IArgumentConvertorProvider
  3 {
  4     public object Convert(ArgumentConveresionContext context)
  5     {
  6         if (context.ParameterInfo.ParameterType == typeof(string))
  7         {
  8             return context.Value?.ToString()?.ToUpper();
  9         }
 10         return context.Value;
 11     }
 12
 13     public IArgumentConvertor GetArgumentConvertor()
 14     {
 15         return this;
 16     }
 17 }

我们最后来看看真正完成参数转换的Interceptor是如何实现的。如下面的代码所示,在ArgumentConversionInterceptor的InvokeAsync方法中,我们通过标识方法调用上下文的InvocationContext对象的TargetMethod属性得到表示目标方法的MethodInfo对象,然后解析出标准在参数上的所有ArgumentConverterProvider。然后通过InvocationContext的Arguments属性得到对应的参数值,并将参数值和对应的MethodInfo对象创建出ArgumentConveresionContext对象,后者最后传入由ArgumentConverterProvider提供的ArgumentConvertor作相应的参数。被转换后的参数被重新写入由InvocationContext的Arguments属性表示的参数列表中即可。

  1 public class ArgumentConversionInterceptor
  2 {
  3     private InterceptDelegate _next;
  4
  5     public ArgumentConversionInterceptor(InterceptDelegate next)
  6     {
  7         _next = next;
  8     }
  9
 10     public Task InvokeAsync(InvocationContext invocationContext)
 11     {
 12         var parameters = invocationContext.TargetMethod.GetParameters();
 13         for (int index = 0; index < invocationContext.Arguments.Length; index++)
 14         {
 15             var parameter = parameters[index];
 16             var converterProviders = parameter.GetCustomAttributes(false).OfType<IArgumentConvertorProvider>().ToArray();
 17             if (converterProviders.Length > 0)
 18             {
 19                 var convertors = converterProviders.Select(it => it.GetArgumentConvertor()).ToArray();
 20                 var value = invocationContext.Arguments[0];
 21                 foreach (var convertor in convertors)
 22                 {
 23                     var context = new ArgumentConveresionContext(parameter, value);
 24                     value = convertor.Convert(context);
 25                 }
 26                 invocationContext.Arguments[index] = value;
 27             }
 28         }
 29         return _next(invocationContext);
 30     }
 31 }
 32
 33 public class ConvertArgumentsAttribute : InterceptorAttribute
 34 {
 35     public override void Use(IInterceptorChainBuilder builder)
 36     {
 37         builder.Use<ArgumentConversionInterceptor>(this.Order);
 38     }
 39 }

二、对参数的自动化验证

将相应的验证规则应用到方法的参数上,进而实现自动化参数验证是AOP的一个更加常见的应用场景。一如下的代码片段为例,还是Demo的Invoke方法,我们在input参数上应用一个MaxLengthAttribute特性,这是微软自身提供的一个用于限制字符串长度的ValidationAttribute。在这个例子中,我们将字符串长度限制为5个字符以下,并提供了一个验证错误消息。针对对参数实施验证的是标准在方法上的ValidateArgumentsAttribute提供的Interceptor。在Main方法中,我们按照DI的方式得到Demo对应的代理对象,并调用其Invoke方法。由于传入的字符串(“Foobar”)的长度为6,所以验证会失败,后果就是会抛出一个ValidationException类型的异常,后者被进一步封装成AggregateException异常。

  1 class Program
  2 {
  3     static void Main(string[] args)
  4     {
  5         var demo = new ServiceCollection()
  6                 .AddSingleton<Demo, Demo>()
  7                 .BuildInterceptableServiceProvider()
  8                 .GetRequiredService<Demo>();
  9             try
 10             {
 11                 demo.Invoke("Foobar");
 12                 Debug.Fail("期望的验证异常没有抛出");
 13             }
 14             catch (AggregateException ex)
 15             {
 16                 ValidationException validationException = (ValidationException)ex.InnerException;
 17                 Debug.Assert("字符串长度不能超过5" == validationException.Message);
 18             }
 19     }
 20 }
 21 public class Demo
 22 {
 23     [ValidateArguments]
 24     public virtual string Invoke(
 25         [MaxLength(5, ErrorMessage ="字符串长度不能超过5")]
 26         string input)
 27     {
 28         return input;
 29     }
 30 }

那么我们看看ValidateArgumentsAttribute和由它提供的Interceptor具有怎样的实现。从下面给出的代码可以看出ValidationInterceptor的实现与上面这个ArgumentConversionInterceptor具有类似的实现,逻辑非常简单,我就不作解释的。在这里我顺便说说另一个问题:有一些框架会将Interceptor直接应用到参数上(比如WCF可以定义ParameterInspector来对参数进行检验),我觉得从设计上讲是不妥的,因为AOP的本质是针对方法的拦截,所以Interceptor最终都只应该与方法进行映射,针对参数验证、转化以及其他基于参数的处理都应该是具体某个Interceptor自身的行为。换句话说,应用在参数上的规则是为具体某种类型的Interceptor服务的,这些规则应该由对应的Interceptor来解析,但是Interceptor自身不应该映射到参数上。

  1 public class ValidationInterceptor
  2 {
  3     private InterceptDelegate _next;
  4
  5     public ValidationInterceptor(InterceptDelegate next)
  6     {
  7         _next = next;
  8     }
  9
 10     public Task InvokeAsync(InvocationContext invocationContext)
 11     {
 12         var parameters = invocationContext.TargetMethod.GetParameters();
 13         for (int index = 0; index < invocationContext.Arguments.Length; index++)
 14         {
 15             var parameter = parameters[index];
 16             var attributes = parameter.GetCustomAttributes(false).OfType<ValidationAttribute>();
 17             foreach (var attribute in attributes)
 18             {
 19                 var value = invocationContext.Arguments[index];
 20                 var context = new ValidationContext(value);
 21                 attribute.Validate(value, context);
 22             }
 23         }
 24         return _next(invocationContext);
 25     }
 26 }
 27
 28 public class ValidateArgumentsAttribute : InterceptorAttribute
 29 {
 30     public override void Use(IInterceptorChainBuilder builder)
 31     {
 32         builder.Use<ValidationInterceptor>(this.Order);
 33     }
 34 }

三、对方法的返回值进行自动缓存

有时候我们会定义这样一些方法:方法自身会进行一些相对耗时的操作并返回最终的处理结果,并且方法的输入决定方法的输出。对于这些方法,为了避免耗时方法的频繁执行,我们可以采用AOP的方式对方法的返回值进行自动缓存,我们照例先来看看最终的效果。如下面的代码片段所示,Demo类型具有一个GetCurrentTime返回当前时间,它具有一个参数用来指定返回时间的Kind(Local、UTC或者Unspecified)。该方法上标注了一个CaheReturnValueAttribute提供一个Interceptor来缓存方法的返回值。缓存是针对输入参数进行的,也就是说,如果输入参数一致,得到的执行结果就是相同的,Main方法的调试断言证实了这一点。

class Program
{
    static void Main(string[] args)
    {
        var demo = new ServiceCollection()
                .AddMemoryCache()
                .AddSingleton<Demo, Demo>()
                .BuildInterceptableServiceProvider()
                .GetRequiredService<Demo>();

        var time1 = demo.GetCurrentTime(DateTimeKind.Local);
        Thread.Sleep(1000);
        Debug.Assert(time1 == demo.GetCurrentTime(DateTimeKind.Local));

        var time2 = demo.GetCurrentTime(DateTimeKind.Utc);
        Debug.Assert(time1 != time2);
        Thread.Sleep(1000);
        Debug.Assert(time2 == demo.GetCurrentTime(DateTimeKind.Utc));

        var time3 = demo.GetCurrentTime(DateTimeKind.Unspecified);
        Debug.Assert(time3 != time1);
        Debug.Assert(time3 != time2);
        Thread.Sleep(1000);
        Debug.Assert(time3 == demo.GetCurrentTime(DateTimeKind.Unspecified));
        Console.Read();
    }
} 

public class Demo
{
    [CacheReturnValue]
    public virtual DateTime GetCurrentTime(DateTimeKind dateTimeKind)
    {
        switch (dateTimeKind)
        {
            case DateTimeKind.Local: return DateTime.Now.ToLocalTime();
            case DateTimeKind.Utc: return DateTime.UtcNow;
            default: return DateTime.Now;
        }
    }
}

现在我们实现缓存的CacheInterceptor是如何定义的,不过在这之前我们先来看看作为缓存的Key的定义。缓存的Key是具有如下定义的CacheKey,它由两部分组成,表示方法的MethodBase和调用方法传入的参数。

public struct Cachekey
{
public MethodBase Method { get; }
public object[] InputArguments { get; }

public Cachekey(MethodBase method, object[] arguments)
{
    this.Method = method;
    this.InputArguments = arguments;
}

public override bool Equals(object obj)
{
    if (!(obj is Cachekey))
    {
        return false;
    }
    Cachekey another = (Cachekey)obj;
    if (!this.Method.Equals(another.Method))
    {
        return false;
    }
    for (int index = 0; index < this.InputArguments.Length; index++)
    {
        var argument1 = this.InputArguments[index];
        var argument2 = another.InputArguments[index];
        if (argument1 == null && argument2 == null)
        {
            continue;
        }

        if (argument1 == null || argument2 == null)
        {
            return false;
        }

        if (!argument2.Equals(argument2))
        {
            return false;
        }
    }
    return true;
}

public override int GetHashCode()
{
    int hashCode = this.Method.GetHashCode();
    foreach (var argument in this.InputArguments)
    {
        hashCode = hashCode ^ argument.GetHashCode();
    }
    return hashCode;
}
}

如下所示的是CacheInterceptor的定义,可以看出实现的逻辑非常简单。CacheInterceptor采用以方法注入形式提供的IMemoryCache 来对方法调用的返回值进行缓存。在InvokeAsync方法中,我们根据当前执行上下文提供的代表当前方法的MethodBase和输入参数创建作为缓存Key的CacheKey对象。如果根据这个Key能够从缓存中提取相应的返回值,那么它会直接将此值保存到执行上下文中,并且终止当前方法的调用。反之,如果返回值尚未被缓存,它会继续后续的调用,并在调用结束之后将返回值存入缓存,以便后续调用时使用。

public class CacheInterceptor
{
    private readonly InterceptDelegate _next;
    public CacheInterceptor(InterceptDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(InvocationContext context, IMemoryCache cache)
    {
        var key = new Cachekey(context.Method, context.Arguments);
        if (cache.TryGetValue(key, out object value))
        {
            context.ReturnValue = value;
        }
        else
        {
            await _next(context);
            cache.Set(key, context.ReturnValue);
        }
    }
}

我们标注在GetCurrent方法上的CacheReturnValueAttribute定义如下,它只需要在重写的Use方法中按照标准的方式注册上面这个CacheInterceptor即可。顺便再说一下,将Interceptor和注册它的Attribute进行分离还具有一个好处:我可以为Attribute指定一个不同的名称,比如这个CacheReturnValueAttribute。

[AttributeUsage(AttributeTargets.Method)]
public class CacheReturnValueAttribute : InterceptorAttribute
{
    public override void Use(IInterceptorChainBuilder builder)
    {
        builder.Use<CacheInterceptor>(this.Order);
    }
}

Dora.Interception, 为.NET Core度身打造的AOP框架 [1]:全新的版本
Dora.Interception, 为.NET Core度身打造的AOP框架 [2]:不一样的Interceptor定义方式
Dora.Interception, 为.NET Core度身打造的AOP框架 [3]:Interceptor的注册
Dora.Interception, 为.NET Core度身打造的AOP框架 [4]:演示几个典型应用

原文地址:https://www.cnblogs.com/artech/p/dora2-04.html

时间: 2024-10-09 23:31:28

Dora.Interception, 为.NET Core度身打造的AOP框架[4]:演示几个典型应用的相关文章

Dora.Interception,为.NET Core度身打造的AOP框架 [2]:以约定的方式定义拦截器

上一篇<更加简练的编程体验>提供了最新版本的Dora.Interception代码的AOP编程体验,接下来我们会这AOP框架的编程模式进行详细介绍,本篇文章着重关注的是拦截器的定义.采用“基于约定”的Interceptor定义方式是Dora.Interception区别于其他AOP框架的一个显著特征,要了解拦截器的编程约定,就得先来了解一下Dora.Interception中针对方法调用的拦截是如何实现的. 一.针对实例的拦截 总地来说,Dora.Interception针对方法调用的拦截机制

Dora.Interception,为.NET Core度身打造的AOP框架 [4]:与依赖注入框架的无缝集成

Dora.Interception最初的定位就是专门针对.NET Core的AOP框架,所以在整个迭代过程中我大部分是在做减法.对于.NET Core程序开发来说,依赖注入已经成为无处不在并且“深入骨髓”的东西,不论是在进行业务应用的开发,还是进行基础组件的开发,依赖注入是实现“松耦合”最为理想的方式(没有之一).对于绝大部分AOP框架来说,它们最终都会体现为创建一个能够拦截的“代理对象”来实现对方法调用的拦截,但是.NET Core中针对服务实例的提供完全由通过IServiceProvider

Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验

很久之前开发了一个名为Dora.Interception的开源AOP框架(github地址:https://github.com/jiangjinnan/Dora,如果你觉得这个这框架还有那么一点价值,请不吝多点一颗星),最近对它作了一些改进(包括编程模式和性能,目前最新版本2.1.4).一直以来我对软件设计秉承的一个理念就是:好的设计应该是简单的设计.和其他AOP框架相比,虽然Dora.Interception提供的编程模式已经显得足够简单,但是我觉得还应该再简单点,再简单点.这个新版本对拦截

Dora.Interception,为.NET Core度身打造的AOP框架 [5]:轻松地实现与其他AOP框架的整合

这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL Emit实现方式,并且对IL指令进行了深度的优化,但是如果我们真的具有更好的选择,我们可以通过简单的扩展完成对底层拦截机制改变. 一.IInterceptingProxyFactory 对于Dora.Interception来说,方法调用之所有能够被拦截的根源在于我们改变了服务实例的提供方式,原来的对

NET Core度身定制的AOP框架

NET Core度身定制的AOP框架 多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用"注入"的方式将它们放到需要的程序中.我们知道AOP是解决这类问题最理想的方案.为此,我自己写了一个AOP框架,该框架被命名为Dora.Interception.Dora.Interception已经在GitHub上开源,如果有兴趣的朋友想下载源代码或者阅读相关文档,

AOP框架Dora.Interception 3.0 [1]: 编程体验

.NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它进行大范围的重构甚至重新设计.这次重构大部分是在做减法,其目的在于使设计和使用更加简单和灵活,接下来我们就来体验一下在一个ASP.NET Core应用程序下如何使用Dora.Interception. 源代码下载实例1(Console)实例2(ASP.NET Core MVC + 注册可拦截服务)实

AOP框架Dora.Interception 3.0 [3]: 拦截器设计

对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上.这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用.从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的模式来设计拦截器. 一.InvocationContext 我们为整个拦截器管道定义了一个统一的执行上下文,并将其命名为InvocationContext.如下面的代码片段所示,我们可以利用InvocationContext对象得到方法调用上下文的相关信息,其中包括两个方法(定义在接口和实现类型),

AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册

按照单一职责的原则,拦截器只负责需要的拦截操作的执行,至于它采用何种方式应用到目标方法上,以及它在整个拦截器管道中的位置则属于“拦截器注册”的范畴.Dora.Interception提供了几种典型的注册方法,用户也可以根据自己的需求实现自己的注册方式. 一.IInterceptorProvider 一般来说,每一个拦截器类型都对应着一个IInterceptorProvider实现类型,后者利用其Use方法负责将前者放置到拦截器管道指定的位置.如下面的代码所示,IInterceptorProvid

coolpad手机量身打造的刷机工具--下载助手_V2.2.4(Mini_AD_Coolpad)

下载助手_V2.2.4(Mini_AD_Coolpad)是下载助手_V2.2.4系列软件中针对coolpad手机量身打造的刷机工具. 下载助手_V2.2.4(DownloadAssistant_V2.2.4)是一系列高效.安全.方便.无忧的ROM版本刷机工具,由墨科通讯推出. 先上一张图: 下载助手_V2.2.4(Mini_AD_Coolpad)是该系列软件中专门用于coolpad手机刷机的工具.下载地址:http://www.morecomtech.com/community/tool/dlo