Using Factory Pattern with ASP.NET Core Dependency Injection

40,118

Solution 1

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

//...

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

//...

Solution 2

You might want to also check these links...

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

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

Solution 3

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. Source code, NuGet package and explanatory blog article below:

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

Solution 4

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. 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();
    }
}
Share:
40,118
fosbie
Author by

fosbie

Updated on July 09, 2022

Comments

  • fosbie
    fosbie almost 2 years

    I need the ASP.Net Core dependency injection to pass some parameters to the constructor of my GlobalRepository class which implements the ICardPaymentRepository interface.

    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.

    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.

    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:

    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?