How to register multiple implementations of the same interface in Asp.Net Core?

前端 未结 24 1951
不思量自难忘°
不思量自难忘° 2020-11-22 10:16

I have services that are derived from the same interface.

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService         


        
24条回答
  •  醉酒成梦
    2020-11-22 11:06

    I know this post is a couple years old, but I keep running into this and I'm not happy with the service locator pattern.

    Also, I know the OP is looking for an implementation which allows you to choose a concrete implementation based on a string. I also realize that the OP is specifically asking for an implementation of an identical interface. The solution I'm about to describe relies on adding a generic type parameter to your interface. The problem is that you don't have any real use for the type parameter other than service collection binding. I'll try to describe a situation which might require something like this.

    Imagine configuration for such a scenario in appsettings.json which might look something like this (this is just for demonstration, your configuration can come from wherever you want as long as you have the correction configuration provider):

    {
      "sqlDataSource": {
        "connectionString": "Data Source=localhost; Initial catalog=Foo; Connection Timeout=5; Encrypt=True;",
        "username": "foo",
        "password": "this normally comes from a secure source, but putting here for demonstration purposes"
      },
      "mongoDataSource": {
        "hostName": "uw1-mngo01-cl08.company.net",
        "port": 27026,
        "collection": "foo"
      }
    }
    

    You really need a type that represents each of your configuration options:

    public class SqlDataSource
    {
      public string ConnectionString { get;set; }
      public string Username { get;set; }
      public string Password { get;set; }
    }
    
    public class MongoDataSource
    {
      public string HostName { get;set; }
      public string Port { get;set; }
      public string Collection { get;set; }
    }
    

    Now, I know that it might seem a little contrived to have two implementations of the same interface, but it I've definitely seen it in more than one case. The ones I usually come across are:

    1. When migrating from one data store to another, it's useful to be able to implement the same logical operations using the same interfaces so that you don't need to change the calling code. This also allows you to add configuration which swaps between different implementations at runtime (which can be useful for rollback).
    2. When using the decorator pattern. The reason you might use that pattern is that you want to add functionality without changing the interface and fall back to the existing functionality in certain cases (I've used it when adding caching to repository classes because I want circuit breaker-like logic around connections to the cache that fall back to the base repository -- this gives me optimal behavior when the cache is available, but behavior that still functions when it's not).

    Anyway, you can reference them by adding a type parameter to your service interface so that you can implement the different implementations:

    public interface IService {
      void DoServiceOperation();
    }
    
    public class MongoService : IService {
      private readonly MongoDataSource _options;
    
      public FooService(IOptionsMonitor serviceOptions){
        _options = serviceOptions.CurrentValue
      }
    
      void DoServiceOperation(){
        //do something with your mongo data source options (connect to database)
        throw new NotImplementedException();
      }
    }
    
    public class SqlService : IService {
      private readonly SqlDataSource_options;
    
      public SqlService (IOptionsMonitor serviceOptions){
        _options = serviceOptions.CurrentValue
      }
    
      void DoServiceOperation(){
        //do something with your sql data source options (connect to database)
        throw new NotImplementedException();
      }
    }
    

    In startup, you'd register these with the following code:

    services.Configure(configurationSection.GetSection("sqlDataSource"));
    services.Configure(configurationSection.GetSection("mongoDataSource"));
    
    services.AddTransient, SqlService>();
    services.AddTransient, MongoService>();
    

    Finally in the class which relies on the Service with a different connection, you just take a dependency on the service you need and the DI framework will take care of the rest:

    [Route("api/v1)]
    [ApiController]
    public class ControllerWhichNeedsMongoService {  
      private readonly IService _mongoService;
      private readonly IService _sqlService ;
    
      public class ControllerWhichNeedsMongoService(
        IService mongoService, 
        IService sqlService
      )
      {
        _mongoService = mongoService;
        _sqlService = sqlService;
      }
    
      [HttpGet]
      [Route("demo")]
      public async Task GetStuff()
      {
        if(useMongo)
        {
           await _mongoService.DoServiceOperation();
        }
        await _sqlService.DoServiceOperation();
      }
    }
    

    These implementations can even take a dependency on each other. The other big benefit is that you get compile-time binding so any refactoring tools will work correctly.

    Hope this helps someone in the future.

提交回复
热议问题