C# 如何将运行时参数作为依赖项解析的一部分传递?

C# 如何将运行时参数作为依赖项解析的一部分传递?,c#,dependency-injection,asp.net-core,.net-core,C#,Dependency Injection,Asp.net Core,.net Core,我需要能够将连接字符串传递到我的一些服务实现中。我在构造函数中执行此操作。连接字符串可由用户配置,将作为声明添加到ClaimsPrincipal中 到目前为止一切都很好 不幸的是,我还希望能够充分利用ASP.NET核心中的依赖项注入功能,并通过DI解决服务实现问题 我有一个POC实施: public interface IRootService { INestedService NestedService { get; set; } void DoSomething(); }

我需要能够将连接字符串传递到我的一些服务实现中。我在构造函数中执行此操作。连接字符串可由用户配置,将作为声明添加到ClaimsPrincipal中

到目前为止一切都很好

不幸的是,我还希望能够充分利用ASP.NET核心中的依赖项注入功能,并通过DI解决服务实现问题

我有一个POC实施:

public interface IRootService
{
    INestedService NestedService { get; set; }

    void DoSomething();
}

public class RootService : IRootService
{
    public INestedService NestedService { get; set; }

    public RootService(INestedService nestedService)
    {
        NestedService = nestedService;
    }

    public void DoSomething()
    {
        // implement
    }
}


public interface INestedService
{
    string ConnectionString { get; set; }

    void DoSomethingElse();
}

public class NestedService : INestedService
{
    public string ConnectionString { get; set; }

    public NestedService(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public void DoSomethingElse()
    {
        // implement
    }
}
这些服务已在启动期间注册,
INestedService
已添加到控制器的构造函数中

public HomeController(INestedService nestedService)
{
    NestedService = nestedService;
}
正如所料,我在尝试激活“Test.Dependency.Services.NestedService”时遇到错误
无法解析“System.String”类型的服务。

我的选项是什么?

简单配置 然后,如果您已在ConfigurationBuilder(通常位于Startup类的构造函数中)中加载了appSettings.json,那么您的ConfigureServices将如下所示:

public void ConfigureServices(IServiceCollection services)
{
    // Choose Scope, Singleton or Transient method
    services.AddSingleton<IRootService, RootService>();
    services.AddSingleton<INestedService, NestedService>(serviceProvider=>
    {
         var connectionString = Configuration["Data:ConnectionString"];
         return new NestedService(connectionString);
    });
}
使用选项生成器
如果需要更多参数,可以更进一步,创建一个options类,并将其传递给RootService的构造函数。如果变得复杂,可以使用生成器模式。

要传递在应用程序启动时未知的运行时参数,必须使用factory模式。这里有两个选项:

  • 工厂类(类似于IHttpClientFactory的实现方式)

  • 工厂法

     services.AddTransient<Func<string,INestedService>>((provider) => 
     {
         return new Func<string,INestedService>( 
             (connectionString) => new NestedService(connectionString)
         );
     });
    
    或者每次通话都解决它

     public class RootService : IRootService
     {
         public Func<string,INestedService> NestedServiceFactory { get; set; }
    
         public RootService(Func<string,INestedService> nestedServiceFactory)
         {
             NestedServiceFactory = nestedServiceFactory;
         }
    
         public void DoSomething(string connectionString)
         {
             var nestedService = nestedServiceFactory(connectionString);
    
             // implement
         }
     }
    
    公共类RootService:IRootService
    {
    public Func NestedServiceFactory{get;set;}
    公共根服务(Func nestedServiceFactory)
    {
    NestedServiceFactory=NestedServiceFactory;
    }
    公共void DoSomething(字符串连接字符串)
    {
    var nestedService=nestedServiceFactory(connectionString);
    //实施
    }
    }
    

  • 我设计了这个小模式来帮助我解析需要运行时参数的对象,但也有DI容器能够解析的依赖项——我使用WPF应用程序的MS DI容器实现了这一点

    我已经有了一个服务定位器(是的,我知道这是一种代码气味-但我试图在示例结束时解决这个问题),我在特定场景中使用它来访问DIC中的对象:

    public interface IServiceFactory
    {
        T Get<T>();
    }
    
    这可以由任何依赖服务工厂的人创建,如:

    public MainWindowVM(IServiceFactory serviceFactory) // constructor
    {
    }
    
    private void OnHomeEvent()
    {
        CurrentView = serviceFactory.Get<ICategorySelectionVM>();
    }
    
    这些可以这样创建:

    private void OnCategorySelectedEvent(Category category)
    {
        CurrentView = serviceFactory.Create<ISubCategorySelectionVM>(category);
    }
    
    private void OnCategorySelectedEvent(类别)
    {
    CurrentView=serviceFactory.Create(类别);
    }
    
    更新:我只是想添加一点增强功能,避免像服务定位器一样使用服务工厂,因此我创建了一个通用服务工厂,它只能解析B类型的对象:

    public interface IServiceFactory<B>
    {
        T Get<T>() where T : B;
    
        T Create<T>(params object[] p) where T : B;
    }
    
    公共接口IServiceFactory
    {
    T Get(),其中T:B;
    T创建(参数对象[]p),其中T:B;
    }
    
    此功能的实现取决于原始服务工厂,它可以解析任何类型的对象:

    public class ServiceFactory<B> : IServiceFactory<B>
    {
        private readonly IServiceFactory serviceFactory;
    
        public ServiceFactory(IServiceFactory serviceFactory)
        {
            this.serviceFactory = serviceFactory;
        }
    
        public T Get<T>() where T : B
        {
            return serviceFactory.Get<T>();
        }
    
        public T Create<T>(params object[] p) where T : B
        {
            return serviceFactory.Create<T>(p);
        }
    }
    
    公共类服务工厂:IServiceFactory
    {
    专用只读设备目录服务工厂;
    公共服务工厂(IServiceFactory服务工厂)
    {
    this.serviceFactory=serviceFactory;
    }
    公共T Get()其中T:B
    {
    return serviceFactory.Get();
    }
    公共T创建(参数对象[]p),其中T:B
    {
    返回serviceFactory.Create(p);
    }
    }
    
    组合根为所有要依赖的泛型类型工厂以及任何类型工厂添加原始服务工厂:

    services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
    services.AddSingleton<IServiceFactory<BaseVM>, ServiceFactory<BaseVM>>();
    
    services.AddSingleton(provider=>newServiceFactory(provider.GetService,(T,P)=>ActivatorUtilities.CreateInstance(provider,T,P));
    services.AddSingleton();
    
    现在,我们的主视图模型可以限制为仅创建从BaseVM派生的对象:

        public MainWindowVM(IServiceFactory<BaseVM> viewModelFactory)
        {
            this.viewModelFactory = viewModelFactory;
        }
    
        private void OnCategorySelectedEvent(Category category)
        {
            CurrentView = viewModelFactory.Create<SubCategorySelectionVM>(category);
        }
    
        private void OnHomeEvent()
        {
            CurrentView = viewModelFactory.Get<CategorySelectionVM>();
        }
    
    公共主窗口虚拟机(IServiceFactory viewModelFactory)
    {
    this.viewModelFactory=viewModelFactory;
    }
    私有void OnCategorySelectedEvent(类别)
    {
    CurrentView=viewModelFactory.Create(类别);
    }
    私有void OnHomeEvent()
    {
    CurrentView=viewModelFactory.Get();
    }
    
    我知道这有点陈旧,但我想我会给出我的意见,因为在我看来,有一种更简单的方法可以做到这一点。这并没有涵盖其他帖子中显示的所有情况。但这是一种简单的方法

    public class MySingleton {
        public MySingleton(string s, int i, bool b){
            ...
        }
    }
    
    不,让我们创建一个服务扩展类来添加更简单的内容并保持它不需要

    public static class ServiceCollectionExtentions
    {
        public static IServiceCollection RegisterSingleton(this IServiceCollection services, string s, int i, bool b) =>
            services.AddSingleton(new MySingleton(s, i, b));
    }
    
    现在从启动时开始调用它

    services.RegisterSingleton("s", 1, true);
    

    除了@Tseng非常有用的回答之外,我发现我还可以将其修改为使用代理:

    public delegate INestedService CreateNestedService(string connectionString);
    
    services.AddTransient((provider) => new CreateNestedService(
        (connectionString) => new NestedService(connectionString)
    ));
    
    以@Tseng建议的相同方式在
    RootService
    中实现:

    public class RootService : IRootService
    {
        public INestedService NestedService { get; set; }
    
        public RootService(CreateNestedService createNestedService)
        {
            NestedService = createNestedService("ConnectionStringHere");
        }
    
        public void DoSomething()
        {
            // implement
        }
    }
    

    对于需要类中工厂实例的情况,我更喜欢这种方法,因为这意味着我可以拥有
    CreateNestedService
    类型的属性,而不是
    Func
    IMHO,请遵循选项模式。定义一个强类型来保存连接字符串,然后定义一个
    IConfigureOptions
    来从用户声明中配置它

    公共类连接字符串{
    公共字符串值{get;set;}
    }
    公共类配置连接:IConfigureOptions{
    专用只读IHttpContextAccessor访问器;
    公共配置连接(IHttpContextAccessor访问器){
    this.accessor=访问器;
    }
    公共无效配置(ConnectionString配置){
    config.Value=accessor.HttpContext.User。。。
    }
    }
    公共类嵌套服务{
    ...
    公共嵌套服务(IOPS连接){
    ConnectionString=connection.Value.Value;
    }
    ...
    }
    
    Hi Ivan,谢谢你的建议,但我知道如何在startup类中添加服务。这个答案不涉及连接字符串将来自当前用户的声明集合的事实-AFAIK这些在启动时不可用?@AntSwift:Cr
            services.AddSingleton<IServiceFactory>(
                provider => new ServiceFactory(
                    provider.GetService, 
                    (T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
    
    public interface IService<I>
    {
        // Returns mapped type for this I
        Type Type();
    }
    
    public class Service<I, T> : IService<I>
    {
        public Type Type()
        {
            return typeof(T);
        }
    }
    
    services.AddSingleton<ICategorySelectionVM, CategorySelectionVM>();
    services.AddSingleton<IService<ISubCategorySelectionVM>, Service<ISubCategorySelectionVM, SubCategorySelectionVM>>();
    services.AddSingleton<ILogger, Logger>();
    
    public CategorySelectionVM(ILogger logger) // constructor
    
    public MainWindowVM(IServiceFactory serviceFactory) // constructor
    {
    }
    
    private void OnHomeEvent()
    {
        CurrentView = serviceFactory.Get<ICategorySelectionVM>();
    }
    
    public SubCategorySelectionVM(ILogger logger, Category c) // constructor
    
    private void OnCategorySelectedEvent(Category category)
    {
        CurrentView = serviceFactory.Create<ISubCategorySelectionVM>(category);
    }
    
    public interface IServiceFactory<B>
    {
        T Get<T>() where T : B;
    
        T Create<T>(params object[] p) where T : B;
    }
    
    public class ServiceFactory<B> : IServiceFactory<B>
    {
        private readonly IServiceFactory serviceFactory;
    
        public ServiceFactory(IServiceFactory serviceFactory)
        {
            this.serviceFactory = serviceFactory;
        }
    
        public T Get<T>() where T : B
        {
            return serviceFactory.Get<T>();
        }
    
        public T Create<T>(params object[] p) where T : B
        {
            return serviceFactory.Create<T>(p);
        }
    }
    
    services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
    services.AddSingleton<IServiceFactory<BaseVM>, ServiceFactory<BaseVM>>();
    
        public MainWindowVM(IServiceFactory<BaseVM> viewModelFactory)
        {
            this.viewModelFactory = viewModelFactory;
        }
    
        private void OnCategorySelectedEvent(Category category)
        {
            CurrentView = viewModelFactory.Create<SubCategorySelectionVM>(category);
        }
    
        private void OnHomeEvent()
        {
            CurrentView = viewModelFactory.Get<CategorySelectionVM>();
        }
    
    public class MySingleton {
        public MySingleton(string s, int i, bool b){
            ...
        }
    }
    
    public static class ServiceCollectionExtentions
    {
        public static IServiceCollection RegisterSingleton(this IServiceCollection services, string s, int i, bool b) =>
            services.AddSingleton(new MySingleton(s, i, b));
    }
    
    services.RegisterSingleton("s", 1, true);
    
    public delegate INestedService CreateNestedService(string connectionString);
    
    services.AddTransient((provider) => new CreateNestedService(
        (connectionString) => new NestedService(connectionString)
    ));
    
    public class RootService : IRootService
    {
        public INestedService NestedService { get; set; }
    
        public RootService(CreateNestedService createNestedService)
        {
            NestedService = createNestedService("ConnectionStringHere");
        }
    
        public void DoSomething()
        {
            // implement
        }
    }