webapi services property net iservicecollection injection dependency configureservices asp c# asp.net-core dependency-injection .net-core ioc-container

c# - services - .NET Core DI, formas de pasar parámetros al constructor.



property injection net core (3)

Tener el siguiente constructor de servicios.

public class Service : IService { public Service(IOtherService service1, IAnotherOne service2, string arg) { } }

¿Cuáles son las opciones de pasar los parámetros mediante el mecanismo de .NET Core IOC?

_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 ?


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 si necesita reemplazar una dependencia de forma dinámica.

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 acceso directo (no estoy seguro de que funcione con parámetros / tipos de valores / primitivas de cadena (int, float, cadena), sin probar) (Lo probé y confirmé su funcionamiento, incluso con múltiples parámetros de cadena) en lugar de resolver cada dependencia individual 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 reemplazarse (no se deben resolver con 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 de cadena del tipo que se va a instanciar).

ActivatorUtilities.CreateInstance<Service> se usa 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 llamada MyService , y tiene IOtherService , ILogger<MyService> como dependencias y usted desea resolver el servicio pero reemplaza el servicio predeterminado de IOtherService (por ejemplo, OtherServiceA ) con OtherServiceB , podría hacer algo como:

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())

Luego, el primer parámetro de IOtherService recibirá la inyección de OtherServiceB , en lugar de OtherServiceA pero los parámetros restantes provendrán del contenedor.

Esto es útil cuando tiene muchas dependencias y desea tratar especialmente uno solo (es decir, reemplazar un proveedor específico de base de datos con un valor configurado durante la solicitud o para un usuario específico, algo que solo sabe 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 el método de fábrica, ya que ofrece un mejor rendimiento de referencia y referencia de GitHub .

Más adelante, uno es útil cuando el tipo se resuelve con mucha frecuencia (como en SignalR y otros escenarios de alta solicitud). Básicamente, ObjectFactory un ObjectFactory través de

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });

luego guárdelo (como una variable, etc.) y llámelo cuando sea necesario

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

Actualizar:

Solo lo intenté para confirmar que también funciona con cadenas y enteros, y de hecho funciona. Aquí el ejemplo concreto que probé con:

class Program { static void Main(string[] args) { var services = new ServiceCollection(); services.AddTransient<HelloWorldService>(); services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "")); 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 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

Output: Hello Tseng


El parámetro de expresión ( x en este caso), del delegado de fábrica es un IServiceProvider .

Usa eso 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. Cuando se resuelva el tipo, pasará el proveedor completado como parámetro de delegado.


Si no se siente cómodo con la incorporación del servicio, puede utilizar el patrón de Parameter Object .

Así que extraiga 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 el new Service(... llamadas. Otro beneficio es que la configuración es un poco más limpia.

Para un constructor con un solo parámetro o dos, esto podría ser demasiado.