Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 332
Ant Swift Avatar asked Jun 10 '16 09:06

Ant Swift


2 Answers

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      }  } 
like image 189
Tseng Avatar answered Oct 31 '22 16:10

Tseng


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.

like image 24
Ivan Prodanov Avatar answered Oct 31 '22 16:10

Ivan Prodanov