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