仅针对特定范围更改依赖性解析

时间:2019-10-16 14:31:02

标签: c# dependency-injection .net-core

我注册了一个依赖项,如下所示:

interface IDependency { }

class DependencyImpl : IDependency { }

启动:

services.AddScoped<IDependency, DependencyImpl>();

这可以按预期工作,因为我确实想在我的Web API请求范围内重用同一实例。

但是,在一项后台服务中,我想告诉您它将解析为哪个实例:

class MyBackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory; // set in ctor

    public void DoStuff()
    {
        var itens = GetItens();

        var dependencyInstance = new DependencyImpl();

        Parallel.ForEach(itens, (item) =>
        {
             using(var scope = _scopeFactory.CreateScope())
             {
                 scope.SwapDependencyForThisScopeOnly<IDependency>( () => dependencyInstance ); // something like this

                 var someOtherService = scope.ServiceProvider.GetRequiredService<ItemService(); // resolve subsequent services with provided dependencyInstance
                 someOtherService.Process(item);
             }
        });
    }

}

我不能重用同一作用域,因为ItemService(和/或它的依赖项)使用了其他无法共享的作用域服务。我也不想替换整个应用程序的依赖关系解析。

可以在这里做我想做的事吗?有道理吗?

我正在使用带有默认IoC容器的dotnet core 2.2。

编辑以回复@Steven::DependencyImpl包含有关如何处理项目的配置。其中之一包括相对昂贵的查询。 DependencyImpl在图中也被多次注入。因此,当前,它仅读取一次配置,将其缓存在私有属性中,并在后续读取时使用缓存的版本。因为我知道在这里将对所有ituse重用相同的配置,所以我想避免在每次并行执行时再次读取该配置。

我的实际依赖关系与此类似:

interface IDependency 
{ 
    Task<Configuration> GetConfigurationAsync();
}

class DependencyImpl : IDependency 
{ 
    private readonly Configuration _configuration;
    private readonly DbContext _dbContext;

    ctor(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async  Task<Configuration> GetConfigurationAsync()
    {
         if(_configuration is null)
         {
              // read configurations
         }

         return _configuration;
    }
}

我知道,我的课程也不是线程安全的。我必须在开始时强制读取和/或在此处添加一些线程安全性。

此外,那些处理曾经在Web请求的生存期内发生,并且后台服务是新的东西。我宁愿更改尽可能少的现有代码,因为没有适当的测试,当然还有时间限制。

2 个答案:

答案 0 :(得分:1)

通常,在应用程序运行时更改注册对象图的结构不是一个好主意。对于大多数容器而言,这不仅难以实现,而且还容易产生难以发现的细微问题。因此,我建议对您的设计进行一些小的更改,以免您所面临的问题。

与其尝试从整体上改变依赖关系,不如使用在不同线程上加载的数据预先填充现有的依赖关系。

这可以使用以下抽象/实现对完成:

public interface IConfigurationProvider
{
    Task<Configuration> GetConfigurationAsync();
}

public sealed class DatabaseConfigurationProvider : IConfigurationProvider
{
    private readonly DbContext _dbContext;

    public DatabaseConfigurationProvider(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public Configuration Configuration { get; set; }

    public async Task<Configuration> GetConfigurationAsync()
    {
         if (Configuration is null)
         {
             await // read configurations
         }

         return Configuration;
    }
}

Configuration实现上通知公众DatabaseConfigurationProvider,在IConfigurationProvider接口上 not

这是我要介绍的解决方案的核心。允许您的Composition Root设置值,而不会污染您的应用程序抽象,因为应用程序代码不需要覆盖Configuration对象;只有“合成根”才需要。

有了这个抽象/实现对,后台服务可以看起来像这样:

class MyBackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory; // set in ctor

    public Task DoStuff()
    {
        var itens = GetItens();

        // Create a scope for the root operation.
        using (var scope = _scopeFactory.CreateScope())
        {
            // Resolve the IConfigurationProvider first to load
            // the configuration once eagerly.
            var provider = scope.ServiceProvider
                .GetRequiredService<IConfigurationProvider>();

            var configuration = await provider.GetConfigurationAsync();

            Parallel.ForEach(itens, (item) => Process(configuration, item));
        }
    }

    private void Process(Configuration configuration, Item item)
    {
        // Create a new scope per thread
        using (var scope = _scopeFactory.CreateScope())
        {
            // Request the configuration implementation that allows
            // setting the configuration.
            var provider = scope.ServiceProvider
                .GetRequiredService<DatabaseConfigurationProvider>();

            // Set the configuration object for the duration of the scope
            provider.Configuration = configuration;

            // Resolve an object graph that depends on IConfigurationProvider.
            var service = scope.ServiceProvider.GetRequiredService<ItemService>();

            service.Process(item);
        }    
    }
}

要实现此目的,您需要以下DI配置:

services.AddScoped<DatabaseConfigurationProvider>();
services.AddScoped<IConfigurationProvider>(
    p => p.GetRequiredService<DatabaseConfigurationProvider>());

此先前的配置两次注册DatabaseConfigurationProvider:一次用于其具体类型,一次用于其接口。接口注册将转发呼叫并直接解析具体类型。这是在使用MS.DI容器时必须应用的特殊“技巧”,以防止在单个作用域内获取两个单独的DatabaseConfigurationProvider实例。那将完全破坏该实现的正确性。

答案 1 :(得分:0)

创建一个扩展IDependency的接口,该接口仅适用于您需要请求的更快的实现,例如IFasterDependency。然后注册IFasterDependency。这样,您的更快的类仍然是IDependency对象,并且不会破坏太多现有代码,但是您现在可以自由地请求它。

public interface IDependency
{
    // Actual, useful interface definition
}

public interface IFasterDependency : IDependency
{
    // You don't actually have to define anything here
}

public class SlowClass : IDependency
{

}

// FasterClass is now a IDependencyObject, but has its own interface 
// so you can register it in your dependency injection
public class FasterClass : IFasterDependency
{

}