我们在我的应用程序中应用了依赖注入模式,并且每个请求,线程或任务都需要缓存一些组件。我们希望能够启动任务/线程,每个任务/线程都应该使用自己的DbContext
。每个HTTP请求也需要自己的DbContext
。
我们如何配置和实现此行为?我接受任何常见IoC容器的示例。
答案 0 :(得分:5)
我认为几乎所有的DI库都会在这种情况下发生,因为几乎所有的DI库都对scoped生活方式提供了开箱即用的支持。由于您要求提供一些具体示例,我可以通过Simple Injector向您展示如何执行此操作。
在Simple Injector中,在Web请求的生命周期内缓存实例是使用WebRequestLifestyle
或其中一个RegisterPerWebRequest
扩展方法的问题。例如:
container.RegisterPerWebRequest<MyEntities>(() => new MyEntities("some conn.str"));
这当然是一个非常基本的场景,但是你所描述的内容更有趣,因为你正在完成后台任务,每个任务都应该在自己的上下文中运行。在这种情况下,您必须明确定义解析实例的范围;你不能隐式地这样做(就像在web请求的上下文中运行一样)。这与您使用的框架无关。
使用Simple Injector,它取决于这些后台操作的运行方式。如果它们是单线程的,您可以使用LifetimeScopeLifestyle。如果该任务是异步的(使用C#的新的异步异步/等待编程模型),则可以使用ExecutionScopeLifestyle。
但我们假设操作是单线程的。正如我所说,对于每个操作,您必须显式启动一个从中解析对象图的范围。例如:
using (container.BeginLifetimeScope())
{
// Resolve within the context of the scope:
var processor = container.GetInstance<IRequestProcessor>();
processor.Process(request);
}
但是,在这种情况下,由于没有Web请求,您需要以不同方式配置范围对象:
container.RegisterLifetimeScope<MyEntities>(() => new MyEntities("some conn.str"));
如果您希望在运行Web应用程序本身的同一应用程序域中运行这些操作,则需要使用hybrid lifestyle。这是如何做到的:
var scopedLifestyle = Lifestyle.CreateHybrid(
lifestyleSelector: () => HttpContext.Current != null,
trueLifestyle: new WebRequestLifestyle(),
falseLifestyle: new LifetimeScopeLifestyle());
container.Register<MyEntities>(() => new MyEntities("some conn.str"), scopedLifestyle);
container.Register<IRepository<User>, UserRepository>(scopedLifestyle);
// etc
但最佳做法是阻止应用程序依赖容器。因此,您要做的最后一件事就是在整个应用程序中调用container.BeginLifetimeScope()
和container.GetInstance<T>
。
相反,这个逻辑应集中在一个名为Composition Root的地方。允许您的Web请求代码启动后台操作的好方法,而双方都不知道这个事实是使用装饰器。比如说你的MVC控制器执行业务操作,其中一些必须异步执行。
比如说你有一些应该执行这些请求的IRequestProcessor<TRequest>
抽象。您可以为此创建以下装饰器并将其放置在合成根目录中:
public class LifetimeScopeRequestProcessorDecorator<TRequest>
: IRequestProcessor<TRequest>
{
private readonly Container container;
private readonly Func<IRequestProcessor<TRequest>> decorateeFactory;
public LifetimeScopeRequestProcessorDecorator(Container container,
Func<IRequestProcessor<TRequest>> decorateeFactory)
{
this.container = container;
this.decorateeFactory = decorateeFactory;
}
public void Handle(TRequest request)
{
using (this.container.BeginLifetimeScope())
{
IRequestProcessor<TRequest> processor = this.decoratorFactory.Invoke();
processor.Handle(request);
}
}
}
现在这个装饰器可以包含在任何请求处理器实现中。注入的Func<IRequestProcessor<TRequest>>
允许解析新的请求处理器实例,而BeginLifetimeScope
再次确保在此范围的上下文中执行操作。使用Simple Injector,您可以按如下方式注册装饰器:
// using SimpleInjector.Extensions;
container.RegisterDecorator(typeof(IRequestProcessor<>),
typeof(LifetimeScopeRequestProcessorDecorator<>));
现在控制器和处理器本身对此一无所知,通过创建装饰器并注册它,您无需在整个应用程序中进行任何彻底的更改。您可以详细了解here here和here。
但是,如果只有部分请求处理器在后台运行,您可以轻松地执行此操作,例如让命令实现接口并在LifetimeScopeRequestProcessorDecorator<T>
上放置泛型类型约束。 Simple Injector会自动选择它。否则,如果你想根据某个属性来做,你可以使用谓词注册装饰器,如下所示:
container.RegisterDecorator(typeof(IRequestProcessor<>),
typeof(LifetimeScopeRequestProcessorDecorator<>),
c => c.ImplementationType.GetCustomAttribute<AsyncAttribute>() != null);