【问题标题】:Using Factory Pattern with ASP.NET Core Dependency Injection将工厂模式与 ASP.NET Core 依赖注入一起使用
【发布时间】:2019-06-05 05:56:59
【问题描述】:

我需要 ASP.Net Core 依赖注入将一些参数传递给实现 ICardPaymentRepository 接口的 GlobalRepository 类的构造函数。

参数用于配置,来自配置文件和数据库,我不希望我的班级去引用数据库和配置本身。

我认为工厂模式是做到这一点的最佳方式,但我想不出使用工厂类的最佳方式,工厂类本身依赖于配置和数据库。

我的创业公司目前是这样的:

public class Startup
{
    public IConfiguration _configuration { get; }
    public IHostingEnvironment _environment { get; }

    public Startup(IConfiguration configuration, IHostingEnvironment environment)
    {
        _configuration = configuration;
        _environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IDbRepository, DbRepository>();
        var connection = _configuration.GetConnectionString("DbConnection");
        services.Configure<ConnectionStrings>(_configuration.GetSection("ConnectionStrings"));
        services.AddDbContext<DbContext>(options => options.UseSqlServer(connection));
        services.AddScoped<ICardPaymentRepository, GlobalRepository>();
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IRFDbRepository rFDbRepository)
    {
     ...
    }
}

GlobalRepository 构造函数如下所示:

public GlobalRepository(string mode, string apiKey)
{
}

我现在如何将配置中的模式和 DbRepository 中的 apiKey 传递到 Startup 的构造函数中?

【问题讨论】:

  • 注册存储库时使用工厂委托重载

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


【解决方案1】:

其他答案的替代方案。关注options pattern。

首先为你的配置引入一个强类型;

public class RespositoryOptions {
    public string Mode { get; set; }
    public string ApiKey { get; set; }
}

public GlobalRepository(IOptions<RespositoryOptions> options) {
    // use options.Value;
}

如果您愿意,您仍然可以使用服务工厂方法来解开 IOptions&lt;RespositoryOptions&gt;。但随后您将无法验证您的服务依赖项是否已得到满足。

然后您可以从配置中播种您的选项;

public void ConfigureServices(IServiceCollection services) {
    ...
    services.Configure<RespositoryOptions>(_configuration.GetSection(name));
    ...
}

并编写另一个服务以从其他服务(如数据库)更新该选项实例;

public class ConfigureRespositoryOptions : IConfigureOptions<RespositoryOptions> {
    private readonly IDbRepository repo;
    public ConfigureRespositoryOptions(IDbRepository repo) {
        this.repo = repo;
    }
    public void Configure(RespositoryOptions config) {
        string apiKey = repo.GetApiKeyMethodHere();
    }
}

【讨论】:

    【解决方案2】:

    注册存储库时使用工厂委托重载

    //...
    
    string mode = "get value from config";
    
    services.AddScoped<ICardPaymentRepository, GlobalRepository>(sp => {        
        IDbRepository repo = sp.GetRequiredService<IDbRepository>();
        string apiKey = repo.GetApiKeyMethodHere();
    
        return new GlobalRepository(mode, apiKey);
    });
    
    //...
    

    替代使用ActivatorUtilities.CreateInstance

    //...
    
    string mode = "get value from config";
    
    services.AddScoped<ICardPaymentRepository>(sp => {        
        IDbRepository repo = sp.GetRequiredService<IDbRepository>();
        string apiKey = repo.GetApiKeyMethodHere();
    
        return ActivatorUtilities.CreateInstance<GlobalRepository>(sp, mode, apiKey);
    });
    
    //...
    

    【讨论】:

    • 另外,如果 GlobalRepository 需要其他服务,那么我只需使用 sp.GetService 手动传递它们?
    • 此用法的参考:stevejgordon.co.uk/…
    【解决方案3】:

    我一直在遇到同样的问题,并通过为IFactory&lt;TService&gt;、IFactory&lt;T, TService&gt;、IFactory&lt;T1, T2, TService&gt; 等注册一组开放泛型解决了这个问题。在启动时添加此工具的单个调用然后允许任何@ 987654327@ 将被注入/解析,这将为给定的一组参数类型实例化一个 TService 的实例,前提是存在一个构造函数,其最后一个参数与工厂泛型的 T* 类型匹配。源代码、NuGet 包和解释性博客文章如下:

    https://github.com/jmg48/useful

    https://www.nuget.org/packages/Ariadne.Extensions.ServiceCollection/

    https://jon-glass.medium.com/abstract-factory-support-for-microsoft-net-dependency-injection-3c3834894c19

    【讨论】:

      【解决方案4】:

      您可能还想查看这些链接...

      https://github.com/Microsoft/AspNetCoreInjection.TypedFactories

      https://espressocoder.com/2018/10/08/injecting-a-factory-service-in-asp-net-core/

      关于最后一个链接,代码基本上是:

      public class Factory<T> : IFactory<T>
      {
          private readonly Func<T> _initFunc;
      
          public Factory(Func<T> initFunc)
          {
              _initFunc = initFunc;
          }
      
          public T Create()
          {
              return _initFunc();
          }
      }
      
      public static class ServiceCollectionExtensions
      {
          public static void AddFactory<TService, TImplementation>(this IServiceCollection services) 
          where TService : class
          where TImplementation : class, TService
          {
              services.AddTransient<TService, TImplementation>();
              services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
              services.AddSingleton<IFactory<TService>, Factory<TService>>();
          }
      }
      

      我认为温莎城堡的类型化工厂会在他们自己被处理时处理他们创建的所有内容(这可能并不总是最好的主意),如果您仍然期待这种行为,您可能必须考虑这些链接。当我重新考虑我为什么想要一个工厂时,我最终只是创建了一个简单的工厂包装新的,例如:

      public class DefaultFooFactory: IFooFactory{
        public IFoo create(){return new DefaultFoo();}
      }
      

      【讨论】:

      • Bill,如果您不需要将接口 (TService) 与实现 (TImplementation) 分开,那么您的要点会更简单(因此更好)。当您的目标是使用工厂生成一个不包含太多实现代码的类(例如 DAO)时,您的要点非常合适。感谢您的评论。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-19
      • 2018-09-25
      • 2017-03-23
      相关资源
      最近更新 更多