89

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?

svick
  • 236,525
  • 50
  • 385
  • 514
Ant Swift
  • 20,089
  • 10
  • 38
  • 55
  • In general, runtime values should not be injected into the constructors of your components, as expressed [here](https://blogs.cuttingedge.it/steven/posts/2015/code-smell-injecting-runtime-data-into-components/). Do note, however, that in your case, the connection string is *not* a runtime value, as it the value is known at startup and constant. Values passed in by the used through a web request, on the other hand, are considered runtime values. – Steven Apr 11 '19 at 09:22
  • My generic solution: https://gist.github.com/ReallyLiri/c669c60db2109554d5ce47e03613a7a9 – Mugen Sep 18 '19 at 11:52
  • @Mugen - Could you please provide some insight as to the purpose of the value in the name/value pairs that are passed to/created in the two methods public static void AddSingletonWithConstructorParams( this IServiceCollection services, object paramsWithNames) public static void AddSingletonWithConstructorParams( this IServiceCollection services, params object[] parameters) – SOHO Developer Mar 17 '20 at 11:30
  • @SOHODeveloper if you want to inject parameters you have two options by my solution: either pass them by value, but then you'd have to have at most one value per concrete type, or pass them as key value pairs, where you specify actual constructor parameter names. It can be used for more complex constructors that requires many parameters of same type. Key value pairs are represented by an `object` because I found it more convenient. – Mugen Mar 17 '20 at 11:40
  • @Mugen - I seem to be a bit dense today. Would you be so kind as to show an example of registering the service with just one parameter and then show how to retrieve and instance of the service. – SOHO Developer Mar 18 '20 at 20:47
  • @SOHODeveloper updated in gist :) – Mugen Mar 19 '20 at 07:21
  • @Mugen - Thanks much. Your examples make perfect sense based on what I dug out of the code. Unfortunately for me, that is not what I want/need. I want to be able to apply the (for example 60, false) when the service is created/retrieved and also create another instance with for example 70, true). I am sure that the updates will be useful for people going forward. – SOHO Developer Mar 19 '20 at 21:10

6 Answers6

97

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:

  1. 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);
    
  2. 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
         }
     }
    
Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 3
    do you have a full sample of this ? it's not clear to me an I have the feeling than some parts are missing. – TheBoubou Feb 20 '19 at 17:57
  • 4
    Does that mean I have to resolve all the dependencies manually? – Kishan Vaishnav Feb 28 '19 at 12:50
  • @KishanVaishnav With the above sample yes (but you are doing that in the composition root, where you set up the factory). On a lot of dependencies, you may want to consider [this answer](https://stackoverflow.com/a/53885374/455493) – Tseng Feb 28 '19 at 12:53
  • The problem is I want to pass a dynamic parameter and not a static. Can you look over this question? And tell me what's wrong with the question. https://stackoverflow.com/questions/54925945/change-dbcontext-based-on-user-login – Kishan Vaishnav Feb 28 '19 at 12:58
  • @KishanVaishnav: On your `IConnectionStringsService` have a method which accepts user id (or tentant id - what ever you need to know how to assign connection string to a user) as a parameter and returns a connection string for that user – Tseng Feb 28 '19 at 13:24
  • Or Resolve `IHttpContextAccessor` in your `IConnectionStringServce` implementation and get the user ID / tenant / whatever to query for the connection string – Tseng Feb 28 '19 at 14:28
  • I've done something like this. if (httpContextAccessor.HttpContext.User.Identity.IsAuthenticated) { var username = httpContextAccessor.HttpContext.User.Identity.Name; var dbContext1 = ConnectionStringService.Create(username, _loggerFactory); _userService.DbContext = dbContext1; } The create method creates DbContext based on username and then I assign the DbContext to my service. Is this correct approach? – Kishan Vaishnav Mar 01 '19 at 05:21
  • I want to be able to pay you a bounty for this answer. I never could have figured it out myself and it's exactly what I needed to know. – catfood May 10 '20 at 16:03
74

Simple configuration

public void ConfigureServices(IServiceCollection services)
{
    // Choose Scope, Singleton or Transient method
    services.AddSingleton<IRootService, RootService>();
    services.AddSingleton<INestedService, NestedService>(serviceProvider=>
    {
         return new NestedService("someConnectionString");
    });
}

With appSettings.json

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

With extension methods

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

With options builder

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.

Ivan Prodanov
  • 34,634
  • 78
  • 176
  • 248
  • 6
    Hi Ivan, thanks for the suggestion but I am aware of how to add services in the startup class. This answer doesn't deal with the fact that the connection string will come from the current user's Claims collection - AFAIK these aren't available at startup? – Ant Swift Jun 10 '16 at 11:00
  • 1
    @AntSwift: Create a factory class, pass IServiceProvider to it, implement a "CreateMyService" method and pass the parameter to it, create/instantiate your class with the required parameter. Inject the factory into your controllers/services instead of the desired service. If a whole class is to much for your needs, register a `Func` as dependency and inject it instead of `IMyService`, then resolve it via `myServiceFactory(myConnectionString)`. – Tseng Jun 10 '16 at 13:00
  • @Tseng - thanks for the suggestion. I'm trying out an implementation of this now and all looks good. You should promote the comment to an answer and reap all the glory. – Ant Swift Jun 11 '16 at 12:50
  • I am not able to debug var connectionString = Configuration["Data:ConnectionString"]; //With appSettings.json – kudlatiger Aug 21 '18 at 09:09
10

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>();
    }
Jay Evans
  • 211
  • 4
  • 7
  • Very nice. The only suggestion is to add as Scoped vs Singleton. This way it guarantees scoped related dependencies by using the correct provider => – code5 Jan 09 '20 at 01:17
  • It's a concise solution to get ViewModels to depend on a factory service that depends on the .NET `ServiceProvider` to make ViewModels still free to use registered services in the container created by ServiceProvider. – liviaerxin Aug 11 '22 at 05:41
0

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>.

Chris Mack
  • 5,148
  • 2
  • 12
  • 29
  • What benefit do you get from naming your delegate as if it's an interface? Because that's incredibly confusing and violates the principle of least astonishment. – Ian Kemp Apr 15 '21 at 15:47
  • @IanKemp Thanks for pointing that out - I've corrected it. – Chris Mack Apr 15 '21 at 19:15
0

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;
    }
    ...
}
Jeremy Lakeman
  • 9,515
  • 25
  • 29
-1

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);
Cornelis
  • 1,065
  • 8
  • 23