Home > Enterprise >  Microsoft DI: If two different classes take the same type in their constructor params, how can I inj
Microsoft DI: If two different classes take the same type in their constructor params, how can I inj

Time:11-03

Using Microsoft's generic host DI and configuration, let's say I want two different objects (maybe of different, even unrelated types) that each take a SomeKindOfParameter in their constructors. They may need different SomeKindOfParameter values to be injected into them, with the values specified in configuration (like, say, in appsettings.json). How do I do this?

I know how I can get different values for different SomeKindOfParameter instances out of configuration:

SomeKindOfParameter parameterForTypeX = config.GetSection("ParamForX").Get<SomeKindOfParameter>();
SomeKindOfParameter parameterForTypeY = config.GetSection("ParamForY").Get<SomeKindOfParameter>();

And given a single SomeKindOfParameter, I know how I can get it injected into both of them:

SomeKindOfParameter param = ...
services.AddSingleton<SomeKindOfParameter>(param);
services.AddTransient<IX, X>();
services.AddTransient<IY, Y>();

But how can I inject parameterForTypeX into X and parameterForTypeY into Y? Thanks.

CodePudding user response:

he "right" way is to use named options, as described in the documentation.

services.Configure<SomeKindOfParameter>("ParamForX",
                                       Configuration.GetSection("SectionForX"));
services.AddTransient<ServiceX>();

public class ServiceX{
    public ServiceX(IOptionsSnapshot<SomeKindOfParameter> snapshot){
        .... = snapshot.Get("ParamForX");
    }
}

While a service collection will only have one service object for each type, you could add a layer of indirection via an "open generic" service, to hide the implementation details and provide a different configuration instance to each service;

public class Parameter<T> where T : class {
    public Parameter(IOptionsSnapshot<SomeKindOfParameter> snapshot){
        Value = snapshot.Get(typeof(T).Name);
    }
    public SomeKindOfParameter Value { get; private set; }
}

public ServiceX(Parameter<ServiceX> p){
    .... = p.Value;
}

services.AddSingleton(typeof(Parameter<>));

There's probably also a way to bind each configuration section on the fly. Based on how .Configure is implemented.

CodePudding user response:

There are a couple ways:

Use generic type

You could define a generic helper type derived from SomeKindOfParameter:

class SomeKindOfParameter { }

class SomeKindOfParameter<TCategory> : SomeKindOfParameter
{ }

Then inject SomeKindOfParameter of TCategory accordingly to each service:

services.AddTransient<SomeKindOfParameter<X>>();
services.AddTransient<IX, X>();

services.AddTransient<SomeKindOfParameter<Y>>();
services.AddTransient<IY, Y>();

class X : IX
{
    public X(SomeKindOfParameter<X> parameters)
    {
    }
}

class Y : IY
{
    public Y(SomeKindOfParameter<Y> parameters)
    {
    }
}

Use ActivatorUtilities class

ActivatorUtilities.CreateInstance method allows you to pass parameters directly to the constructor:

services.AddTransient<IX>(serviceProvider =>
{
    var config = serviceProvider.GetRequiredService<IConfiguration>();
    SomeKindOfParameter parameterForTypeX = config.GetSection("ParamForX")
        .Get<SomeKindOfParameter>();

    return ActivatorUtilities.CreateInstance<X>(
        serviceProvider,
        // Parameters not passed through this array will be activated
        // from DI container
        new object[]
        {
            parameterForTypeX
        });
});
  • Related