对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上。这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用。从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的模式来设计拦截器。
一、InvocationContext
我们为整个拦截器管道定义了一个统一的执行上下文,并将其命名为InvocationContext。如下面的代码片段所示,我们可以利用InvocationContext对象得到方法调用上下文的相关信息,其中包括两个方法(定义在接口和实现类型),目标对象、参数列表(含输入和输出参数)、返回值(可读写)。Properties 属性提供了一个自定义的属性容器,我们可以利用它来存放任意与当前方法调用上下文相关的信息。如果需要调用后续的拦截器或者目标方法(如果当前为最后一个拦截器),我们只需要直接调用ProceedAsync方法即可。
代码语言:javascript复制public abstract class InvocationContext
{
public abstract MethodInfo Method { get; }
public MethodInfo TargetMethod { get; }
public abstract object Target { get; }
public abstract object[] Arguments { get; }
public abstract object ReturnValue { get; set; }
public abstract IDictionary<string, object> Properties { get; }
public Task ProceedAsync();
}
二、两个委托对象
既然所有的拦截器都是在同一个InvocationContext上下文中执行的,那么我们可以将任意的拦截操作定义成一个Func<InvocationContext, Task>对象。Func<InvocationContext, Task>对象不仅可以表示某项单一的拦截操作,实际上包括目标方法调用在内的整个拦截器管道都可以表示成一个Func<InvocationContext, Task>对象。由于这个委托的重要性,我们将它定义成如下这个InterceptDelegate类型。
代码语言:javascript复制public delegate Task InterceptDelegate(InvocationContext context);
如果以ASP.NET Core框架的请求处理管道作为类比,那么InvocationContext相当于HttpContext,而InterceptDelegate自然对应的就是RequestDelegate。我们知道ASP.NET Core框架将中间件表示成Func<RequestDelegate, RequestDelegate>对象,那么拦截器自然就可以表示成一个Func<InterceptDelegate, InterceptDelegate>。如果读者朋友对此不太理解,可以参阅我的文章《200行代码,7个对象——让你了解ASP.NET Core框架的本质》。由于拦截器的重要性,我们也将它定义成如下这个单独的InterceptorDelegate类型。
代码语言:javascript复制public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);
三、基于约定的拦截器定义
Dora.Interception和ASP.NET Core采用几乎一致的设计。对于ASP.NET Core来说,虽然中间件最终是通过Func<InterceptDelegate, InterceptDelegate>表示的,但是我们可以将中间件定义成一个按照约定定义的类型。Dora.Interception同样支持基于约定的拦截器类型定义。
代码语言:javascript复制public class FoobarInterceptor
{
private readonly IFoo _foo;
private readonly IBar _bar;
private readonly string _baz;
public FoobarInterceptor(IFoo foo, IBar bar, string baz)
{
_foo = foo;
_bar = bar;
_baz = baz;
}
public async InvokeAsync(InvocationContext context)
{
await PreInvokeAsync();
await context.ProceedAsync();
await PostInvokeAsync();
}
}
如上定义的FoobarInterceptor展现了一个典型的基于约定定义的拦截器类型,它体现了如下的约定:
- 拦截器类型是一个实例类型(不能定义成静态类型);
- 必须具有一个公共构造函数,其中可以定义任意参数。
- 拦截操作定义在一个名为InvokeAsync的方法中,该方法的返回类型为Task,其中包含一个InvocationContext类型的参数。如果需要调用后续拦截器管道,需要显式调用InvocationContext上下文的ProceedAsync方法。
四、两种注入方式
由于拦截器最终是利用.NET Core的依赖注入框架提供的,所以依赖服务可以直接注入拦截器的构造函数中。但是就服务的生命周期来讲,拦截器本质上是一个Singleton服务,我们不应该将Scoped服务注入到它的构造函数中。如果具有针对Scoped服务注入的需要,我们应该将它注入到InvokeAsync方法中。
代码语言:javascript复制public class FoobarInterceptor
{
private readonly string _baz;
public FoobarInterceptor(string baz)
{
_baz = baz;
}
public async InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
{
await PreInvokeAsync();
await context.ProceedAsync();
await PostInvokeAsync();
}
}
当Dora.Interception在调用InvokeAsync方法的时候,它会利用当前Scope的IServiceProvider对象来提供其参数。对于ASP.NET Core应用来说,如果拦截器的执行在整个请求处理的调用链中,这个IServiceProvider对象就是当前HttpContext的RequestServices属性。如果当前IServiceProvider不存在,作为根的IServiceProvider对象会被使用。
AOP框架Dora.Interception 3.0 [1]: 编程体验
AOP框架Dora.Interception 3.0 [2]: 实现原理
AOP框架Dora.Interception 3.0 [3]: 拦截器设计
AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册
AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册
AOP框架Dora.Interception 3.0 [6]: 自定义拦截器注册方式