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

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

    Most of the answers here violate the single responsibility principle (a service class should not resolve dependencies itself) and/or use the service locator anti-pattern.

    Another option to avoid these problems is to:

    • use an additional generic type parameter on the interface or a new interface implementing the non generic interface,
    • implement an adapter/interceptor class to add the marker type and then
    • use the generic type as “name”

    I’ve written an article with more details: Dependency Injection in .NET: A way to work around missing named registrations

    0 讨论(0)
  • 2020-11-22 10:48

    I have run into the same problem and I worked with a simple extension to allow Named services. You can find it here:

    • https://www.nuget.org/packages/Subgurim.Microsoft.Extensions.DependencyInjection.Named/
    • https://github.com/subgurim/Microsoft.Extensions.DependencyInjection.Named

    It allows you to add as many (named) services as you want like this:

     var serviceCollection = new ServiceCollection();
     serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
     serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);
    
     var serviceProvider = serviceCollection.BuildServiceProvider();
    
     var myServiceA = serviceProvider.GetService<IMyService>("A");
     var myServiceB = serviceProvider.GetService<IMyService>("B");
    

    The library also allows you to easy implement a "factory pattern" like this:

        [Test]
        public void FactoryPatternTest()
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
            serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);
    
            serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();
    
            var serviceProvider = serviceCollection.BuildServiceProvider();
    
            var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();
    
            var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
            Assert.NotNull(myServiceA);
            Assert.IsInstanceOf<MyServiceA>(myServiceA);
    
            var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
            Assert.NotNull(myServiceB);
            Assert.IsInstanceOf<MyServiceB>(myServiceB);
        }
    
        public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
        {
        }
    
        public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
        {
            public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
            : base(serviceProvider)
            {
            }
        }
    
        public enum MyEnum
        {
            A = 1,
            B = 2
        }
    

    Hope it helps

    0 讨论(0)
  • 2020-11-22 10:51

    You're correct, the built in ASP.NET Core container does not have the concept of registering multiple services and then retrieving a specific one, as you suggest, a factory is the only real solution in that case.

    Alternatively, you could switch to a third party container like Unity or StructureMap that does provide the solution you need (documented here: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container).

    0 讨论(0)
  • 2020-11-22 10:51

    Extending the solution of @rnrneverdies. Instead of ToString(), following options can also be used- 1) With common property implementation, 2) A service of services suggested by @Craig Brunetti.

    public interface IService { }
    public class ServiceA : IService
    {
        public override string ToString()
        {
            return "A";
        }
    }
    
    public class ServiceB : IService
    {
        public override string ToString()
        {
            return "B";
        }
    
    }
    
    /// <summary>
    /// extension method that compares with ToString value of an object and returns an object if found
    /// </summary>
    public static class ServiceProviderServiceExtensions
    {
        public static T GetService<T>(this IServiceProvider provider, string identifier)
        {
            var services = provider.GetServices<T>();
            var service = services.FirstOrDefault(o => o.ToString() == identifier);
            return service;
        }
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        //Initials configurations....
    
        services.AddSingleton<IService, ServiceA>();
        services.AddSingleton<IService, ServiceB>();
        services.AddSingleton<IService, ServiceC>();
    
        var sp = services.BuildServiceProvider();
        var a = sp.GetService<IService>("A"); //returns instance of ServiceA
        var b = sp.GetService<IService>("B"); //returns instance of ServiceB
    
        //Remaining configurations....
    }
    
    0 讨论(0)
  • 2020-11-22 10:52

    I just simply inject an IEnumerable

    ConfigureServices in Startup.cs

    Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                    {
                        services.AddScoped(typeof(IService), t);
                    });
    

    Services Folder

    public interface IService
    {
        string Name { get; set; }
    }
    
    public class ServiceA : IService
    {
        public string Name { get { return "A"; } }
    }
    
    public class ServiceB : IService
    {    
        public string Name { get { return "B"; } }
    }
    
    public class ServiceC : IService
    {    
        public string Name { get { return "C"; } }
    }
    

    MyController.cs

    public class MyController
    {
        private readonly IEnumerable<IService> _services;
        public MyController(IEnumerable<IService> services)
        {
            _services = services;
        }
        public void DoSomething()
        {
            var service = _services.Where(s => s.Name == "A").Single();
        }
    ...
    }
    

    Extensions.cs

        public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
        {
            return assembly.GetTypesAssignableFrom(typeof(T));
        }
        public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
        {
            List<Type> ret = new List<Type>();
            foreach (var type in assembly.DefinedTypes)
            {
                if (compareType.IsAssignableFrom(type) && compareType != type)
                {
                    ret.Add(type);
                }
            }
            return ret;
        }
    
    0 讨论(0)
  • 2020-11-22 10:52

    After reading the answers here and articles elsewhere I was able to get it working without strings. When you have multiple implementations of the same interface the DI will add these to a collection, so it's then possible to retrieve the version you want from the collection using typeof.

    // In Startup.cs
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped(IService, ServiceA);
        services.AddScoped(IService, ServiceB);
        services.AddScoped(IService, ServiceC);
    }
    
    // Any class that uses the service(s)
    public class Consumer
    {
        private readonly IEnumerable<IService> _myServices;
    
        public Consumer(IEnumerable<IService> myServices)
        {
            _myServices = myServices;
        }
    
        public UseServiceA()
        {
            var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
            serviceA.DoTheThing();
        }
    
        public UseServiceB()
        {
            var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
            serviceB.DoTheThing();
        }
    
        public UseServiceC()
        {
            var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
            serviceC.DoTheThing();
        }
    }
    
    0 讨论(0)
提交回复
热议问题