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

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

一、对输入参数的格式化

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

 class Program
{
static void Main(string[] args)
{
var demo = new ServiceCollection()
.AddSingleton<Demo, Demo>()
.BuildInterceptableServiceProvider()
.GetRequiredService<Demo>();
Debug.Assert(demo.Invoke("foobar") == "FOOBAR");
}
}
public class Demo
{
[ConvertArguments]
public virtual string Invoke([UpperCase]string input)
{
return input;
}
}

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

 public interface IArgumentConvertor
{
object Convert(ArgumentConveresionContext context);
} public class ArgumentConveresionContext
{
public ParameterInfo ParameterInfo { get; }
public object Value { get; } public ArgumentConveresionContext(ParameterInfo parameterInfo, object valule)
{
this.ParameterInfo = parameterInfo;
this.Value = valule;
}
}

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

 public interface IArgumentConvertorProvider
{
IArgumentConvertor GetArgumentConvertor();
}

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

 [AttributeUsage(AttributeTargets.Parameter)]
public class UpperCaseAttribute : Attribute, IArgumentConvertor, IArgumentConvertorProvider
{
public object Convert(ArgumentConveresionContext context)
{
if (context.ParameterInfo.ParameterType == typeof(string))
{
return context.Value?.ToString()?.ToUpper();
}
return context.Value;
} public IArgumentConvertor GetArgumentConvertor()
{
return this;
}
}

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

 public class ArgumentConversionInterceptor
{
private InterceptDelegate _next; public ArgumentConversionInterceptor(InterceptDelegate next)
{
_next = next;
} public Task InvokeAsync(InvocationContext invocationContext)
{
var parameters = invocationContext.TargetMethod.GetParameters();
for (int index = 0; index < invocationContext.Arguments.Length; index++)
{
var parameter = parameters[index];
var converterProviders = parameter.GetCustomAttributes(false).OfType<IArgumentConvertorProvider>().ToArray();
if (converterProviders.Length > 0)
{
var convertors = converterProviders.Select(it => it.GetArgumentConvertor()).ToArray();
var value = invocationContext.Arguments[0];
foreach (var convertor in convertors)
{
var context = new ArgumentConveresionContext(parameter, value);
value = convertor.Convert(context);
}
invocationContext.Arguments[index] = value;
}
}
return _next(invocationContext);
}
} public class ConvertArgumentsAttribute : InterceptorAttribute
{
public override void Use(IInterceptorChainBuilder builder)
{
builder.Use<ArgumentConversionInterceptor>(this.Order);
}
}

二、对参数的自动化验证

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

 class Program
{
static void Main(string[] args)
{
var demo = new ServiceCollection()
.AddSingleton<Demo, Demo>()
.BuildInterceptableServiceProvider()
.GetRequiredService<Demo>();
try
{
demo.Invoke("Foobar");
Debug.Fail("期望的验证异常没有抛出");
}
catch (AggregateException ex)
{
ValidationException validationException = (ValidationException)ex.InnerException;
Debug.Assert("字符串长度不能超过5" == validationException.Message);
}
}
}
public class Demo
{
[ValidateArguments]
public virtual string Invoke(
[MaxLength(5, ErrorMessage ="字符串长度不能超过5")]
string input)
{
return input;
}
}

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

 public class ValidationInterceptor
{
private InterceptDelegate _next; public ValidationInterceptor(InterceptDelegate next)
{
_next = next;
} public Task InvokeAsync(InvocationContext invocationContext)
{
var parameters = invocationContext.TargetMethod.GetParameters();
for (int index = 0; index < invocationContext.Arguments.Length; index++)
{
var parameter = parameters[index];
var attributes = parameter.GetCustomAttributes(false).OfType<ValidationAttribute>();
foreach (var attribute in attributes)
{
var value = invocationContext.Arguments[index];
var context = new ValidationContext(value);
attribute.Validate(value, context);
}
}
return _next(invocationContext);
}
} public class ValidateArgumentsAttribute : InterceptorAttribute
{
public override void Use(IInterceptorChainBuilder builder)
{
builder.Use<ValidationInterceptor>(this.Order);
}
}

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

有时候我们会定义这样一些方法:方法自身会进行一些相对耗时的操作并返回最终的处理结果,并且方法的输入决定方法的输出。对于这些方法,为了避免耗时方法的频繁执行,我们可以采用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();
Debug.Assert(time1 == demo.GetCurrentTime(DateTimeKind.Local)); var time2 = demo.GetCurrentTime(DateTimeKind.Utc);
Debug.Assert(time1 != time2);
Thread.Sleep();
Debug.Assert(time2 == demo.GetCurrentTime(DateTimeKind.Utc)); var time3 = demo.GetCurrentTime(DateTimeKind.Unspecified);
Debug.Assert(time3 != time1);
Debug.Assert(time3 != time2);
Thread.Sleep();
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 = ; 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]:演示几个典型应用

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

  1. Dora.Interception,为.NET Core度身打造的AOP框架:全新的版本

    Dora.Interception 1.0(Github地址:可以访问GitHub地址:https://github.com/jiangjinnan/Dora)推出有一段时间了,最近花了点时间将它升级 ...

  2. Dora.Interception, 为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式

    相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式.我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口, ...

  3. Dora.Interception, 为.NET Core度身打造的AOP框架[3]:Interceptor的注册

    在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...

  4. Dora.Interception, 一个为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式

    相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式.我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口, ...

  5. Dora.Interception, 一个为.NET Core度身打造的AOP框架[3]:Interceptor的注册

    在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...

  6. Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式

    在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...

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

    很久之前开发了一个名为Dora.Interception的开源AOP框架(github地址:https://github.com/jiangjinnan/Dora,如果你觉得这个这框架还有那么一点价值 ...

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

    这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL E ...

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

    Dora.Interception最初的定位就是专门针对.NET Core的AOP框架,所以在整个迭代过程中我大部分是在做减法.对于.NET Core程序开发来说,依赖注入已经成为无处不在并且“深入骨 ...

随机推荐

  1. vue2.0父子组件以及非父子组件如何通信

    1.父组件传递数据给子组件 父组件数据如何传递给子组件呢?可以通过props属性来实现 父组件: <parent> <child :child-msg="msg" ...

  2. asp.net MVC里的 ModelState使用方法

    https://www.cnblogs.com/hohoa/p/5839993.html if (!ModelState.IsValid) { string error = string.Empty; ...

  3. [HNOI2008]Cards

    题目描述 小春现在很清闲,面对书桌上的N张牌,他决定给每张染色,目前小春只有3种颜色:红色,蓝色,绿色.他询问Sun有多少种染色方案,Sun很快就给出了答案. 进一步,小春要求染出Sr张红色,Sb张蓝 ...

  4. umask的作用[转]

    umask的作用 umask 命令允许你设定文件创建时的缺省模式,对应每一类用户(文件属主.同组用户.其他用户)存在一个相应的umask值中的数字.对于文件来说,这一数字的最 大值分别是6.系统不允许 ...

  5. python常用模块详解

    python常用模块详解 什么是模块? 常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀. 但其实import加载的模块分为四个通用类别: 1 使用p ...

  6. 微信小程序跳一跳辅助程序(手动版)

    最近,微信官方推出了demo小程序游戏<跳一跳>,这个游戏操作简单,容易上手,却又不容易获得高分,受到很多人的喜爱(emm...这游戏有毒).自己也尝试了玩了几次,作为一个手残+脑残的资深 ...

  7. [转]在Mac系统中安装配置Tomcat及和Eclipse 配置

    第一步:下载Tomcat 下载地址:http://tomcat.apache.org/download-70.cgi 直接下载如下选中即可: 第二步:   下载完成后 ,把解压的文件夹放到一个目录下 ...

  8. Linux 监测命令

    1.  ps  -ef -e显示所有进程:-f 显示完整格式的输出: 2.  ps  -l -l 显示一个长列表 3.  ps  -efH -H 用层级格式显示进程(树状) [ps 命令:显示某个特定 ...

  9. 如何将VS 2015中的项目上传到github

    最近开始慢慢接触github,现在希望将自己平时写的小程序,上传到github上,以便以后有个参考,在遇到同样问题的时候不至于想不起来怎么做而到处找别人的例子. VS 2015设置 首先下载跟gith ...

  10. windows 多任务与进程

    多任务,进程与线程的简单说明 多任务的本质就是并行计算,它能够利用至少2处理器相互协调,同时计算同一个任务的不同部分,从而提高求解速度,或者求解单机无法求解的大规模问题.以前的分布式计算正是利用这点, ...