c# - route - ¿Cómo registrar múltiples implementaciones de la misma interfaz en Asp.Net Core?
istringlocalizer (16)
Tengo servicios derivados 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
Unity
permiten registrar implementaciones concretas mediante alguna
Key
que los distingue.
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 que ninguno de los métodos de
Add
servicio tome parámetros
key
o de
name
que generalmente se usan para distinguir la implementación concreta.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services here of the same interface
}
public MyController:Controller
{
public void DoSomeThing(string key)
{
// How do get service based on key
}
}
¿Es el patrón Factory la única opción aquí?
Actualización1
He leído el artículo
here
que muestra cómo usar el patrón de fábrica para obtener instancias de servicio cuando tenemos una implementación concreate múltiple.
Sin embargo, todavía no es la solución completa.
cuando llamo
_serviceProvider.GetService()
método
_serviceProvider.GetService()
no puedo inyectar datos en el constructor.
Por ejemplo considere este ejemplo
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 puede
_serviceProvider.GetService()
inyectar la cadena de conexión adecuada?
En Unity o en cualquier otro COI podemos hacerlo al momento del registro de tipo.
Puedo usar
IOption
sin embargo, eso 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 un nuevo contenedor.
También usar el patrón de fábrica para crear una instancia de servicio va en contra de DIP ya que la fábrica aumenta el número de dependencias que un cliente se ve obligado a depender de los detalles aquí
Así que creo que el DI predeterminado en el núcleo ASP.NET falta 2 cosas
1> Registrar instancias usando la clave
2> Inyectar datos estáticos en el constructor durante el registro
¿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 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....
}
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 una optimización puede estar en orden, pero honestamente 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 interruptores 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 resulta que no lo hace por usted, entonces vaya complejo.
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.... }
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 registros con nombres faltantes
Si bien la implementación inmediata 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
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
public class SNSConfig
{
public string SNSDefaultRegion { get; set; }
public string SNSSMSRegion { get; set; }
}
SNSConfig
"SNSRegions": "ap-south-1,us-west-2",
"SNSDefaultRegion": "ap-south-1",
"SNSSMSRegion": "us-west-2",
appsettings.json
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();
}
Fábrica SNS
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();
}
.........
}
Ahora puede obtener el servicio SNS para la región que desea en su servicio o controlador personalizado
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;
}
Hice una solución simple usando
Func
cuando me encontré en esta situación.
Primero declare un delegado compartido:
services.AddTransient<Consumer>();
services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();
services.AddTransient<Func<string, IService>>(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
}
});
Luego, en su
Startup.cs
, configure los múltiples registros concretos y una asignación manual de esos tipos:
public class Consumer
{
private readonly Func<string, IService> serviceAccessor;
public Consumer(Func<string, IService> serviceAccessor)
{
this.serviceAccessor = serviceAccesor;
}
public void UseServiceA()
{
//use serviceAccessor field to resolve desired type
serviceAccessor("A").DoIServiceOperation();
}
}
Y úselo de cualquier clase registrada con DI:
services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();
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.
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)
) -
Utilice 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 abstracciones de fábrica personalizadas. 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 cerrado abierto).
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 usa reflexión y
lógica inteligente
para adivinar argumentos para el constructor.
Esto también puede conducir a 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
IOptions
donde 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:
Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
{
services.AddScoped(typeof(IService), t);
});
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 class YourClassA : IStage<YouClassA> {
public void DoSomething()
{
...TODO
}
}
public class YourClassB : IStage<YourClassB> { .....etc. }
Crea tus diversas implementaciones
services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()
Registro
public class Whatever
{
private IStage ClassA { get; }
public Whatever(IStage<YourClassA> yourClassA)
{
ClassA = yourClassA;
}
public void SomeWhateverMethod()
{
ClassA.DoSomething();
.....
}
Uso del constructor y la instancia ...
services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();
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:
services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);
Y luego registre el diccionario con la colección de servicios:
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
};
});
si no desea 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 dar al posible mantenedor la oportunidad de comprender lo que está sucediendo; la notación de flecha es un poco críptica)
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection
Ahora puede acceder a sus tipos con cualquiera
System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection
o
IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
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):
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://.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;
};
});
(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 un
Name
propiedad (o lo que sea), y buscarlo por clave:
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);
}
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
No es compatible con
Microsoft.Extensions.DependencyInjection
.
Pero puede conectar otro mecanismo de inyección de dependencia, como
StructureMap
Consulte su página de inicio
y su
proyecto GitHub
.
No es difícil en absoluto:
-
Agregue una dependencia a StructureMap en su
project.json
: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>(); }
-
Inyecte en la tubería ASP.NET dentro de
ConfigureServices
y registre sus clases (ver documentos)public class HomeController : Controller { public HomeController(IContainer injectedContainer) { var myPet = injectedContainer.GetInstance<IPet>("B"); string name = myPet.Name; // Returns "CatB"
-
Luego, para obtener una instancia con nombre, deberá solicitar el
IContainer
public interface IPet { string Name { get; set; } } public class Cat : IPet { public Cat(string name) { Name = name; } public string Name {get; set; } }
Eso es.
Para el ejemplo para construir, necesitas
var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
s => new MyFactoryImpl(
mongoConnection, efConnection, otherConnection,
s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
Otra opción es utilizar el método de extensión
GetServices
de
Microsoft.Extensions.DependencyInjection
.
Registre sus servicios como:
var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));
Luego resuelve con un poco de Linq:
var serviceZ = services.First(o => o.Name.Equals("Z"));
o
"Structuremap.Microsoft.DependencyInjection" : "1.0.1",
(suponiendo que
IService
tiene una propiedad de cadena llamada "Nombre")
Asegúrese de
using Microsoft.Extensions.DependencyInjection;
Actualizar
Fuente AspNet 2.1:
GetServices
Si bien parece que @Miguel A. Arilla lo ha señalado claramente y voté por él, creé sobre su solución útil otra solución que se ve ordenada pero requiere mucho más trabajo.
Definitivamente depende de la solución anterior.
Así que básicamente creé algo similar a
Func<string, IService>>
y lo llamé
IServiceAccessor
como interfaz y luego tuve que agregar algunas extensiones más a
IServiceCollection
como tal:
public interface IServiceAccessor<TService>
{
void Register(TService service,string name);
TService Resolve(string name);
}
El servicio Accesor se ve así:
services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");
El resultado final, podrá registrar servicios con nombres o instancias con nombre como solíamos hacer con otros contenedores ... por ejemplo:
/// <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;
}
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 , pero no puedo encontrarla, donde el póster ha explicado en detalle por qué esta característica 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.
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
{}
Simplemente inyecto un IEnumerable
ConfigureServices en Startup.cs
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"; } }
}
Carpeta de servicios
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();
}
...
}
MyController.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;
}
Extensiones.cs
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);
Tiene razón, el contenedor incorporado de ASP.NET Core no tiene el concepto de registrar múltiples servicios y luego recuperar uno específico, como sugiere, una fábrica es la única solución real en ese caso.
Alternativamente, puede cambiar a un contenedor de terceros como Unity o StructureMap que brinde la solución que necesita (documentado aquí: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing-the-default-services-container ).
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, como
IRepository<T>
, como base para el acceso a datos.
Ejemplos de interfaces e implementaciones:
container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
Envase:
public interface IStage<out T> : IStage { }
public interface IStage {
void DoSomething();
}
Un poco tarde para esta fiesta, pero aquí está mi solución: ...
Startup.cs o Program.cs si el controlador genérico ...
public interface IMyInterface<T> where T : class, IMyInterface<T>
{
Task Consume();
}
Interfaz IMy de la configuración de la interfaz T
public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
public async Task Consume();
}
public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
public async Task Consume();
}
Implementaciones concretas de IMyInterface de T
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"));
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.