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

use*_*970 0 c# asp.net-mvc multithreading dependency-injection ioc-container

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

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

Ste*_*ven 5

我认为几乎所有的DI库都会在这种情况下完成,因为几乎所有的DI库都对scoped生活方式提供了开箱即用的支持.由于您需要一些特定示例,我可以向您展示如何使用Simple Injector执行此操作.

在Simple Injector中,在Web请求的生命周期内缓存实例是使用扩展方法WebRequestLifestyle或其中一种RegisterPerWebRequest扩展方法的问题.例:

container.RegisterPerWebRequest<MyEntities>(() => new MyEntities("some conn.str"));
Run Code Online (Sandbox Code Playgroud)

这当然是一个非常基本的场景,但是你所描述的内容更有趣,因为你正在完成后台任务,每个任务都应该在自己的上下文中运行.在这种情况下,您必须明确定义解析实例的范围; 你不能隐式地这样做(就像在web请求的上下文中运行一样).这与您使用的框架无关.

使用Simple Injector,它取决于这些后台操作的运行方式.如果它们是单线程的,则可以使用LifetimeScopeLifestyle.如果该任务是异步的(使用C#的新的异步异步/等待编程模型),则可以使用ExecutionScopeLifestyle.

但我们假设操作是单线程的.正如我所说,对于每个操作,您必须显式启动一个从中解析对象图的范围.例:

using (container.BeginLifetimeScope())
{
    // Resolve within the context of the scope:
    var processor = container.GetInstance<IRequestProcessor>();

    processor.Process(request);
}
Run Code Online (Sandbox Code Playgroud)

但是,在这种情况下,由于没有Web请求,您需要以不同方式配置范围对象:

container.RegisterLifetimeScope<MyEntities>(() => new MyEntities("some conn.str"));
Run Code Online (Sandbox Code Playgroud)

但是,如果您希望在运行Web应用程序本身的同一应用程序域中运行这些操作,则需要使用混合生活方式.这是怎么做的:

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
Run Code Online (Sandbox Code Playgroud)

但最佳做法是防止应用程序依赖容器.所以你要做的最后一件事就是在整个应用程序中调用container.BeginLifetimeScope()container.GetInstance<T>传播.

相反,这个逻辑应该集中在一个叫做组合根的地方.允许您的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,这个装饰器可以包含在任何请求处理器实现中.注入Func<IRequestProcessor<TRequest>>允许解析新的请求处理器实例,同时BeginLifetimeScope再次确保在此范围的上下文中执行操作.使用Simple Injector,您可以按如下方式注册装饰器:

 // using SimpleInjector.Extensions;
container.RegisterDecorator(typeof(IRequestProcessor<>),
    typeof(LifetimeScopeRequestProcessorDecorator<>));
Run Code Online (Sandbox Code Playgroud)

现在控制器和处理器本身对此都一无所知,通过创建装饰器并注册它,您无需在整个应用程序中进行任何彻底的更改.你可以 这里这里阅读更多相关信息.

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

container.RegisterDecorator(typeof(IRequestProcessor<>),
    typeof(LifetimeScopeRequestProcessorDecorator<>), 
        c => c.ImplementationType.GetCustomAttribute<AsyncAttribute>() != null);
Run Code Online (Sandbox Code Playgroud)