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

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

    Bit late to this party, but here is my solution:...

    Startup.cs or Program.cs if Generic Handler...

    services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
    services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
    

    IMyInterface of T Interface Setup

    public interface IMyInterface<T> where T : class, IMyInterface<T>
    {
        Task Consume();
    }
    

    Concrete implementations of IMyInterface of T

    public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
    {
        public async Task Consume();
    }
    
    public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
    {
        public async Task Consume();
    }
    

    Hopefully if there is any issue with doing it this way, someone will kindly point out why this is the wrong way to do this.

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

    Necromancing.
    I think people here are reinventing the wheel - and badly, if I may say so ...
    If you want to register a component by key, just use a dictionary:

    System.Collections.Generic.Dictionary<string, IConnectionFactory> dict = 
        new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
            System.StringComparer.OrdinalIgnoreCase);
    
    dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
    dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
    dict.Add("TestDB", new ConnectionFactory("connectionString3"));
    dict.Add("Analytics", new ConnectionFactory("connectionString4"));
    dict.Add("LogDB", new ConnectionFactory("connectionString5"));
    

    And then register the dictionary with the service-collection:

    services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);
    

    if you then are unwilling to get the dictionary and access it by key, you can hide the dictionary by adding an additional key-lookup-method to the service-collection:
    (the use of delegate/closure should give a prospective maintainer a chance at understanding what's going on - the arrow-notation is a bit cryptic)

    services.AddTransient<Func<string, IConnectionFactory>>(
        delegate (IServiceProvider sp)
        {
            return
                delegate (string key)
                {
                    System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
     <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);
    
                    if (dbs.ContainsKey(key))
                        return dbs[key];
    
                    throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
                };
        });
    

    Now you can access your types with either

    IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
    logDB.Connection
    

    or

    System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
    dbs["logDB"].Connection
    

    As we can see, the first one is just completely superfluous, because you can also do exactly that with a dictionary, without requiring closures and AddTransient (and if you use VB, not even the braces will be different):

    IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
    logDB.Connection
    

    (simpler is better - you might want to use it as extension method though)

    Of course, if you don't like the dictionary, you can also outfit your interface with a property Name (or whatever), and look that up by key:

    services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
    services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
    services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
    services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
    services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));
    
    
    
    // https://stackoverflow.com/questions/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
    services.AddTransient<Func<string, IConnectionFactory>>(
        delegate(IServiceProvider sp)
        {
            return
                delegate(string key)
                {
                    System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                        sp.GetServices<IConnectionFactory>();
    
                    foreach (IConnectionFactory thisService in svs)
                    {
                        if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                            return thisService;
                    }
    
                    return null;
                };
        });
    

    But that requires changing your interface to accommodate the property, and looping through a lot of elements should be much slower than an associative-array lookup (dictionary).
    It's nice to know that it can be done without dicionary, though.

    These are just my $0.05

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

    While it seems @Miguel A. Arilla has pointed it out clearly and I voted up for him, I created on top of his useful solution another solution which looks neat but requires a lot more work.

    It definitely depends on the above solution. So basically I created something similar to Func<string, IService>> and I called it IServiceAccessor as an interface and then I had to add a some more extensions to the IServiceCollection as such:

    public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
                this IServiceCollection services,
                string instanceName
            )
                where TService : class
                where TImplementation : class, TService
                where TServiceAccessor : class, IServiceAccessor<TService>
            {
                services.AddSingleton<TService, TImplementation>();
                services.AddSingleton<TServiceAccessor>();
                var provider = services.BuildServiceProvider();
                var implementationInstance = provider.GetServices<TService>().Last();
                var accessor = provider.GetServices<TServiceAccessor>().First();
    
                var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
                while (serviceDescriptors.Any())
                {
                    services.Remove(serviceDescriptors.First());
                }
    
                accessor.SetService(implementationInstance, instanceName);
                services.AddSingleton<TServiceAccessor>(prvd => accessor);
                return services;
            }
    

    The service Accessor looks like:

     public interface IServiceAccessor<TService>
        {
             void Register(TService service,string name);
             TService Resolve(string name);
    
        }
    

    The end result,you will be able to register services with names or named instances like we used to do with other containers..for instance:

        services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
        services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");
    

    That is enough for now, but to make your work complete, it is better to add more extension methods as you can to cover all types of registrations following the same approach.

    There was another post on stackoverflow, but I can not find it, where the poster has explained in details why this feature is not supported and how to work around it, basically similar to what @Miguel stated. It was nice post even though I do not agree with each point because I think there are situation where you really need named instances. I will post that link here once I find it again.

    As a matter of fact, you do not need to pass that Selector or Accessor:

    I am using the following code in my project and it worked well so far.

     /// <summary>
        /// Adds the singleton.
        /// </summary>
        /// <typeparam name="TService">The type of the t service.</typeparam>
        /// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
        /// <param name="services">The services.</param>
        /// <param name="instanceName">Name of the instance.</param>
        /// <returns>IServiceCollection.</returns>
        public static IServiceCollection AddSingleton<TService, TImplementation>(
            this IServiceCollection services,
            string instanceName
        )
            where TService : class
            where TImplementation : class, TService
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = provider.GetServices<TService>().LastOrDefault();
            if (implementationInstance.IsNull())
            {
                services.AddSingleton<TService, TImplementation>();
                provider = services.BuildServiceProvider();
                implementationInstance = provider.GetServices<TService>().Single();
            }
            return services.RegisterInternal(instanceName, provider, implementationInstance);
        }
    
        private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
            string instanceName, ServiceProvider provider, TService implementationInstance)
            where TService : class
        {
            var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
            if (accessor.IsNull())
            {
                services.AddSingleton<ServiceAccessor<TService>>();
                provider = services.BuildServiceProvider();
                accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
            }
            else
            {
                var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
                while (serviceDescriptors.Any())
                {
                    services.Remove(serviceDescriptors.First());
                }
            }
            accessor.Register(implementationInstance, instanceName);
            services.AddSingleton<TService>(prvd => implementationInstance);
            services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
            return services;
        }
    
        //
        // Summary:
        //     Adds a singleton service of the type specified in TService with an instance specified
        //     in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
        //
        // Parameters:
        //   services:
        //     The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
        //     to.
        //   implementationInstance:
        //     The instance of the service.
        //   instanceName:
        //     The name of the instance.
        //
        // Returns:
        //     A reference to this instance after the operation has completed.
        public static IServiceCollection AddSingleton<TService>(
            this IServiceCollection services,
            TService implementationInstance,
            string instanceName) where TService : class
        {
            var provider = services.BuildServiceProvider();
            return RegisterInternal(services, instanceName, provider, implementationInstance);
        }
    
        /// <summary>
        /// Registers an interface for a class
        /// </summary>
        /// <typeparam name="TInterface">The type of the t interface.</typeparam>
        /// <param name="services">The services.</param>
        /// <returns>IServiceCollection.</returns>
        public static IServiceCollection As<TInterface>(this IServiceCollection services)
             where TInterface : class
        {
            var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
            if (descriptor.IsNotNull())
            {
                var provider = services.BuildServiceProvider();
                var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
                services?.AddSingleton(implementationInstance);
            }
            return services;
        }
    
    0 讨论(0)
  • 2020-11-22 10:44

    I have created a library for this that implements some nice features. Code can be found on GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

    Usage is straightforward:

    1. Add the Dazinator.Extensions.DependencyInjection nuget package to your project.
    2. Add your Named Service registrations.
        var services = new ServiceCollection();
        services.AddNamed<AnimalService>(names =>
        {
            names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
            names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
            names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
            names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).
    
            names.AddTransient("E"); // new AnimalService() every time..
            names.AddTransient<LionService>("F"); // new LionService() every time..
    
            names.AddScoped("G");  // scoped AnimalService
            names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.
    
        });
    
    
    

    In the example above, notice that for each named registration, you are also specifying the lifetime or Singleton, Scoped, or Transient.

    You can resolve services in one of two ways, depending on if you are comfortable with having your services take a dependency on this package of not:

    public MyController(Func<string, AnimalService> namedServices)
    {
       AnimalService serviceA = namedServices("A");
       AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
    }
    

    or

    public MyController(NamedServiceResolver<AnimalService> namedServices)
    {
       AnimalService serviceA = namedServices["A"];
       AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
    }
    
    

    I have specifically designed this library to work well with Microsoft.Extensions.DependencyInjection - for example:

    1. When you register named services, any types that you register can have constructors with parameters - they will be satisfied via DI, in the same way that AddTransient<>, AddScoped<> and AddSingleton<> methods work ordinarily.

    2. For transient and scoped named services, the registry builds an ObjectFactory so that it can activate new instances of the type very quickly when needed. This is much faster than other approaches and is in line with how Microsoft.Extensions.DependencyInjection does things.

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

    Why not use inheritance? This way we can have as many copies of the interface as we want and we can pick suitable names for each of them . And we have a benefit of type safety

    public interface IReportGenerator
    public interface IExcelReportGenerator : IReportGenerator
    public interface IPdfReportGenerator : IReportGenerator
    

    Concrete classes:

    public class ExcelReportGenerator : IExcelReportGenerator
    public class PdfReportGenerator : IPdfReportGenerator
    

    Register:

    instead of

    services.AddScoped<IReportGenerator, PdfReportGenerator>();
    services.AddScoped<IReportGenerator, ExcelReportGenerator>();
    

    we have :

    services.AddScoped<IPdfReportGenerator, PdfReportGenerator>();
    services.AddScoped<IExcelReportGenerator, ExcelReportGenerator>();
    

    Client:

    public class ReportManager : IReportManager
    {
        private readonly IExcelReportGenerator excelReportGenerator;
        private readonly IPdfReportGenerator pdfReportGenerator;
    
        public ReportManager(IExcelReportGenerator excelReportGenerator, 
                             IPdfReportGenerator pdfReportGenerator)
        {
            this.excelReportGenerator = excelReportGenerator;
            this.pdfReportGenerator = pdfReportGenerator;
        }
    

    this approach also allows for louse coupled code, because we can move IReportGenerator to the core of the application and have child interfaces that will be declared at higher levels.

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

    While the out of the box implementation doesn't offer it, here's a sample project that allows you to register named instances, and then inject INamedServiceFactory into your code and pull out instances by name. Unlike other facory solutions here, it will allow you to register multiple instances of same implementation but configured differently

    https://github.com/macsux/DotNetDINamedInstances

    0 讨论(0)
提交回复
热议问题