Autofac 管道
在 Autofac(从 6.0 版本开始)中,实际根据服务请求创建实例的逻辑被实现为一个 管道 ,由多个 中间件 组成。每个单独的中间件代表构建或定位实例所需过程的一部分,并将其返回给你。
对于高级自定义场景,Autofac 允许你在管道中添加自己的中间件,以拦截、短路或扩展现有的解析行为。
服务管道与注册管道
每个 服务 都有自己的服务管道,而每个 注册 也有自己的注册管道。
让我们看看典型服务的“默认”执行管道:
服务管道附着在要解决的服务上,即你用来解决问题的东西。无论实际注册提供实例的方式如何,这些对服务的所有解析都是通用的。
注册管道附着在每个单独的注册上,并适用于所有调用该注册的解析,无论使用哪个服务进行解析。
我们可以利用这种分离管道的概念,将行为附加到给定服务的所有调用(装饰器 这样做),或者附加到单个注册(例如,向管道添加 生命周期事件)。
管道阶段
当我们向管道添加中间件时,需要指定中间件应运行的 阶段。
通过指定阶段,我们可以允许在管道内部对中间件进行排序,从而不依赖于添加中间件的实际顺序。
以下是可用的管道阶段,分为服务阶段和注册阶段。
服务管道阶段 | 描述 |
---|---|
ResolveRequestStart | 解析请求的开始。在这一阶段添加的自定义中间件会在检测循环依赖之前执行。 |
ScopeSelection | 在这一阶段,选择生命周期范围。如果某些中间件需要更改范围来针对其解析,它会在这里发生(但请注意,注册的 Autofac 生命周期仍然有效)。 |
Decoration | 在这个阶段,将对实例进行装饰(在管道的输出方向)。 |
Sharing | 在这一阶段结束时,如果共享实例满足请求,管道将停止执行并退出。在此阶段添加自定义中间件以选择您自己的共享实例。 |
ServicePipelineEnd | 此阶段发生在服务管道结束(即将开始注册管道)之前。 |
注册管道阶段 | 描述 |
---|---|
RegistrationPipelineStart | 在注册管道开始时发生。 |
ParameterSelection | 在激活之前运行此阶段,是推荐的参数替换点,如果需要的话。 |
Activation | 激活阶段是管道的最后一个阶段,在此阶段创建组件的新实例。 |
注意
如果您尝试在添加注册中间件时指定服务管道阶段(反之亦然),您将收到错误。您需要根据要添加到的管道使用适当的阶段。
添加注册中间件
让我们看看如何在创建注册时向注册管道插入我们自己的中间件,使用一个简单的 “Hello World” lambda 中间件,它将在控制台打印一些信息:
var builder = new ContainerBuilder();
builder.RegisterType<MyImplementation>().As<IMyService>().ConfigurePipeline(p =>
{
// 在注册管道的开始处添加中间件。
p.Use(PipelinePhase.RegistrationPipelineStart, (context, next) =>
{
Console.WriteLine("Before Activation - 请求 {0}", context.Service);
// 调用管道中的下一个中间件。
next(context);
Console.WriteLine("After Activation - 实例化 {0}", context.Instance);
});
});
您可以看到,我们通过提供给 next
回调的方法调用管道中的下一个中间件,从而允许解析操作继续。
在 next
返回后,您可以访问创建的实例。这是因为在调用 next
时,它会调用管道中的下一个中间件,然后再次调用 next
,直到管道结束,实例被激活。
如果不调用 next
回调,管道将结束,我们将返回到调用者。
定义中间件类
除了通过 lambda 函数提供中间件外,您还可以定义自己的中间件类,并将这些类的实例添加到管道中:
class MyCustomMiddleware : IResolveMiddleware
{
public PipelinePhase Phase => PipelinePhase.RegistrationPipelineStart;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
Console.WriteLine("Before Activation - 请求 {0}", context.Service);
// 调用管道中的下一个中间件。
next(context);
Console.WriteLine("After Activation - 实例化 {0}", context.Instance);
}
}
// ...
builder.RegisterType<MyImplementation>().As<IMyService>().ConfigurePipeline(p =>
{
p.Use(new MyCustomMiddleware());
});
两种添加中间件的方式行为相同,但对于复杂的中间件,定义一个类可能会有所帮助。
将中间件添加到所有注册
如果您想为所有注册添加一段中间件,可以像添加其他共享注册行为一样使用 Registered
事件:
// 将 MyCustomMiddleware 添加到每个注册。
builder.ComponentRegistryBuilder.Registered += (sender, args) =>
{
// PipelineBuilding 事件在管道构建之前触发,可以在其中添加中间件。
args.ComponentRegistration.PipelineBuilding += (sender2, pipeline) =>
{
pipeline.Use(new MyCustomMiddleware());
};
};
解析请求上下文
传递给所有中间件的上下文对象是 ResolveRequestContext
的一个实例。这个对象存储了解析请求的初始属性,以及解析执行过程中更新的任何属性。
您可以使用此上下文:
- 检查正在解析的服务,使用
Service
属性。 - 检查提供服务的注册。
- 使用
Instance
属性获取或设置解析操作的结果。 - 使用
Parameters
属性访问请求参数,并使用ChangeParameters
方法更改参数。 - 使用任何正常的解析方法解析另一个服务。
注意
ResolveRequestContext
是一个抽象基类。如果您想为中间件编写单元测试,可以创建一个模拟对象并将模拟对象传递给中间件实现。
添加服务中间件
服务中间件附着在服务上,而不是特定的注册。因此,当我们添加服务中间件时,我们可以为服务的所有解析添加行为,而不关心提供实例的注册是什么。
直接将服务中间件添加到 ContainerBuilder
上:
var builder = new ContainerBuilder();
// 在管道的最开始运行一些中间件,早于任何核心 Autofac 行为。
builder.RegisterServiceMiddleware<IMyService>(PipelinePhase.ResolveRequestStart, (context, next) =>
{
Console.WriteLine("请求服务:{0}", context.Service);
next(context);
});
与注册中间件类似,您可以注册中间件类而不是 lambda 函数:
builder.RegisterServiceMiddleware<IMyService>(new MyServiceMiddleware());
服务中间件源
类似于 注册源 <registration-sources>
,如果您想在运行时动态地在运行时添加服务中间件,可以添加一个 服务中间件源。
这对于像开放泛型服务这样的情况特别有用,其中我们不知道实际的服务类型直到运行时。
通过实现 IServiceMiddlewareSource
并将其注册到 ContainerBuilder
来定义服务中间件源。
class MyServiceMiddlewareSource : IServiceMiddlewareSource
{
public void ProvideMiddleware(Service service, IComponentRegistryServices availableServices, IResolvePipelineBuilder pipelineBuilder)
{
// 在每个服务的 Sharing 阶段添加一些中间件。
pipelineBuilder.Use(PipelinePhase.Sharing, (context, next) =>
{
Console.WriteLine("我出现在每个服务上!");
next(context);
});
}
}
// ...
builder.RegisterServiceMiddlewareSource(new MyServiceMiddlewareSource());