如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截器。如果对那个的程序集是由第三方提供的呢?此时我们可以采用提供的第二种基于表达式的拦截器应用方式。这里的拦截器是一个调用目标类型某个方法或者提取某个属性的Lambda表达式,我们采用这种强类型的编程方式得到目标方法,并提升编程体验。(拙著《ASP.NET Core 6框架揭秘》于日前上市,加入读者群享6折优惠)

目录

一、IInterceptorRegistry

二、将拦截器应用到某个类型

三、应用到指定的方法和属性

四、指定构建拦截器的参数

五、拦截屏蔽

六、两个后备方法

一、IInterceptorRegistry

以表达式采用强类型的方式将指定类型的拦截器应用到目标方法上是借助如下这个IInterceptorRegistry接口完成的。IInterceptorRegistry接口提供了一个For<TInterceptor>方法以待注册的拦截器类型关联,参数arguments用来提供构建拦截器对象的参数。该方法会返回一个IInterceptorRegistry<TInterceptor>对象,它提供了一系列的方法帮助我们将指定的拦截器应用到指定目标类型(通过泛型参数类型TTarget表示)相应的方法上。

public interface IInterceptorRegistry
{ IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
...
} public interface IInterceptorRegistry<TInterceptor>
{
IInterceptorRegistry<TInterceptor> ToAllMethods<TTarget>(int order);
IInterceptorRegistry<TInterceptor> ToMethod<TTarget>(int order, Expression<Action<TTarget>> methodCall);
IInterceptorRegistry<TInterceptor> ToMethod(int order, Type targetType, MethodInfo method);
IInterceptorRegistry<TInterceptor> ToGetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry<TInterceptor> ToSetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry<TInterceptor> ToProperty<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
}

封装了IServiceCollection集合的InterceptionBuilder提供了一个RegisterInterceptors扩展方法,我们可以利用该方法定义的Action<IInterceptorRegistry>类型的参数来使用上述的这个IInterceptorRegistry接口。不论是IServiceCollection接口的BuildInterceptableServiceProvider扩展方法,还是IHostBuilder接口的UseInterception方法均提供了一个可选的Action<InterceptionBuilder>委托类型的参数。

public sealed class InterceptionBuilder
{
public IServiceCollection Services { get; } public InterceptionBuilder(IServiceCollection services);
} public static class Extensions
{
public static InterceptionBuilder RegisterInterceptors(this InterceptionBuilder builder, Action<IInterceptorRegistry> register); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder>? setup = null);
public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, Action<InterceptionBuilder>? setup = null);
}

二、将拦截器应用到某个类型

类似与将InterceptorAttribute标注到某个类型上,我们也可以采用这种方式将指定的拦截器应用到目标类型上,背后的含义就是应用到该类型可以被拦截的所以方法上(含属性方法)。

public class FoobarInterceptor
{
public ValueTask InvokeAsync(InvocationContext invocationContext)
{
var method = invocationContext.MethodInfo;
Console.WriteLine($"{method.DeclaringType!.Name}.{method.Name} is intercepted.");
return invocationContext.ProceedAsync();
}
} public class Foobar
{
public virtual void M() { }
public virtual object? P { get; set; }
}

我们可以采用如下的方式将调用IInterceptorRegistry<TInterceptor>的ToAllMethods<TTarget>方法将上面定义的拦截器FoobarInterceptor应用到Foobar类型的所有方法上。

var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>(); foobar.M();
foobar.P = null;
_ = foobar.P; static void RegisterInterceptors(IInterceptorRegistry registry)
{
var foobar = registry.For<FoobarInterceptor>();
foobar.ToAllMethods<Foobar>(order: 1);
}

从如下所示的执行结果可以看出,Foobar类型的M方法和P属性均被FoobarInterceptor拦截下来(源代码)。

三、应用到指定的方法和属性

我们可以通过指定调用方法或者获取属性的表达式来指定拦截器应用的目标方法。我们将目标类型Foobar定义成如下的形式,两个重载的M方法和三个属性均是可以拦截的。

public class Foobar
{
public virtual void M(int x, int y) { }
public virtual void M(double x, double y) { }
public virtual object? P1 { get; set; }
public virtual object? P2 { get; set; }
public virtual object? P3 { get; set; }
}

我们利用如下的代码将上面定义的FoobarInterceptor应用到Foobar类型相应的成员上。具体来说,我们调用ToMethod<TTarget>方法应用到两个重载的M方法,调用ToProperty<TTarget>方法应用到P1属性的Get和Set方法上,调用ToGetMethod<TTarget>和ToSetMethod<TTarget>方法应用到P2属性的Get方法和P3属性的Set方法。

var provider = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)); var foobar = provider.GetRequiredService<Foobar>(); foobar.M(1, 1);
foobar.M(3.14, 3.14);
foobar.P1 = null;
_ = foobar.P1;
foobar.P2 = null;
_ = foobar.P2;
foobar.P3 = null;
_ = foobar.P3;
Console.ReadLine(); static void RegisterInterceptors(IInterceptorRegistry registry)
{
var foobar = registry.For<FoobarInterceptor>();
foobar
.ToMethod<Foobar>(order: 1, it => it.M(default(int), default(int)))
.ToMethod<Foobar>(order: 1, it => it.M(default(double), default(double)))
.ToProperty<Foobar>(order: 1, it => it.P1)
.ToGetMethod<Foobar>(order: 1, it => it.P2)
.ToSetMethod<Foobar>(order: 1, it => it.P3)
;
}

程序运行后,针对Foobar相应成员的拦截体现在如下所示的输出结果上(源代码)。

四、指定构建拦截器的参数

如果应用的拦截器类型构造函数指定了参数,我们采用这种注册方式的时候也可以指定参数。以如下这个FoobarInterceptor为例,其构造函数中指定了两个参数,一个是代表拦截器名称的name参数,另一个是IFoobar对象。

public class FoobarInterceptor
{
public FoobarInterceptor(string name, IFoobar foobar)
{
Name = name;
Foobar = foobar;
} public string Name { get; }
public IFoobar Foobar { get; }
public ValueTask InvokeAsync(InvocationContext invocationContext)
{
Console.WriteLine($"{invocationContext.MethodInfo.Name} is intercepted by FoobarInterceptor {Name}.");
Console.WriteLine($"Foobar is '{Foobar.GetType()}'.");
return invocationContext.ProceedAsync();
}
}
public interface IFoobar { }
public class Foo : IFoobar { }
public class Bar: IFoobar { } public class Invoker
{
public virtual void M1() { }
public virtual void M2() { }
}

由于字符串参数name无法从依赖注入容器提取,所以在注册FoobarInterceptor是必须显式指定。如果容器能够提供IFoobar对象,但是希望指定一个不通过的对象,也可以在注册的时候显式指定一个IFoobar对象。我们按照如下的方式将两个不同的FoobarInterceptor对象分别应用到Invoker类型的Invoke1和Invoke2方法上,并分别将名称设置为Interceptor1和Interceptor2,第二个拦截器还指定了一个Bar对象作为参数(容器默认提供的IFoobar对象的类型为Foo)。

var invoker = new ServiceCollection()
.AddSingleton<Invoker>()
.AddSingleton<IFoobar, Foo>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Invoker>(); invoker.M1();
Console.WriteLine();
invoker.M2(); static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>("Interceptor1").ToMethod<Invoker>(order: 1, it => it.M1());
registry.For<FoobarInterceptor>("Interceptor2", new Bar()).ToMethod<Invoker>(order: 1, it => it.M2());
}

程序运行之后,两个FoobarInterceptor对象的名称和依赖的IFoobar对象的类型以如下的形式输出到控制台上(源代码)。

五、拦截屏蔽

除了用来注册指定拦截器的For<TInterceptor>方法,IInterceptorRegistry接口还定义了如下这些用来屏蔽拦截的SuppressXxx方法。

public interface IInterceptorRegistry
{
IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
IInterceptorRegistry SupressType<TTarget>();
IInterceptorRegistry SupressTypes(params Type[] types);
IInterceptorRegistry SupressMethod<TTarget>(Expression<Action<TTarget>> methodCall);
IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
IInterceptorRegistry SupressProperty<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry SupressSetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
IInterceptorRegistry SupressGetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
}

我们可以采用如下的方式会将屏蔽掉Foobar类型所有成员的拦截特性,虽然拦截器FoobarInterceptor被注册到了这个类型上(源代码)。

var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>();
... static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
registry.SupressType<Foobar>();
}

下面的程序明确屏蔽掉Foobar类型如下这些方法的拦截能力:M方法,P1属性的Get和Set方法(如果有)以及P属性的Get方法(源代码)。

var foobar = new ServiceCollection()
.AddSingleton<Foobar>()
.BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
.GetRequiredService<Foobar>(); ... static void RegisterInterceptors(IInterceptorRegistry registry)
{
registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
registry.SupressMethod<Foobar>(it=>it.M());
registry.SupressProperty<Foobar>(it => it.P1);
registry.SupressGetMethod<Foobar>(it => it.P2);
}

六、两个后备方法

通过指定调用目标方法或者提取属性的表达式来提供拦截器应用的方法和需要屏蔽的方法提供了较好的编程体验,但是能够提供这种强类型编程模式的前提是目标方法或者属性是公共成员。对于受保护(protected)的方法和属性,我们只能使用如下两个后备方法,指定代表目标方法的MethodInfo对象。

public interface IInterceptorRegistry<TInterceptor>
{
IInterceptorRegistry<TInterceptor> ToMethods<TTarget>(int order, params MethodInfo[] methods);
} public interface IInterceptorRegistry
{
IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
}

全新升级的AOP框架Dora.Interception[1]: 编程体验
全新升级的AOP框架Dora.Interception[2]: 基于约定的拦截器定义方式
全新升级的AOP框架Dora.Interception[3]: 基于“特性标注”的拦截器注册方式
全新升级的AOP框架Dora.Interception[4]: 基于“Lambda表达式”的拦截器注册方式
全新升级的AOP框架Dora.Interception[5]: 实现任意的拦截器注册方式
全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理

全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式的更多相关文章

  1. 全新升级的AOP框架Dora.Interception[3]: 基于特性标注的拦截器注册方式

    在Dora.Interception(github地址,觉得不错不妨给一颗星)中按照约定方式定义的拦截器可以采用多种方式注册到目标方法上.本篇文章介绍最常用的基于"特性标注"的拦截 ...

  2. 全新升级的AOP框架Dora.Interception[2]: 基于&ldquo;约定&rdquo;的拦截器定义方式

    Dora.Interception有别于其他AOP框架的最大的一个特点就是采用针对"约定"的拦截器定义方式.如果我们为拦截器定义了一个接口或者基类,那么拦截方法将失去任意注册依赖服 ...

  3. 全新升级的AOP框架Dora.Interception[1]: 编程体验

    多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...

  4. 全新升级的AOP框架Dora.Interception[6]: 实现任意的拦截器注册方式

    Dora.Interception提供了两种拦截器注册方式,一种是利用标注在目标类型.属性和方法上的InterceptorAttribute特性,另一种采用基于目标方法或者属性的调用表达式.通过提供的 ...

  5. 全新升级的AOP框架Dora.Interception[6]: 框架设计和实现原理

    本系列前面的五篇文章主要介绍Dora.Interception(github地址,觉得不错不妨给一颗星)的编程模式以及对它的扩展定制,现在我们来聊聊它的设计和实现原理.(拙著<ASP.NET C ...

  6. 全新升级的AOP框架Dora.Interception[汇总,共6篇]

    多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...

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

    .NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...

  8. AOP框架Dora.Interception 3.0 [2]: 实现原理

    和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...

  9. AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式

    注册拦截器旨在解决如何将拦截器应用到目标方法的问题.在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少.如果注册的方式过于模 ...

随机推荐

  1. mysql组提交

    当mysql开启binlog日志时,会存在一个内部XA的问题:事务在存储引擎层redo log的写入和binlog的写入一致性问题. mysql通过两阶段提交很好的解决了redo log和binlog ...

  2. 五分钟配置 MinGW-W64 编译工具

    编译器是一个诸如 C 语言撰写的源程序一步一步走向机器世界彼岸的桥梁. Gnu 项目的 GCC 编译器是常用的编译器之一.儿在Windows 上也有 MinGW 这样可用的套件,可以让我们使用 GCC ...

  3. 不太一样的Go Web框架—编程范式

    项目地址:https://github.com/Codexiaoyi/linweb 这是一个系列文章: 不太一样的Go Web框架-总览 不太一样的Go Web框架-编程范式 前言 上文说过,linw ...

  4. 项目开发字符串模型strstr_while

    #define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <std ...

  5. 动态规划 Dynamic Programming 学习笔记

    文章以 CC-BY-SA 方式共享,此说明高于本站内其他说明. 本文尚未完工,但内容足够丰富,故提前发布. 内容包含大量 \(\LaTeX\) 公式,渲染可能需要一些时间,请耐心等待渲染(约 5s). ...

  6. 斯坦福NLP课程 | 第2讲 - 词向量进阶

    作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...

  7. Lab1:练习四——分析bootloader加载ELF格式的OS的过程

    练习四:分析bootloader加载ELF格式的OS的过程. 1.题目要求 通过阅读bootmain.c,了解bootloader如何加载ELF文件.通过分析源代码和通过qemu来运行并调试bootl ...

  8. C++进阶-3-6-map/multimap容器

    C++进阶-3-6-map/multimap容器 1 #include<iostream> 2 #include<map> 3 using namespace std; 4 5 ...

  9. (Bezier)贝塞尔曲在路径规划的运用

    前言 之前被安排了活,一个局部区域机器运动控制的工作,大致是一个机器位于一个极限区域时候,机器要进入一个特殊的机制,使得机器可以安全的走出来.其中用到了bezier曲线进行优化路径,今天写一下,正好也 ...

  10. python 动态规划(背包问题和最长公共子串)

    背包问题 现在要往一个可以装4个单位重量的背包里怎么装价值最高:A重量1个单位,价值15:B重量3个单位,价值20:C重量4个重量,价值30 使用动态规划填充空格 class SolutionBag: ...