¿Cómo registrar múltiples implementaciones de la misma interfaz en Asp.Net Core?

240

Tengo servicios que se derivan de la misma interfaz.

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

Por lo general, otros contenedores IoC como le Unitypermiten registrar implementaciones concretas por parte de algunos Keyque los distinguen.

En ASP.NET Core, ¿cómo registro estos servicios y los resuelvo en tiempo de ejecución en función de alguna clave?

No veo ningún Addmétodo de servicio que tome un parámetro keyo name, que normalmente se usaría para distinguir la implementación concreta.

    public void ConfigureServices(IServiceCollection services)
    {            
         // How do I register services of the same interface?            
    }


    public MyController:Controller
    {
       public void DoSomething(string key)
       { 
          // How do I resolve the service by key?
       }
    }

¿Es el patrón Factory la única opción aquí?

Actualización1
He leído el artículo aquí que muestra cómo usar el patrón de fábrica para obtener instancias de servicio cuando tenemos múltiples implementaciones concretas. Sin embargo, todavía no es una solución completa. Cuando llamo al _serviceProvider.GetService()método, no puedo inyectar datos en el constructor.

Por ejemplo considere esto:

public class ServiceA : IService
{
     private string _efConnectionString;
     ServiceA(string efconnectionString)
     {
       _efConnecttionString = efConnectionString;
     } 
}

public class ServiceB : IService
{    
   private string _mongoConnectionString;
   public ServiceB(string mongoConnectionString)
   {
      _mongoConnectionString = mongoConnectionString;
   }
}

public class ServiceC : IService
{    
    private string _someOtherConnectionString
    public ServiceC(string someOtherConnectionString)
    {
      _someOtherConnectionString = someOtherConnectionString;
    }
}

¿Cómo se puede _serviceProvider.GetService()inyectar la cadena de conexión adecuada? En Unity, o en cualquier otra biblioteca de IoC, podemos hacerlo al registrar el tipo. Sin embargo, puedo usar IOption , que requerirá que inyecte todas las configuraciones. No puedo inyectar una cadena de conexión particular en el servicio.

También tenga en cuenta que estoy tratando de evitar el uso de otros contenedores (incluido Unity) porque entonces también tengo que registrar todo lo demás (por ejemplo, Controladores) con el nuevo contenedor.

Además, el uso del patrón de fábrica para crear instancias de servicio va en contra de DIP, ya que aumenta el número de dependencias que un cliente tiene detalles aquí .

Por lo tanto, creo que el DI predeterminado en ASP.NET Core falta dos cosas:

  1. La capacidad de registrar instancias usando una clave
  2. La capacidad de inyectar datos estáticos en constructores durante el registro
LP13
fuente
44
Posible duplicado de la inyección
adem caglin
2
Hay, finalmente, una extensión en Nuget para los registros basados en nombres, espero que pueda ayudar
Neleo
Hola, perdón por mi estúpida pregunta, pero soy nuevo con Microsoft.Extensions.DependencyInjection ... ¿crees que crear 3 interfaces vacías que extienden Iservice como "public interface IServiceA: IService" y que "public class ServiceA: IServiceA "... podría ser una buena práctica?
Emiliano Magliocca
¿Es útil este artículo? stevejgordon.co.uk/…
Mike B
Se Update1puede mover a una pregunta diferente, ya que inyectar cosas en los constructores es muy diferente de calcular qué objeto construir
Neil

Respuestas:

246

Hice una solución simple usando Funccuando me encontré en esta situación.

Primero declare un delegado compartido:

public delegate IService ServiceResolver(string key);

Luego, en su Startup.cs, configure los múltiples registros concretos y una asignación manual de esos tipos:

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

Y úselo de cualquier clase registrada con DI:

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

Tenga en cuenta que en este ejemplo la clave para la resolución es una cadena, en aras de la simplicidad y porque OP estaba pidiendo este caso en particular.

Pero podría usar cualquier tipo de resolución personalizada como clave, ya que generalmente no desea que un gran interruptor de n-case pudra su código. Depende de cómo se escala tu aplicación.

Miguel A. Arilla
fuente
1
@MatthewStevenMonkan actualizó mi respuesta con un ejemplo
Miguel A. Arilla
2
Usar un patrón de fábrica como este es la mejor manera de hacerlo. ¡Gracias por compartir!
Sergey Akopov
2
+1 Muy limpio y ordenado, porque cuando usamos otro contenedor, tenemos que incluir su paquete siempre que necesitemos resolver dependencias, por ejemplo. ILifetimeScope en AutoFac.
Anupam Singh
1
@AnupamSingh En mi opinión, la mayoría de las aplicaciones pequeñas a medianas que se ejecutan en .NET Core no necesitan ningún marco DI, solo agrega complejidad y dependencias no deseadas, la belleza y simplicidad del DI incorporado es más que suficiente, y puede También se ampliará con facilidad.
Miguel A. Arilla
8
Explicación de voto negativo: es muy interesante, pero actualmente estoy refactorizando una base de código masiva para eliminar toda esta magia Func que alguien hizo hace unos años (antes de la revolución MS DI) El problema con esto es que aumenta drásticamente la complejidad de connascencia en las propiedades que puede causar una resolución DI complicada más adelante en la línea. Por ejemplo, trabajé en un controlador de servicio de Windows que tenía más de 1,6 mil líneas de código que hacer con Func y, después de hacerlo, la forma recomendada de DI lo reduje a 0,2 mil líneas. OK-Las líneas de código no significan nada ... excepto que es más fácil de leer y reutilizar ahora ...
Piotr Kula
79

Otra opción es utilizar el método de extensión GetServicesdesde Microsoft.Extensions.DependencyInjection.

Registre sus servicios como:

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();

Luego resuelve con un poco de Linq:

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));

o

var serviceZ = services.First(o => o.Name.Equals("Z"));

(suponiendo que IServicetenga una propiedad de cadena llamada "Nombre")

Asegúrate de tener using Microsoft.Extensions.DependencyInjection;

Actualizar

Fuente AspNet 2.1: GetServices

rnrneverdies
fuente
66
No estoy seguro, pero creo que no es determinista. Cualquier resultado que obtenga hoy puede cambiar mañana, no parece una buena práctica.
rnrneverdies
44
Voté al enlace de GetServices, que me mostró que puede solicitar una lista de servicios de un servicio dependiente solicitandoIEnumerable<IService>
johnny 5
20
serviceProvider.GetServices <IService> () instanciará cada uno de ServiceA, ServiceB y ServiceC. Desea llamar al constructor de un solo servicio, el que realmente necesita. Este es un gran problema si las implementaciones no son livianas o si tiene muchas implementaciones de IService (por ejemplo, tiene implementaciones autogeneradas de IRepository para cada modelo).
Uros
66
Estoy de acuerdo con @Uros. Esta no es una buena solución. Imagine lo que sucede si registra 10 implementaciones de IService y la instancia que realmente necesita es la última. En este caso, DI crea realmente 9 instancias, que nunca se utilizan.
thomai
44
Mala idea: Múltiples instancias no utilizadas, anti patrón del localizador de servicios y acoplamiento directo a la implementación real (tipo de <ServicioA>).
Rico Suter
20

No es compatible con Microsoft.Extensions.DependencyInjection.

Pero puede conectar otro mecanismo de inyección de dependencia, como StructureMap Ver es la página de inicio y es el Proyecto GitHub .

No es difícil en absoluto:

  1. Agregue una dependencia a StructureMap en su project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Inyecte en la tubería de ASP.NET dentro ConfigureServicesy registre sus clases (ver documentos)

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
    
  3. Luego, para obtener una instancia con nombre, deberá solicitar el IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"
    

Eso es.

Para el ejemplo para construir, necesitas

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }
Gerardo Grignoli
fuente
He intentado este enfoque, pero tengo errores de tiempo de ejecución en mi controlador porque IContainer no se encuentra en los planes de compilación. ¿Hay algo que deba exigir para que IContainer se inyecte automáticamente?
mohrtan
Por cierto, estoy usando StructureMap.Micorosoft.DependencyInjection 1.3.0.
mohrtan
¿Devuelve el nuevo contenedor en ConfigureServices?
Gerardo Grignoli
Estoy devolviendo la IServiceProviderInstance del nuevo contenedor como se indica en el paso 2 anterior. Copié eso exactamente solo cambiándolo para mis tipos. Esta es una buena solución y está funcionando perfectamente. El único inconveniente es que no puedo usar un contenedor inyectado y recurro a un contenedor estático, lo que no quiero hacer.
mohrtan
1
Funciona para mí gracias GerardoGrignoli. @mohrtan el código de muestra está aquí si todavía está investigando esto. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza
13

Me he enfrentado al mismo problema y quiero compartir cómo lo resolví y por qué.

Como mencionaste hay dos problemas. El primero:

En Asp.Net Core, ¿cómo registro estos servicios y los resuelvo en tiempo de ejecución en función de alguna clave?

Entonces, ¿qué opciones tenemos? La gente sugiere dos:

  • Use una fábrica personalizada (como _myFactory.GetServiceByKey(key))

  • Use otro motor DI (como _unityContainer.Resolve<IService>(key))

¿Es el patrón Factory la única opción aquí?

De hecho, ambas opciones son fábricas porque cada IoC Container también es una fábrica (aunque altamente configurable y complicado). Y me parece que otras opciones también son variaciones del patrón Factory.

Entonces, ¿qué opción es mejor entonces? Aquí estoy de acuerdo con @Sock que sugirió usar la fábrica personalizada, y es por eso.

Primero, siempre trato de evitar agregar nuevas dependencias cuando realmente no son necesarias. Así que estoy de acuerdo contigo en este punto. Además, usar dos marcos DI es peor que crear una abstracción de fábrica personalizada. En el segundo caso, debe agregar una nueva dependencia de paquete (como Unity), pero depender de una nueva interfaz de fábrica es menos mal aquí. La idea principal de ASP.NET Core DI, creo, es la simplicidad. Mantiene un conjunto mínimo de características siguiendo el principio KISS . Si necesita alguna función adicional, hágalo usted mismo o use un Plungin correspondiente que implemente la función deseada (Principio de cierre cerrado).

En segundo lugar, a menudo necesitamos inyectar muchas dependencias con nombre para un solo servicio. En el caso de Unity, es posible que deba especificar nombres para los parámetros del constructor (usando InjectionConstructor). Este registro utiliza reflexión y lógica inteligente para adivinar argumentos para el constructor. Esto también puede provocar errores de tiempo de ejecución si el registro no coincide con los argumentos del constructor. Por otro lado, cuando usa su propia fábrica, tiene control total sobre cómo proporcionar los parámetros del constructor. Es más legible y se resuelve en tiempo de compilación. Principio de KISS nuevamente.

El segundo problema:

¿Cómo puede _serviceProvider.GetService () inyectar la cadena de conexión adecuada?

Primero, estoy de acuerdo con usted en que depender de cosas nuevas como IOptions(y, por lo tanto, del paquete Microsoft.Extensions.Options.ConfigurationExtensions) no es una buena idea. He visto algunas discusiones sobre IOptionsdónde había diferentes opiniones sobre su beneficio. Nuevamente, trato de evitar agregar nuevas dependencias cuando realmente no son necesarias. ¿Es realmente necesario? Creo que no. De lo contrario, cada implementación tendría que depender de ella sin ninguna necesidad clara de esa implementación (para mí parece una violación del ISP, donde también estoy de acuerdo con usted). Esto también es cierto en función de la fábrica, pero en este caso se puede evitar.

ASP.NET Core DI proporciona una sobrecarga muy agradable para ese propósito:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
neleus
fuente
Hola, perdón por mi estúpida pregunta, pero soy nuevo con Microsoft.Extensions.DependencyInjection ... ¿crees que creas 3 interfaces que extienden Iservice como "public interface IServiceA: IService" y que "public class ServiceA: IServiceA" ... podría ser una buena práctica?
Emiliano Magliocca
1
@ emiliano-magliocca En general, no debe depender de las interfaces que no utiliza (ISP), IServiceAen su caso. Como está utilizando métodos de IServicesolo, debe tener dependencia de IServicesolo.
neleus
1
@ cagatay-kalan En caso de pregunta de OP, puede lograr fácilmente su objetivo con ASP.NET Core DI. No hay necesidad de otros marcos DI.
neleus
1
@EmilianoMagliocca Se puede resolver fácilmente de esta manera: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));para la primera clase y services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));para la segunda.
neleus
1
@EmilianoMagliocca en mi ejemplo, tanto 'MyFirstClass' como 'MySecondClass' tienen el mismo parámetro ctor de tipo de interfaz que implementan Escpos y Usbpos. Por lo tanto, el código anterior solo indica al contenedor IoC cómo instanciar 'MyFirstClass' y 'MySecondClass'. Nada mas. Por lo tanto, es posible que deba asignar otras interfaces a 'MyFirstClass' y 'MySecondClass'. Depende de sus necesidades y no lo cubrí en mi ejemplo.
neleus
13

Simplemente inyecto un IEnumerable

ConfigureServices en Startup.cs

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

Carpeta de servicios

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();
    }
...
}

Extensiones.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;
    }
T Brown
fuente
En el método DoSomething () del controlador, puede usar typeof para resolver el servicio que desea: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen
Literalmente intenté todo, y esta es la única solución que funcionó para mí. ¡Gracias!
Skatz1990
@ Skatz1990 Pruebe la solución que creé a continuación en otra publicación. Creo que es más limpio y fácil de usar.
T Brown
12

La mayoría de las respuestas aquí violan el principio de responsabilidad única (una clase de servicio no debe resolver las dependencias por sí misma) y / o usan el antipatrón del localizador de servicios.

Otra opción para evitar estos problemas es:

  • use un parámetro de tipo genérico adicional en la interfaz o una nueva interfaz que implemente la interfaz no genérica,
  • implementar una clase de adaptador / interceptor para agregar el tipo de marcador y luego
  • use el tipo genérico como "nombre"

He escrito un artículo con más detalles: Inyección de dependencias en .NET: una forma de evitar los registros con nombres faltantes

Rico Suter
fuente
¿Cómo la respuesta aceptada viola el principio de responsabilidad única?
LP13
Vea los comentarios de stackoverflow.com/a/52066039/876814 y también en la respuesta aceptada, el servicio se resuelve de manera perezosa, es decir, solo sabe si falla en tiempo de ejecución y no hay forma de verificarlo estáticamente en el inicio después de la compilación del contenedor (similar a la respuesta en el comentario) SRP porque el servicio no solo es responsable de su lógica comercial sino también de la resolución de la dependencia
Rico Suter
@RicoSuter Realmente me gusta la solución en su blog, pero estoy confundido por su DI dentro de la clase Startup. Específicamente, no entiendo la línea MessagePublisher ("MyOrderCreatedQueue") ya que no veo un constructor con esa firma. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (nuevo MessagePublisher <OrderCreatedMessage> (nuevo MessagePublisher ("MyOrderCreatedQueue")));
Lee Z
Gracias, actualicé el artículo y utilicé MyMessagePublisher como una implementación de muestra de IMessagePublisher
Rico Suter
7

Un poco tarde para esta fiesta, pero aquí está mi solución: ...

Startup.cs o Program.cs si el controlador genérico ...

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

Interfaz IMy de la configuración de la interfaz T

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

Implementaciones concretas de IMyInterface de T

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

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

Con suerte, si hay algún problema al hacerlo de esta manera, alguien amablemente señalará por qué esta es la forma incorrecta de hacerlo.

gris
fuente
3
IMyInterface<CustomerSavedConsumer>y IMyInterface<ManagerSavedConsumer>son diferentes tipos de servicios: esto no responde a la pregunta de los OP en absoluto.
Richard Hauer
2
El OP quería una forma de registrar múltiples implementaciones de la misma interfaz en el núcleo de Asp.net. Si no hice esto, explique cómo (exactamente).
Gray
1
Si bien tienes razón, este patrón permite el efecto que el operador quería. Al menos cuando intentaba hacer esto yo mismo, me topé con esta publicación y mi solución funcionó mejor para mi situación.
Gray
1
Espero que el problema sea más que registrar múltiples implementaciones para una sola interfaz (usando MS DI) no permite que el contenedor distinga una implementación de otra. En otras DI, puede introducirlas para que el contenedor sepa cuál elegir. En la EM que tiene que utilizar un delegado y elegir manualmente. Su solución no aborda este escenario ya que sus interfaces son diferentes, por lo que el contenedor no tiene problemas para elegir la implementación correcta. Si bien su muestra obviamente funciona, no es una solución para el problema como se indicó.
Richard Hauer
3
@ Gray A pesar de que su publicación recibió mala prensa, le agradezco por presentar esta solución. Ofrece a los lectores otra opción para superar las limitaciones en .net cores DI. Aunque puede que no responda la pregunta de los OP directamente, proporciona una solución alternativa perfecta, que es de lo que se trata SO, ¿verdad?
Neil Watson el
5

Aparentemente, ¡puedes inyectar IEnumerable de tu interfaz de servicio! Y luego encuentre la instancia que desea usando LINQ.

Mi ejemplo es para el servicio AWS SNS, pero realmente puede hacer lo mismo para cualquier servicio inyectado.

Puesta en marcha

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

Fábrica SNS

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

Ahora puede obtener el servicio SNS para la región que desea en su servicio o controlador personalizado

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}
ArcadeRenegado
fuente
5

Un enfoque de fábrica es ciertamente viable. Otro enfoque es utilizar la herencia para crear interfaces individuales que hereden de IService, implemente las interfaces heredadas en sus implementaciones de IService y registre las interfaces heredadas en lugar de la base. Si agregar una jerarquía de herencia o fábricas es el patrón "correcto" todo depende de con quién hable. A menudo tengo que usar este patrón cuando trato con múltiples proveedores de bases de datos en la misma aplicación que usa un genérico IRepository<T>, como la base para el acceso a datos.

Ejemplos de interfaces e implementaciones:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

Envase:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
jlc397
fuente
5

Nigromancia
Creo que la gente aquí está reinventando la rueda, y mal, si puedo decirlo ...
Si desea registrar un componente por clave, simplemente use un diccionario:

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"));

Y luego registre el diccionario con la colección de servicios:

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

si no está dispuesto a obtener el diccionario y acceder a él por clave, puede ocultar el diccionario agregando un método adicional de búsqueda de claves a la colección de servicios:
(el uso de delegado / cierre debería brindarle la oportunidad a un posible responsable de mantenimiento entender lo que está sucediendo: la notación de flecha es un poco críptica)

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
            };
    });

Ahora puede acceder a sus tipos con cualquiera

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

o

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

Como podemos ver, el primero es completamente superfluo, porque también puede hacer exactamente eso con un diccionario, sin requerir cierres y AddTransient (y si usa VB, ni siquiera las llaves serán diferentes):

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

(más simple es mejor; sin embargo, es posible que desee usarlo como método de extensión)

Por supuesto, si no le gusta el diccionario, también puede equipar su interfaz con una propiedad Name(o lo que sea), y buscarlo por clave:

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"));



// /programming/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;
            };
    });

Pero eso requiere cambiar su interfaz para acomodar la propiedad, y recorrer muchos elementos debería ser mucho más lento que una búsqueda de matriz asociativa (diccionario).
Sin embargo, es bueno saber que se puede hacer sin un diccionario.

Estos son solo mis $ 0.05

Stefan Steiger
fuente
Si el servicio se ha IDisposeimplementado, ¿quién es responsable de deshacerse del servicio? Se ha registrado el diccionario comoSingleton
LP13
@ LP13: También puede registrar el diccionario con un delegado como valor, luego puede registrarlo en transitorio y crear una nueva instancia, por ejemplo. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger
5

desde mi publicación anterior, me mudé a una clase de fábrica genérica

Uso

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

Implementación

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

Extensión

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}
T Brown
fuente
¿Puede proporcionar la extensión del método .AddFactory ()?
desarrollador
Lo siento Acabo de ver esto ... agregado
T Brown
3

Si bien parece que @Miguel A. Arilla lo ha señalado claramente y voté por él, creé, además de su solución útil, otra solución que se ve ordenada pero requiere mucho más trabajo.

Definitivamente depende de la solución anterior. Básicamente, creé algo similar Func<string, IService>>y lo llamé IServiceAccessorcomo una interfaz y luego tuve que agregar algunas extensiones más IServiceCollectioncomo tal:

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;
        }

El servicio Accesor se ve así:

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

    }

El resultado final, podrá registrar servicios con nombres o instancias con nombre como solíamos hacer con otros contenedores ... por ejemplo:

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

Eso es suficiente por ahora, pero para completar su trabajo, es mejor agregar más métodos de extensión como sea posible para cubrir todo tipo de registros siguiendo el mismo enfoque.

Hubo otra publicación en stackoverflow, pero no puedo encontrarla, donde el póster ha explicado en detalle por qué esta función no es compatible y cómo solucionarla, básicamente similar a lo que dijo @Miguel. Fue una buena publicación, aunque no estoy de acuerdo con cada punto porque creo que hay situaciones en las que realmente necesitas instancias con nombre. Publicaré ese enlace aquí una vez que lo encuentre nuevamente.

De hecho, no necesita pasar ese Selector o Accesor:

Estoy usando el siguiente código en mi proyecto y funcionó bien hasta ahora.

 /// <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;
    }
Assil
fuente
1
Esto ayudó a resolver mi problema en el que estaba perdiendo el registro de tipos en el descriptor de acceso al servicio. ¡El truco fue eliminar todos los enlaces para el descriptor de acceso al servicio y luego agregarlo nuevamente!
Umar Farooq Khawaja
3

He creado una biblioteca para esto que implementa algunas características agradables. El código se puede encontrar en GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

El uso es sencillo:

  1. Agregue el paquete Nuget Dazinator.Extensions.DependencyInjection a su proyecto.
  2. Agregue sus registros de servicios nombrados.
    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.

    });

En el ejemplo anterior, observe que para cada registro con nombre, también está especificando la duración o Singleton, Scoped o Transient.

Puede resolver los servicios de una de dos maneras, dependiendo de si se siente cómodo con que sus servicios dependan de este paquete de no:

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

o

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

He diseñado específicamente esta biblioteca para que funcione bien con Microsoft.Extensions.DependencyInjection, por ejemplo:

  1. Cuando registra servicios con nombre, cualquier tipo que registre puede tener constructores con parámetros: se satisfarán a través de DI, de la misma manera que AddTransient<>, AddScoped<>y los AddSingleton<>métodos funcionan normalmente.

  2. Para los servicios con nombre transitorios y con alcance, el registro crea un ObjectFactorymodo para que pueda activar nuevas instancias del tipo muy rápidamente cuando sea necesario. Esto es mucho más rápido que otros enfoques y está en línea con la forma en que Microsoft.Extensions.DependencyInjection hace las cosas.

Darrell
fuente
2

Mi solución para lo que vale ... consideró cambiarme a Castle Windsor, ya que no puedo decir que me gustaron las soluciones anteriores. ¡¡Lo siento!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Crea tus diversas implementaciones

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

Registro

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

Uso del constructor y la instancia ...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
littgle
fuente
1

Extendiendo la solución de @rnrneverdies. En lugar de ToString (), también se pueden usar las siguientes opciones: 1) Con implementación de propiedad común, 2) Un servicio de servicios sugerido por @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....
}
vrluckyin
fuente
1

Después de leer las respuestas aquí y los artículos en otros lugares, pude hacerlo funcionar sin condiciones. Cuando tiene múltiples implementaciones de la misma interfaz, el DI las agregará a una colección, por lo que es posible recuperar la versión que desea de la colección usando 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();
    }
}
Ciaran Bruen
fuente
Derrota el propósito de IoC. También podrías escribir:var serviceA = new ServiceA();
James Curran
2
@JamesCurran no si ServiceA tiene dependencias, o si desea probar la clase de la unidad.
Jorn.Beyers
0

Si bien la implementación lista para usar no lo ofrece, aquí hay un proyecto de muestra que le permite registrar instancias con nombre y luego inyectar INamedServiceFactory en su código y extraer instancias por nombre. A diferencia de otras soluciones de fábrica aquí, le permitirá registrar múltiples instancias de la misma implementación pero configuradas de manera diferente

https://github.com/macsux/DotNetDINamedInstances

Andrew Stakhov
fuente
0

¿Qué tal un servicio para servicios?

Si tuviéramos una interfaz INamedService (con la propiedad .Name), podríamos escribir una extensión IServiceCollection para .GetService (nombre de cadena), donde la extensión tomaría ese parámetro de cadena, y haría un .GetServices () en sí mismo, y en cada uno devuelto instancia, busque la instancia cuyo INamedService.Name coincida con el nombre dado.

Me gusta esto:

public interface INamedService
{
    string Name { get; }
}

public static T GetService<T>(this IServiceProvider provider, string serviceName)
    where T : INamedService
{
    var candidates = provider.GetServices<T>();
    return candidates.FirstOrDefault(s => s.Name == serviceName);
}

Por lo tanto, su IMyService debe implementar INamedService, pero obtendrá la resolución basada en claves que desea, ¿verdad?

Para ser justos, incluso tener que tener esta interfaz INamedService parece feo, pero si quieres ir más allá y hacer las cosas más elegantes, entonces el código de este [...] podría encontrar una [NamedServiceAttribute ("A")] en la implementación / clase. extensión, y funcionaría igual de bien. Para ser aún más justos, Reflection es lento, por lo que puede ser necesaria una optimización, pero sinceramente, eso es algo con lo que el motor DI debería haber estado ayudando. La velocidad y la simplicidad son grandes contribuyentes al TCO.

Con todo, no hay necesidad de una fábrica explícita, porque "encontrar un servicio con nombre" es un concepto tan reutilizable, y las clases de fábrica no escalan como una solución. Y un Func <> parece estar bien, pero un bloque de interruptor es muy malo , y nuevamente, escribirás Funcs tan a menudo como estarías escribiendo Fábricas. Comience de forma simple, reutilizable, con menos código, y si eso resulta no hacerlo por usted, entonces vaya complejo.

Craig Brunetti
fuente
2
Esto se llama el patrón de localización del servicio y, por lo general, no es la mejor ruta a menos que sea absolutamente necesario
Joe Phillips
@JoePhillips ¿Tiene alguna información sobre por qué no es una buena solución? Me encanta su elegancia. El único inconveniente que se me ocurre es que creo una instancia de todos ellos cada vez que obtienes uno.
Peter
2
@ Peter La razón principal es porque es muy, muy difícil trabajar con él. Si está pasando un objeto serviceLocator a una clase, no es obvio en absoluto qué dependencias usa esa clase, ya que las obtiene de un objeto mágico "dios". Imagine tener que encontrar referencias del tipo que desea cambiar. Esa capacidad básicamente desaparece cuando obtienes todo a través de un objeto localizador de servicios. La inyección del constructor es mucho más clara y confiable
Joe Phillips,
No se. La obviedad no es una desventaja para mí ... porque si me preocupara por hacer un seguimiento de cómo mis componentes aprovechan sus dependencias, tendría pruebas unitarias para eso ... pruebas que no solo se refieren a cada dependencia, sino que nos ayudan a comprender CÓMO se necesita cada dependencia. ¿De qué otra manera te darás cuenta de eso leyendo los constructores?
Craig Brunetti
0

Me he encontrado con el mismo problema y trabajé con una extensión simple para permitir servicios con nombre. Lo puedes encontrar aquí:

Le permite agregar tantos servicios (con nombre) como desee de esta manera:

 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");

La biblioteca también le permite implementar fácilmente un "patrón de fábrica" ​​como este:

    [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
    }

Espero eso ayude

Subgurim
fuente
0

Creé mi propia extensión sobre la extensión IServiceCollectionusada WithName:

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapperes una instancia singleton que los mapas IService> NameOfService> Implementationdonde una interfaz podrían tener muchas implementaciones con diferentes nombres, esto permite registrar tipos de lo que podemos resolver cuando wee necesidad y es un enfoque diferente a múltiples servicios de resolución para seleccionar lo que queremos.

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

Para registrar un nuevo servicio:

services.AddScopedWithName<IService, MyService>("Name");

Para resolver el servicio, necesitamos una extensión IServiceProvidercomo esta.

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

Cuando resuelva:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Recuerde que serviceProvider se puede inyectar dentro de un constructor en nuestra aplicación como IServiceProvider.

Espero que esto ayude.

svladimirrc
fuente