正确的方法来覆盖范围内的依赖关系

时间:2017-05-17 04:47:27

标签: c# dependency-injection simple-injector

我正在使用Simple Injector。我有一个后台处理器,从一开始就使用DI。它将提取作业以运行并运行它们。但是,每个作业都需要在自己的作用域内运行,以便我可以覆盖一些上下文依赖项。例如,作业需要在特定的安全上下文(创建它的那个)中运行,因此我需要启动一个新的作用域并覆盖ISecurityContext注入,以便正确保护作业。

为了做到这一点,我正在创建一个新的容器(使用正确的ISecurityContext)并启动一个作用域,然后运行该作业,但我不确定这是否适合做。

RunJob

private readonly Func<ISecurityContext, Container> _containerFactory;

internal async Task RunJob(BackgroundJob job) {
    var parameters = job.GetParameters();
    var securityContext = parameters.SecurityContext;

    using (var container = _containerFactory(securityContext))
    using (AsyncScopedLifestyle.BeginScope(container)) {
        // Run the job within this scope.
    }
}

DI Bits

container.RegisterSingleton<Func<ISecurityContext, Container>>(() => securityContext => {
    var c = new Container();

    RegisterDependencies(c);

    c.Options.AllowOverridingRegistrations = true;
    c.Register<ISecurityContext>(() => securityContext, Lifestyle.Scoped);

    return c;
});

对我来说感觉不对,但我不确定正确的解决方案是什么。

1 个答案:

答案 0 :(得分:3)

Simple Injector文档warns,说明您正在做的事情:

  

警告:不要创建无限数量的Container实例(例如每个请求一个实例)。这样做会降低应用程序的性能。该库已针对使用非常有限数量的Container实例进行了优化。创建和初始化Container实例的开销很大,但初始化后,从Container解析的速度非常快。

通常,您应该为每个应用程序仅创建一个 Container实例。这不仅仅是从性能的角度来看,而且这种“儿童容器”的创造通常充满了怪癖和缺陷。例如,如何确保注册是整个应用程序的单例?

因此,不要将容器滥用于运行时状态,而是将其存储在其他位置。您可以使用Scope实例作为作用域状态的字典,但是为注册为ISecurityContext实例的Scoped创建一个简单的包装并在创建范围后直接初始化它也很容易如下例所示。

// Can be part of your Composition Root
internal sealed class SecurityContextWrapper : ISecurityContext
{
    // One of the rare cases that Property Injection makes sense.
    public ISecurityContext WrappedSecurityContext { get; set; }

    // Implement ISecurityContext methods here that delegate to WrappedSecurityContext.
}


// Composition Root. Only have 1 container for the complete application
c = new Container();

RegisterDependencies(c);

c.Register<SecurityContextWrapper>(Lifestyle.Scoped);
c.Register<ISecurityContext, SecurityContextWrapper>(Lifestyle.Scoped);


// Job logic
private readonly Container _container;

internal async Task RunJob(BackgroundJob job) {
    var parameters = job.GetParameters();
    var securityContext = parameters.SecurityContext;

    using (AsyncScopedLifestyle.BeginScope(_container)) {
        // Resolve the wapper inside the scope
        var wrapper = _container.GetInstance<SecurityContextWrapper>();
        // Set it's wrapped value.
        wrapper.WrappedSecurityContext = securityContext;

        // Run the job within this scope.
    }
}

或者,如果使用Scope作为状态,则可以注入Scope实例作为SecurityContextWrapper的构造函数参数。这消除了使用Property Injection的需要,但确实使SecurityContextWrapper依赖于Simple Injector:

// Can be part of your Composition Root
internal sealed class SecurityContextWrapper : ISecurityContext
{
    ISecurityContext _wrappedSecurityContext;

    public SecurityContextWrapper(Scope scope)
    {
        _wrappedSecurityContext= (ISecurityContext)scope.GetItem(typeof(ISecurityContext));
    }

    // Implement ISecurityContext methods here that delegate to WrappedSecurityContext.
}

// Composition Root. Only have 1 container for the complete application
c = new Container();

RegisterDependencies(c);

c.Register<ISecurityContext, SecurityContextWrapper>(Lifestyle.Scoped);


// Job logic
private readonly Container _container;

internal async Task RunJob(BackgroundJob job) {
    var parameters = job.GetParameters();
    var securityContext = parameters.SecurityContext;

    using (var scope = AsyncScopedLifestyle.BeginScope(_container)) {
        // Set it's wrapped value.
        scope.SetItem(typeof(ISecurityContext), securityContext);

        // Run the job within this scope.
    }
}