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

前端 未结 24 1523
不思量自难忘°
不思量自难忘° 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 10:55

    I've faced the same issue and want to share how I solved it and why.

    As you mentioned there are two problems. The first:

    In Asp.Net Core how do I register these services and resolve it at runtime based on some key?

    So what options do we have? Folks suggest two:

    • Use a custom factory (like _myFactory.GetServiceByKey(key))

    • Use another DI engine (like _unityContainer.Resolve(key))

    Is the Factory pattern the only option here?

    In fact both options are factories because each IoC Container is also a factory (highly configurable and complicated though). And it seems to me that other options are also variations of the Factory pattern.

    So what option is better then? Here I agree with @Sock who suggested using custom factory, and that is why.

    First, I always try to avoid adding new dependencies when they are not really needed. So I agree with you in this point. Moreover, using two DI frameworks is worse than creating custom factory abstraction. In the second case you have to add new package dependency (like Unity) but depending on a new factory interface is less evil here. The main idea of ASP.NET Core DI, I believe, is simplicity. It maintains a minimal set of features following KISS principle. If you need some extra feature then DIY or use a corresponding Plungin that implements desired feature (Open Closed Principle).

    Secondly, often we need to inject many named dependencies for single service. In case of Unity you may have to specify names for constructor parameters (using InjectionConstructor). This registration uses reflection and some smart logic to guess arguments for the constructor. This also may lead to runtime errors if registration does not match the constructor arguments. From the other hand, when using your own factory you have full control of how to provide the constructor parameters. It's more readable and it's resolved at compile-time. KISS principle again.

    The second problem:

    How can _serviceProvider.GetService() inject appropriate connection string?

    First, I agree with you that depending on new things like IOptions (and therefore on package Microsoft.Extensions.Options.ConfigurationExtensions) is not a good idea. I've seen some discussing about IOptions where there were different opinions about its benifit. Again, I try to avoid adding new dependencies when they are not really needed. Is it really needed? I think no. Otherwise each implementation would have to depend on it without any clear need coming from that implementation (for me it looks like violation of ISP, where I agree with you too). This is also true about depending on the factory but in this case it can be avoided.

    The ASP.NET Core DI provides a very nice overload for that purpose:

    var mongoConnection = //...
    var efConnection = //...
    var otherConnection = //...
    services.AddTransient(
                 s => new MyFactoryImpl(
                     mongoConnection, efConnection, otherConnection, 
                     s.GetService(), s.GetService())));
    

提交回复
热议问题