简体   繁体   English

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

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

I need the ASP.Net Core dependency injection to pass some parameters to the constructor of my GlobalRepository class which implements the ICardPaymentRepository interface.我需要 ASP.Net Core 依赖注入将一些参数传递给实现 ICardPaymentRepository 接口的 GlobalRepository class 的构造函数。

The parameters are for configuration and come from the config file and the database, and I don't want my class to go and reference the database and config itself.参数用于配置,来自配置文件和数据库,我不希望我的 class 到 go 并引用数据库和配置本身。

I think the factory pattern is the best way to do this but I can't figure out the best way to use a factory class which itself has dependencies on config and database.我认为工厂模式是做到这一点的最佳方式,但我想不出使用工厂 class 的最佳方式,它本身依赖于配置和数据库。

My startup looks like this currently:我的初创公司目前看起来像这样:

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)
    {
     ...
    }
}

The GlobalRepository constructor looks like this: GlobalRepository 构造函数如下所示:

public GlobalRepository(string mode, string apiKey)
{
}

How do I now pass the mode from configuration and the apiKey from the DbRepository into the constructor from Startup?我现在如何将配置中的模式和 DbRepository 中的 apiKey 传递到 Startup 的构造函数中?

Use the factory delegate overload when registering the repository注册存储库时使用工厂委托重载

//...

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

//...

Alternative using ActivatorUtilities.CreateInstance使用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);
});

//...

You might want to also check these links...您可能还想检查这些链接...

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

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

With regard to the last link the code is basically:关于最后一个链接,代码基本上是:

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

I think castle windsor's typed factories dispose of all they created when they themselves are disposed (which may not be always the best idea), with these links you would probably have to consider if you are still expecting that behaviour.我认为温莎城堡的类型工厂会在他们自己被处置时处置他们创建的所有内容(这可能并不总是最好的主意),如果您仍然期待这种行为,您可能需要考虑这些链接。 When I reconsidered why I wanted a factory I ended up just creating a simple factory wrapping new, such as:当我重新考虑为什么我想要一个工厂时,我最终只是创建了一个简单的工厂包装新工厂,例如:

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

I've been running up against the same issue and solved this by registering a set of open generics for IFactory<TService> , IFactory<T, TService> , IFactory<T1, T2, TService> etc. A single call on startup to add this facility then allows any IFactory<...> to be injected / resolved, which will instantiate an instance of TService for a given set of argument types, provided a constuctor exists whose last parameters match the T* types of the factory generic.我一直在遇到同样的问题,并通过为IFactory<TService>IFactory<T, TService>IFactory<T1, T2, TService>等注册一组开放泛型解决了这个问题。启动时的单个调用添加然后,此设施允许注入/解析任何IFactory<...> ,这将为给定的参数类型集实例化TService的实例,前提是存在一个构造函数,其最后一个参数与工厂泛型的T*类型匹配。 Source code, NuGet package and explanatory blog article below:下面的源代码、NuGet 包和解释性博客文章:

https://github.com/jmg48/useful https://github.com/jmg48/useful

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

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

An alternative to the other answers.其他答案的替代方案。 Follow the options pattern .遵循选项模式

First introduce a strong type for your configuration;首先为你的配置引入一个强类型;

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

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

You could still use a service factory method to unwrap the IOptions<RespositoryOptions> if you prefer.如果您愿意,您仍然可以使用服务工厂方法来解IOptions<RespositoryOptions> But then you lose the ability to verify that your service dependencies have all been met.但是随后您就无法验证您的服务依赖项是否已全部满足。

Then you can seed your options from configuration;然后你可以从配置中播种你的选项;

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

And write another service to update that options instance from other services, like a database;并编写另一个服务以从其他服务(如数据库)更新该选项实例;

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

I'll show the minimal example for the factory that resolves ITalk implementation by a string key.我将展示通过字符串键解析ITalk实现的工厂的最小示例。 The solution can be easily extended to a generic factory with any key and entity type.该解决方案可以很容易地扩展到具有任何键和实体类型的通用工厂。

For the sake of example let's define the interface ITalk and two implementations Cat and Dog :为了举例,让我们定义接口ITalk和两个实现CatDog

public interface ITalk
{
    string Talk();
}

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

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

Now define the TalkFactoryOptions and TalkFactory :现在定义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));
    }
}

Add extension method for simple implementations registration:为简单的实现注册添加扩展方法:

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

And register the Cat and Dog implementations:并注册CatDog实现:

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

Now you can inject the TalkFactory and resolve the implementation by the name:现在您可以注入TalkFactory并通过名称解析实现:

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

The trick here is Configure<TOptions() .这里的诀窍是Configure<TOptions() This method is additive, which means you can call it multiple times to configure the same instance of TalkFactoryOptions .此方法是附加的,这意味着您可以多次调用它来配置同一个TalkFactoryOptions实例。
As I said this example can be converted into a generic factory and add the ability to register factory delegate instead of a concrete type.正如我所说,这个示例可以转换为通用工厂,并添加注册工厂委托而不是具体类型的能力。 But the code will be too long for SO.但是代码对于 SO 来说太长了。

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

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