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 Unity
permiten registrar implementaciones concretas por parte de algunos Key
que 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 Add
método de servicio que tome un parámetro key
o 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:
- La capacidad de registrar instancias usando una clave
- La capacidad de inyectar datos estáticos en constructores durante el registro
Update1
puede mover a una pregunta diferente, ya que inyectar cosas en los constructores es muy diferente de calcular qué objeto construirRespuestas:
Hice una solución simple usando
Func
cuando me encontré en esta situación.Primero declare un delegado compartido:
Luego, en su
Startup.cs
, configure los múltiples registros concretos y una asignación manual de esos tipos:Y úselo de cualquier clase registrada con DI:
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.
fuente
Otra opción es utilizar el método de extensión
GetServices
desdeMicrosoft.Extensions.DependencyInjection
.Registre sus servicios como:
Luego resuelve con un poco de Linq:
o
(suponiendo que
IService
tenga una propiedad de cadena llamada "Nombre")Asegúrate de tener
using Microsoft.Extensions.DependencyInjection;
Actualizar
Fuente AspNet 2.1:
GetServices
fuente
IEnumerable<IService>
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:
Agregue una dependencia a StructureMap en su
project.json
:Inyecte en la tubería de ASP.NET dentro
ConfigureServices
y registre sus clases (ver documentos)Luego, para obtener una instancia con nombre, deberá solicitar el
IContainer
Eso es.
Para el ejemplo para construir, necesitas
fuente
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 ).
fuente
Me he enfrentado al mismo problema y quiero compartir cómo lo resolví y por qué.
Como mencionaste hay dos problemas. El primero:
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)
)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:
Primero, estoy de acuerdo con usted en que depender de cosas nuevas como
IOptions
(y, por lo tanto, del paqueteMicrosoft.Extensions.Options.ConfigurationExtensions
) no es una buena idea. He visto algunas discusiones sobreIOptions
dó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:
fuente
IServiceA
en su caso. Como está utilizando métodos deIService
solo, debe tener dependencia deIService
solo.services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
para la primera clase yservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
para la segunda.Simplemente inyecto un IEnumerable
ConfigureServices en Startup.cs
Carpeta de servicios
MyController.cs
Extensiones.cs
fuente
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:
He escrito un artículo con más detalles: Inyección de dependencias en .NET: una forma de evitar los registros con nombres faltantes
fuente
Un poco tarde para esta fiesta, pero aquí está mi solución: ...
Startup.cs o Program.cs si el controlador genérico ...
Interfaz IMy de la configuración de la interfaz T
Implementaciones concretas de IMyInterface de T
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.
fuente
IMyInterface<CustomerSavedConsumer>
yIMyInterface<ManagerSavedConsumer>
son diferentes tipos de servicios: esto no responde a la pregunta de los OP en absoluto.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
SNSConfig
appsettings.json
Fábrica SNS
Ahora puede obtener el servicio SNS para la región que desea en su servicio o controlador personalizado
fuente
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:
Envase:
fuente
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:
Y luego registre el diccionario con la colección de servicios:
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)
Ahora puede acceder a sus tipos con cualquiera
o
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):
(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: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
fuente
IDispose
implementado, ¿quién es responsable de deshacerse del servicio? Se ha registrado el diccionario comoSingleton
desde mi publicación anterior, me mudé a una clase de fábrica genérica
Uso
Implementación
Extensión
fuente
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éIServiceAccessor
como una interfaz y luego tuve que agregar algunas extensiones másIServiceCollection
como tal:El servicio Accesor se ve así:
El resultado final, podrá registrar servicios con nombres o instancias con nombre como solíamos hacer con otros contenedores ... por ejemplo:
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.
fuente
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:
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:
o
He diseñado específicamente esta biblioteca para que funcione bien con Microsoft.Extensions.DependencyInjection, por ejemplo:
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 losAddSingleton<>
métodos funcionan normalmente.Para los servicios con nombre transitorios y con alcance, el registro crea un
ObjectFactory
modo 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.fuente
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!!
Crea tus diversas implementaciones
Registro
Uso del constructor y la instancia ...
fuente
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.
fuente
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
.fuente
var serviceA = new ServiceA();
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
fuente
¿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:
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.
fuente
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:
La biblioteca también le permite implementar fácilmente un "patrón de fábrica" como este:
Espero eso ayude
fuente
Creé mi propia extensión sobre la extensión
IServiceCollection
usadaWithName
:ServiceCollectionTypeMapper
es una instancia singleton que los mapasIService
>NameOfService
>Implementation
donde 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.Para registrar un nuevo servicio:
Para resolver el servicio, necesitamos una extensión
IServiceProvider
como esta.Cuando resuelva:
Recuerde que serviceProvider se puede inyectar dentro de un constructor en nuestra aplicación como
IServiceProvider
.Espero que esto ayude.
fuente