Reputation: 21177
I need to be able to pass a connection string into some of my service implementations. I am doing this in the constructor. The connection string is configurable by user will be added the ClaimsPrincipal as a Claim.
All fine so far.
Unfortunately, I also want to be able to use the dependency injection features in ASP.NET Core to the fullest and resolve the service implementation though DI.
I have a POC implmentation:
public interface IRootService
{
INestedService NestedService { get; set; }
void DoSomething();
}
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(INestedService nestedService)
{
NestedService = nestedService;
}
public void DoSomething()
{
// implement
}
}
public interface INestedService
{
string ConnectionString { get; set; }
void DoSomethingElse();
}
public class NestedService : INestedService
{
public string ConnectionString { get; set; }
public NestedService(string connectionString)
{
ConnectionString = connectionString;
}
public void DoSomethingElse()
{
// implement
}
}
These services have been registered during startup and INestedService
has been added the constructor of a controller.
public HomeController(INestedService nestedService)
{
NestedService = nestedService;
}
As expected, I get the error Unable to resolve service for type 'System.String' while attempting to activate 'Test.Dependency.Services.NestedService'.
What are my options here?
Upvotes: 99
Views: 105368
Reputation: 5203
If you are using AutoFac, you can inject ILifetimeScope
and use it's .Resolve<T>(...)
function. AutoFac's Resolve
function can accept runtime parameters to use instead of the default implementation. See https://docs.autofac.org/en/latest/resolve/parameters.html
Adding AutoFac purely for this scenario isn't a bad idea. The rest of your application can stay the same.
It's a shame you can't do this easily with the built-in DI.
Upvotes: 0
Reputation: 858
If connectionString
is not known at the moment of registering services: connectionString
should not be a parameter of the constructor. You can make it a parameter of the methods that need it or retrieve it in another way (e.g. get it from another service).
If connectionString
is known at the moment of registering services (this is usually the case with connection strings): Register the NestedService
normally. Like this
services.AddSingleton<INestedService>(s =>
new NestedService(connectionString)
);
Upvotes: 0
Reputation: 64121
To pass a runtime parameter not known at the start of the application, you have to use the factory pattern. You have two options here:
factory class (similar to how IHttpClientFactory
is implemented)
public class RootService : IRootService
{
public RootService(INestedService nested, IOtherService other)
{
// ...
}
}
public class RootServiceFactory : IRootServiceFactory
{
// in case you need other dependencies, that can be resolved by DI
private readonly IServiceProvider services;
public RootServiceFactory(IServiceProvider services)
{
this.services = services;
}
public IRootService CreateInstance(string connectionString)
{
// instantiate service that needs runtime parameter
var nestedService = new NestedService(connectionString);
// note that in this example, RootService also has a dependency on
// IOtherService - ActivatorUtilities.CreateInstance will automagically
// resolve that dependency, and any others not explicitly provided, from
// the specified IServiceProvider
return ActivatorUtilities.CreateInstance<RootService>(services,
new object[] { nestedService, });
}
}
and inject IRootServiceFactory
instead of your IRootService
IRootService rootService = rootServiceFactory.CreateInstance(connectionString);
factory method
services.AddTransient<Func<string,INestedService>>((provider) =>
{
return new Func<string,INestedService>(
(connectionString) => new NestedService(connectionString)
);
});
and inject the factory method into your service instead of INestedService
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(Func<string,INestedService> nestedServiceFactory)
{
NestedService = nestedServiceFactory("ConnectionStringHere");
}
public void DoSomething()
{
// implement
}
}
or resolve it per call
public class RootService : IRootService
{
public Func<string,INestedService> NestedServiceFactory { get; set; }
public RootService(Func<string,INestedService> nestedServiceFactory)
{
NestedServiceFactory = nestedServiceFactory;
}
public void DoSomething(string connectionString)
{
var nestedService = nestedServiceFactory(connectionString);
// implement
}
}
Upvotes: 106
Reputation: 5208
Further to @Tseng's extremely helpful answer, I found I could also adapt it to use delegates:
public delegate INestedService CreateNestedService(string connectionString);
services.AddTransient((provider) => new CreateNestedService(
(connectionString) => new NestedService(connectionString)
));
Implemented in RootService
in the same way @Tseng suggested:
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(CreateNestedService createNestedService)
{
NestedService = createNestedService("ConnectionStringHere");
}
public void DoSomething()
{
// implement
}
}
I prefer this approach for cases where I need an instance of a factory in a class, as it means I can have a property of type CreateNestedService
rather than Func<string, INestedService>
.
Upvotes: 1
Reputation: 11090
IMHO, follow the options pattern. Define a strong type to hold your connection string, then an IConfigureOptions<T>
to configure it from your user claim.
public class ConnectionString {
public string Value { get; set; }
}
public class ConfigureConnection : IConfigureOptions<ConnectionString> {
private readonly IHttpContextAccessor accessor;
public ConfigureConnection (IHttpContextAccessor accessor) {
this.accessor = accessor;
}
public void Configure(ConnectionString config) {
config.Value = accessor.HttpContext.User ...
}
}
public class NestedService {
...
public NestedService(IOptions<ConnectionString> connection) {
ConnectionString = connection.Value.Value;
}
...
}
Upvotes: 0
Reputation: 1107
I know this is a bit old but thought i'd give my input since there is a easier way to do this in my opinion. This doesn't cover all the cases as shown in other posts. But this is a easy way of doing it.
public class MySingleton {
public MySingleton(string s, int i, bool b){
...
}
}
No lets create a service extention class to add easier and keep it neet
public static class ServiceCollectionExtentions
{
public static IServiceCollection RegisterSingleton(this IServiceCollection services, string s, int i, bool b) =>
services.AddSingleton(new MySingleton(s, i, b));
}
Now to call it from startup
services.RegisterSingleton("s", 1, true);
Upvotes: -1
Reputation: 255
I devised this little pattern to help me resolve objects that require runtime parameters ,but also have dependencies which the DI container is able to resolve - I implemented this using the MS DI Container for a WPF App.
I already had a Service Locator (yes I know its a code smell - but I attempt to resolve that by the end of the example) that I used in specific scenarios to get access to objects in the DIC:
public interface IServiceFactory
{
T Get<T>();
}
Its implementation takes a func<> in the constructor to decouple the fact it relies on MS DI.
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
public ServiceFactory(Func<Type, object> factory)
{
this.factory = factory;
}
// Get an object of type T where T is usually an interface
public T Get<T>()
{
return (T)factory(typeof(T));
}
}
This was created in the composition root like so:
services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService));
This pattern was extended to not only 'Get' objects of type T, but 'Create' objects of type T with parameters P:
public interface IServiceFactory
{
T Get<T>();
T Create<T>(params object[] p);
}
The implementation took another func<> to decouple the creation mechanism:
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
private readonly Func<Type, object[], object> creator;
public ServiceFactory(Func<Type, object> factory, Func<Type, object[], object> creator)
{
this.factory = factory;
this.creator = creator;
}
// Get an object of type T where T is usually an interface
public T Get<T>()
{
return (T)factory(typeof(T));
}
// Create (an obviously transient) object of type T, with runtime parameters 'p'
public T Create<T>(params object[] p)
{
IService<T> lookup = Get<IService<T>>();
return (T)creator(lookup.Type(), p);
}
}
The creation mechanism for the MS DI container is in the ActivatorUtilities extensions, here's the updated composition root:
services.AddSingleton<IServiceFactory>(
provider => new ServiceFactory(
provider.GetService,
(T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
Now that we can create objects the problem becomes we have no way of determining the type of object we need without the DI container actually creating an object of that type, which is where the IService interface comes in:
public interface IService<I>
{
// Returns mapped type for this I
Type Type();
}
This is used to determine what type we are trying to create, without actually creating the type, its implementation is:
public class Service<I, T> : IService<I>
{
public Type Type()
{
return typeof(T);
}
}
So to pull it all together, in your composition root you can have objects that don't have runtime parameters which can be resolved by 'Get' and ones which do resolved by 'Create' e.g.:
services.AddSingleton<ICategorySelectionVM, CategorySelectionVM>();
services.AddSingleton<IService<ISubCategorySelectionVM>, Service<ISubCategorySelectionVM, SubCategorySelectionVM>>();
services.AddSingleton<ILogger, Logger>();
The CategorySelectionVM has only dependencies that can be resolved via the DIC:
public CategorySelectionVM(ILogger logger) // constructor
And this can be created by anyone with a dependency on the service factory like:
public MainWindowVM(IServiceFactory serviceFactory) // constructor
{
}
private void OnHomeEvent()
{
CurrentView = serviceFactory.Get<ICategorySelectionVM>();
}
Where as the SubCategorySelectionVM has both dependencies that the DIC can resolve, and dependencies only known at runtime:
public SubCategorySelectionVM(ILogger logger, Category c) // constructor
And these can be created like so:
private void OnCategorySelectedEvent(Category category)
{
CurrentView = serviceFactory.Create<ISubCategorySelectionVM>(category);
}
Update : I just wanted to add a little enhancement which avoided using the service factory like a service locator, so I created a generic service factory which could only resolve objects of type B:
public interface IServiceFactory<B>
{
T Get<T>() where T : B;
T Create<T>(params object[] p) where T : B;
}
The implementation of this depends on the original service factory which could resolve objects of any type:
public class ServiceFactory<B> : IServiceFactory<B>
{
private readonly IServiceFactory serviceFactory;
public ServiceFactory(IServiceFactory serviceFactory)
{
this.serviceFactory = serviceFactory;
}
public T Get<T>() where T : B
{
return serviceFactory.Get<T>();
}
public T Create<T>(params object[] p) where T : B
{
return serviceFactory.Create<T>(p);
}
}
The composition root adds the original service factory for all the generic typed factories to depend on, and any of the typed factories:
services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
services.AddSingleton<IServiceFactory<BaseVM>, ServiceFactory<BaseVM>>();
Now our main view model can be restricted to creating only objects that derive from BaseVM:
public MainWindowVM(IServiceFactory<BaseVM> viewModelFactory)
{
this.viewModelFactory = viewModelFactory;
}
private void OnCategorySelectedEvent(Category category)
{
CurrentView = viewModelFactory.Create<SubCategorySelectionVM>(category);
}
private void OnHomeEvent()
{
CurrentView = viewModelFactory.Get<CategorySelectionVM>();
}
Upvotes: 10
Reputation: 35502
public void ConfigureServices(IServiceCollection services)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(serviceProvider=>
{
return new NestedService("someConnectionString");
});
}
If you decide to hide your connection string inside appSettings.json, e.g:
"Data": {
"ConnectionString": "someConnectionString"
}
Then provided that you've loaded your appSettings.json in the ConfigurationBuilder (usually located in the constructor of the Startup class), then your ConfigureServices would look like this:
public void ConfigureServices(IServiceCollection services)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(serviceProvider=>
{
var connectionString = Configuration["Data:ConnectionString"];
return new NestedService(connectionString);
});
}
namespace Microsoft.Extensions.DependencyInjection
{
public static class RootServiceExtensions //you can pick a better name
{
//again pick a better name
public static IServiceCollection AddRootServices(this IServiceCollection services, string connectionString)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(_ =>
new NestedService(connectionString));
}
}
}
Then your ConfigureServices method would look like this
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["Data:ConnectionString"];
services.AddRootServices(connectionString);
}
Should you need more parameters, you can go a step further and create an options class which you pass to RootService's constructor. If it becomes complex, you can use the Builder pattern.
Upvotes: 80