Tener el siguiente constructor de servicios
public class Service : IService
{
public Service(IOtherService service1, IAnotherOne service2, string arg)
{
}
}
¿Cuáles son las opciones para pasar los parámetros mediante el mecanismo de IOC de .NET Core?
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));
Hay alguna otra manera ?
Respuestas:
El parámetro de expresión ( x en este caso), del delegado de fábrica es a
IServiceProvider
.Úselo para resolver las dependencias,
_serviceCollection.AddSingleton<IService>(x => new Service(x.GetRequiredService<IOtherService>(), x.GetRequiredService<IAnotherOne>(), ""));
El delegado de fábrica es una invocación retrasada. Siempre que se deba resolver el tipo, pasará el proveedor completo como parámetro de delegado.
fuente
.WithParameter("argument", "");
Microsoft.Extensions.DependencyInjection.Abstractions
paquete (por lo que no hay dependencias específicas del contenedor)Cabe señalar que la forma recomendada es utilizar el patrón de opciones . Pero hay casos de uso en los que no es práctico (cuando los parámetros solo se conocen en tiempo de ejecución, no en el momento de inicio / compilación) o es necesario reemplazar dinámicamente una dependencia.
Es muy útil cuando necesita reemplazar una sola dependencia (ya sea una cadena, un entero u otro tipo de dependencia) o cuando usa una biblioteca de terceros que acepta solo parámetros de cadena / entero y necesita un parámetro de tiempo de ejecución.
Puede probar CreateInstance (IServiceProvider, Object []) como una mano de acceso directo
(no estoy seguro de que funcione con parámetros de cadena / tipos de valor / primitivas (int, float, string), sin probar)(Solo probé y confirmó que funciona, incluso con múltiples parámetros de cadena) en lugar de resolver cada dependencia a mano:_serviceCollection.AddSingleton<IService>(x => ActivatorUtilities.CreateInstance<Service>(x, ""); );
Los parámetros (último parámetro de
CreateInstance<T>
/CreateInstance
) definen los parámetros que deben ser reemplazados (no resueltos por el proveedor). Se aplican de izquierda a derecha a medida que aparecen (es decir, la primera cadena se reemplazará con el primer parámetro de tipo cadena del tipo que se va a instanciar).ActivatorUtilities.CreateInstance<Service>
se utiliza en muchos lugares para resolver un servicio y reemplazar uno de los registros predeterminados para esta única activación.Por ejemplo, si tiene una clase nombrada
MyService
, y tieneIOtherService
,ILogger<MyService>
como dependencias y desea resolver el servicio pero reemplazar el servicio predeterminado deIOtherService
(diga suOtherServiceA
) conOtherServiceB
, podría hacer algo como:myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())
Luego
IOtherService
, seOtherServiceB
inyectará el primer parámetro de , en lugar de queOtherServiceA
los parámetros restantes provengan del contenedor.Esto es útil cuando tiene muchas dependencias y solo desea tratar una sola de manera especial (es decir, reemplazar un proveedor específico de la base de datos con un valor configurado durante la solicitud o para un usuario específico, algo que solo conoce en tiempo de ejecución y durante una solicitud y no cuando la aplicación está construida / iniciada).
También puede usar el método ActivatorUtilities.CreateFactory (Type, Type []) para crear un método de fábrica en su lugar, ya que ofrece un mejor rendimiento GitHub Reference y Benchmark .
Posteriormente, uno es útil cuando el tipo se resuelve con mucha frecuencia (como en SignalR y otros escenarios de alta solicitud). Básicamente, crearías una
ObjectFactory
víavar myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });
luego almacenarlo en caché (como una variable, etc.) y llamarlo donde sea necesario
## Actualización: Lo intenté yo mismo para confirmar que también funciona con cadenas y números enteros, y de hecho funciona. Aquí el ejemplo concreto con el que probé:
class Program { static void Main(string[] args) { var services = new ServiceCollection(); services.AddTransient<HelloWorldService>(); services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow")); var provider = services.BuildServiceProvider(); var demoService = provider.GetRequiredService<DemoService>(); Console.WriteLine($"Output: {demoService.HelloWorld()}"); Console.ReadKey(); } } public class DemoService { private readonly HelloWorldService helloWorldService; private readonly string firstname; private readonly string lastname; public DemoService(HelloWorldService helloWorldService, string firstname, string lastname) { this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService)); this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname)); this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname)); } public string HelloWorld() { return this.helloWorldService.Hello(firstName, lastName); } } public class HelloWorldService { public string Hello(string name) => $"Hello {name}"; public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}"; } // Just a helper method to shorten code registration code static class ServiceProviderExtensions { public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => ActivatorUtilities.CreateInstance<T>(provider, parameters); }
Huellas dactilares
fuente
.AddControllersAsServices
se use, que reemplaza elControllerActivatorProvider
conServiceBasedControllerActivator
ActivatorUtilities.CreateInstance()
es exactamente lo que necesitaba. ¡Gracias!public string HelloWorld()
faltaba la implementación del métodoSi no se siente cómodo al renovar el servicio, puede usar el
Parameter Object
patrón.Así que extrae el parámetro de cadena en su propio tipo
public class ServiceArgs { public string Arg1 {get; set;} }
Y el constructor ahora se verá como
public Service(IOtherService service1, IAnotherOne service2, ServiceArgs args) { }
Y la configuración
_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; }); _serviceCollection.AddSingleton<IOtherService , OtherService>(); _serviceCollection.AddSingleton<IAnotherOne , AnotherOne>(); _serviceCollection.AddSingleton<IService, Service>();
El primer beneficio es que si necesita cambiar el constructor del servicio y agregarle nuevos servicios, entonces no tiene que cambiar las
new Service(...
llamadas. Otro beneficio es que la configuración es un poco más limpia.Sin embargo, para un constructor con uno o dos parámetros, esto podría ser demasiado.
fuente