如何使用.NET Core依赖项注入在运行时解析服务并注入其他构造函数参数?

mad*_*dd0 5 c# dependency-injection .net-core asp.net-core

我有一个用例,其中我想使用.NET Core依赖项注入创建存储库实例,但需要在运行时更改构造函数参数之一。确切地说,应在运行时确定的参数是“数据库连接”,它将指向由调用者确定的一个或另一个数据库。顺便说一下,该类型在DI容器中注册,但其他所有类型都已注册。

调用者将使用存储库工厂类型来创建具有所需连接的存储库。

看起来像这样:

class ARepository : IARepository
{
    public ARepository(IService1 svc1, IService2 svc2, IConnection connection) { }

    public IEnumerable<Data> GetData() { }
}

class RepositoryFactory : IRepositoryFactory
{
    public RepositoryFactory(IServiceProvider serviceProvider) =>
        _serviceProvider = serviceProvider;

    public IConnection CreateAlwaysFresh<TRepository>() =>
        this.Create<TRepository>(new FreshButExpensiveConnection());

    public IConnection CreatePossiblyStale<TRepository>() =>
        return this.Create<TRepository>(new PossiblyStaleButCheapConnection());

    private IConnection Create<TRepository>(IConnection conn)
    {
        // Fails because TRepository will be an interface, not the actual type
        // that I want to create (see code of AService below)
        return ActivatorUtilities.CreateInstance<TRepository>(_serviceProvider,conn);

        // Fails because IConnection is not registered, which is normal
        // because I want to use the instance held in parameter conn
        return _serviceProvider.GetService<TRepository>();
    }
}
Run Code Online (Sandbox Code Playgroud)

登记了以下类型:

services.AddTransient<IARepository, ARepository>();  // Probably not needed
services.AddTransient<IService1, Service1>();
services.AddTransient<IService2, Service2>();
services.AddTransient<IRepositoryFactory, RepositoryFactory>();
Run Code Online (Sandbox Code Playgroud)

工厂将这样使用:

services.AddTransient<IARepository, ARepository>();  // Probably not needed
services.AddTransient<IService1, Service1>();
services.AddTransient<IService2, Service2>();
services.AddTransient<IRepositoryFactory, RepositoryFactory>();
Run Code Online (Sandbox Code Playgroud)

我之所以保留所有基于接口的代码的原因之一,当然是用于单元测试。但是,正如您从的伪实现中可以看到的那样RepositoryFactory.Create<TRepository>,这也是一个问题,因为我达到了需要这样做的地步:

  • 确定IARepository要传递给DI容器中与之关联的concret类型ActivatorUtilities,以便在使用或IConnection解析其他构造函数参数时使用期望的值创建它的实例。IServiceProvider

  • 以某种方式告诉获得特定服务时IServiceProvider使用特定实例IConnection

使用.NET Core DI完全可以吗?

(奖金问题:我应该使用其他更简单的方法吗?)

更新:我对示例代码进行了一些编辑,以期使我的意图更加清楚。这个想法是允许相同的存储库,完全相同的代码根据调用者的特定需求使用不同的连接(在应用程序启动期间配置)。总结一下:

  • 存储库的责任是在请求操作时在Connection上执行正确的查询。
  • 调用方将对存储库返回的数据进行操作
  • 但是,调用方可能需要存储库在特定的Connection上执行其查询(在此示例中,此连接控制数据的新鲜度)

在工厂中注入正确连接的问题有几种解决方法:

  • 在存储库中添加一个可变的Connection属性,并在创建后立即设置它=>此解决方案让我最困扰的是,它很容易忘记设置连接,例如在测试代码中。这也为改变存储库的属性打开了大门,该属性应该是不变的。
  • 不要将Connection注入到类中,而是将其作为方法参数传递给=>,这使API显得不太优雅,因为每个方法现在都有一个“ extra”参数,可以将其简单地提供给类以开头,额外的参数只是一个“实现细节”

the*_*000 1

由于IConnectionDI 不会创建,您可以从存储库构造函数中将其删除并将其作为属性,然后在工厂上您可以在创建后分配其值:

interface IARepository 
{ 
    IConnection Connection { set; }
}

class ARepository : IARepository
{
    public IConnection Connection { private get; set; }

    public ARepository(IService1 svc1, IService2 svc2)
    { /* ... */ }
}


class RepositoryFactory : IRepositoryFactory
{
    /* ... */
    private IConnection Create<TRepository>(IConnection conn) 
        where TRepository : IARepository
    {
        var svc = _serviceProvider.GetService<TRepository>();
        svc.Connection = conn;
        return svc;
    }
}
Run Code Online (Sandbox Code Playgroud)