Search code examples
c#dependency-injectionasp.net-core.net-core

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


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?


Solution

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