How can I pass a runtime parameter as part of the dependency resolution?

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

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.


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