簡體   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