繁体   English   中英

使用带有 ASP.NET 核心依赖注入的工厂模式

[英]Using Factory Pattern with ASP.NET Core Dependency Injection

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

参数用于配置,来自配置文件和数据库,我不希望我的 class 到 go 并引用数据库和配置本身。

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

我的初创公司目前看起来像这样:

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 的构造函数中?

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

//...

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);
});

//...

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

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();}
}

我一直在遇到同样的问题,并通过为IFactory<TService>IFactory<T, TService>IFactory<T1, T2, TService>等注册一组开放泛型解决了这个问题。启动时的单个调用添加然后,此设施允许注入/解析任何IFactory<...> ,这将为给定的参数类型集实例化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

其他答案的替代方案。 遵循选项模式

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

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

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

如果您愿意,您仍然可以使用服务工厂方法来解IOptions<RespositoryOptions> 但是随后您就无法验证您的服务依赖项是否已全部满足。

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

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();
    }
}

我将展示通过字符串键解析ITalk实现的工厂的最小示例。 该解决方案可以很容易地扩展到具有任何键和实体类型的通用工厂。

为了举例,让我们定义接口ITalk和两个实现CatDog

public interface ITalk
{
    string Talk();
}

public class Cat : ITalk
{
    public string Talk() => "Meow!";
}

public class Dog : ITalk
{
    public string Talk() => "Woof!";
}

现在定义TalkFactoryOptionsTalkFactory

public class TalkFactoryOptions
{
    public IDictionary<string, Type> Types { get; } = new Dictionary<string, Type>();

    public void Register<T>(string name) where T : ITalk
    {
        Types.Add(name, typeof(T));
    }
}

public class TalkFactory
{
    private readonly IServiceProvider _provider;
    private readonly IDictionary<string, Type> _types;

    public TalkFactory(IServiceProvider provider, IOptions<TalkFactoryOptions> options)
    {
        _provider = provider;
        _types = options.Value.Types;
    }

    public ITalk Resolve(string name)
    {
        if (_types.TryGetValue(name, out var type))
        {
            return (ITalk)_provider.GetRequiredService(type);
        }

        throw new ArgumentOutOfRangeException(nameof(name));
    }
}

为简单的实现注册添加扩展方法:

public static class FactoryDiExtensions
{
    public static IServiceCollection RegisterTransientSpeaker<TImplementation>(this IServiceCollection services, string name)
        where TImplementation : class, ITalk
    {
        services.TryAddTransient<TalkFactory>();
        services.TryAddTransient<TImplementation>();
        services.Configure<TalkFactoryOptions>(options => options.Register<TImplementation>(name));
        return services;
    }
}

并注册CatDog实现:

services
  .RegisterTransientSpeaker<Cat>("cat")
  .RegisterTransientSpeaker<Dog>("dog");

现在您可以注入TalkFactory并通过名称解析实现:

var speaker = _factory.Resolve("cat");
var speech = speaker.Talk();

这里的诀窍是Configure<TOptions() 此方法是附加的,这意味着您可以多次调用它来配置同一个TalkFactoryOptions实例。
正如我所说,这个示例可以转换为通用工厂,并添加注册工厂委托而不是具体类型的能力。 但是代码对于 SO 来说太长了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM