使用混合的每请求/每线程/每任务生活方式注册实例

时间:2014-10-15 14:24:48

标签: c# asp.net-mvc multithreading dependency-injection ioc-container

我们在我的应用程序中应用了依赖注入模式,并且每个请求,线程或任务都需要缓存一些组件。我们希望能够启动任务/线程,每个任务/线程都应该使用自己的DbContext。每个HTTP请求也需要自己的DbContext

我们如何配置和实现此行为?我接受任何常见IoC容器的示例。

1 个答案:

答案 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 herehere

但是,如果只有部分请求处理器在后台运行,您可以轻松地执行此操作,例如让命令实现接口并在LifetimeScopeRequestProcessorDecorator<T>上放置泛型类型约束。 Simple Injector会自动选择它。否则,如果你想根据某个属性来做,你可以使用谓词注册装饰器,如下所示:

container.RegisterDecorator(typeof(IRequestProcessor<>),
    typeof(LifetimeScopeRequestProcessorDecorator<>), 
        c => c.ImplementationType.GetCustomAttribute<AsyncAttribute>() != null);