49

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?

1
  • Use factory delegate overload when registering the repository Commented Jan 10, 2019 at 11:17

5 Answers 5

60

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

//...
Sign up to request clarification or add additional context in comments.

2 Comments

Also, if there are other services that GlobalRepository needs then I just pass them in manually with sp.GetService<IOtherServices>?
A reference for this usage: stevejgordon.co.uk/…
15

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

2 Comments

Bill, your gist would simpler (and therefore better) if you are not required to seperate your interface (TService) from an implemetation (TImplementation). Where your gist would be very appropriate is when you aim to use the factory to generate a class that contains not much implementation code (for example a DAO). Thanks for your comment.
6

I'll show the minimal example for the factory that resolves ITalk implementation by a string key. 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:

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:

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:

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

Now you can inject the TalkFactory and resolve the implementation by the name:

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

The trick here is Configure<TOptions(). This method is additive, which means you can call it multiple times to configure the same instance of 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.

Comments

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

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.