Como dice el título, tengo una aplicación .NET Core a la que intento convertir y aprovechar la inyección de dependencia de Microsoft integrada.
Tengo un objeto y una clase base para el objeto, lo llamo CommunicationBase y Communicator . Cuando mi aplicación se inicia y lee el archivo de configuración, puedo tener una cantidad N de objetos para instanciar.
Anteriormente, antes de cambiar a Inyección de dependencia, en algún lugar de mi rutina de inicio, donde leía el archivo de configuración, tenía una variable List<CommunicationBase> a la que instanciaba y agregaba objetos de Communicator y, al mismo tiempo, configuraba algunos de los valores base. properties, que cambiaron según la cantidad que había en mi configuración y las propiedades de cada uno en config.
¿Cómo lograría esto con DI?
Entiendo que en mis servicios, registraría el tipo para que pueda inyectarse en otros constructores de clase.
Por ejemplo, services.AddTransient<CommunicationBase, Communicator>(); pero según tengo entendido, esto solo registra los tipos con DI. Puedo inyectarlo en una clase y tener una instancia aleatoria de uno de ellos.
¿Cómo tendría N número de instancias y podría establecer las propiedades de cada una a medida que creo la instancia?
¿O es este un escenario en el que DI no es necesario o no funcionará y necesito hacerlo de la forma en que lo estaba haciendo antes?
¡Gracias!
En primer lugar, debe tener claras las diferencias entre la vida útil transitoria, de alcance y única. Para comprender cómo funciona la lista de objetos de Communicator que se leerán de su archivo de configuración.
Un enfoque para resolver su pregunta es
Modificaría ligeramente el enfoque que se muestra aquí . Así que definiría alguna enumeración que luego se usaría para decidir qué instancia devolver.
Configuración de clases de muestra y la enumeración:
public enum CommuniationType { False, True, Other, } public abstract class CommunicationBase { public CommunicationBase(CommuniationType communiationType) { CommuniationType = communiationType; } public bool IsConnected { get; set; } public CommuniationType CommuniationType { get; protected set; } } public class Communicator : CommunicationBase { public Communicator(CommuniationType communiationType) : base(communiationType) { } } Ahora, en el lugar donde tiene acceso a la colección de servicios (por ejemplo, en ASP.NET, el lugar sería el método Stratup.RegisterServices ) define sus objetos de clase concreta y los registra, como en el código de ejemplo a continuación (en la parte inferior, hay también son clases de prueba que usan el objeto CommunicationBase para probar propósitos):
public class Program { static void Main(string[] args) { var serviceCollection = new ServiceCollection(); SetupNObjects(serviceCollection); serviceCollection.AddTransient<CommunicationBaseServiceResolver>(serviceProvider => communicationType => { var implementations = serviceProvider.GetServices<CommunicationBase>(); return implementations.First(x => x.CommuniationType == communicationType); }); serviceCollection.AddScoped<FalseTestClass>(); serviceCollection.AddScoped<TrueTestClass>(); var serviceProvider = serviceCollection.BuildServiceProvider(); var f = serviceProvider.GetService<FalseTestClass>(); var t = serviceProvider.GetService<TrueTestClass>(); } // Here you should take care of registering objects, after reading config. // That would be best place to do that. static void SetupNObjects(ServiceCollection serviceCollection) { var comFalse = new Communicator(CommuniationType.False); comFalse.IsConnected = false; var comTrue = new Communicator(CommuniationType.True); comTrue.IsConnected = true; serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comFalse); serviceCollection.AddScoped<CommunicationBase>((serviceProvider) => comTrue); } } public class FalseTestClass { private readonly CommunicationBase communication; public FalseTestClass(CommunicationBaseServiceResolver resolver) { communication = resolver(CommuniationType.False); } } public class TrueTestClass { private readonly CommunicationBase communication; public TrueTestClass(CommunicationBaseServiceResolver resolver) { communication = resolver(CommuniationType.True); } }Yo lo haría de la siguiente manera.
Primero tienes comunicadores y clases de configuración:
namespace WebApiApp { public abstract class CommunicationBase { public abstract string Communicate(); } public class Communicator1Settings { public string Parameter { get; set; } } public class Communicator1 : CommunicationBase { private readonly string parameter; public Communicator1(string parameter) { this.parameter = parameter; } public override string Communicate() { return $"Type: {nameof(Communicator1)}, parameter: {this.parameter}"; } } public class Communicator2Settings { public string Parameter1 { get; set; } public string Parameter2 { get; set; } } public class Communicator2 : CommunicationBase { private readonly string parameter1; private readonly string parameter2; public Communicator2(string parameter1, string parameter2) { this.parameter1 = parameter1; this.parameter2 = parameter2; } public override string Communicate() { return $"Type: {nameof(Communicator1)}, parameter1: {this.parameter1}, parameter2: {this.parameter2}"; } } public class CommunicatorsSettings { public List<Communicator1Settings> Communicators1 { get; set; } public List<Communicator2Settings> Communicators2 { get; set; } } }En appsettings.json tienes la configuración de comunicadores:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "Communicators": { "Communicators1": [ { "Parameter": "First communicator1 parameter" }, { "Parameter": "Second communicator1 parameter" } ], "Communicators2": [ { "Parameter1": "First communicator2 parameter1", "Parameter2": "First communicator2 parameter2" }, { "Parameter1": "Second communicator2 parameter1", "Parameter2": "Second communicator2 parameter2" } ] } } Entonces, tiene dos instancias de Communicator1 con diferentes parámetros y dos instancias de Communicator2 con diferentes parámetros también.
Luego, configura el contenedor. El siguiente es el contenido de program.cs para .net 6:
using WebApiApp; var builder = WebApplication.CreateBuilder(args); // Add services to the container. AddCommunicators(); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); void AddCommunicators() { var settings = new CommunicatorsSettings(); builder.Configuration.Bind("Communicators", settings); foreach (var communicatorSettings in settings.Communicators1) { builder.Services.AddScoped<CommunicationBase>( _ => new Communicator1(communicatorSettings.Parameter)); } foreach (var communicatorSettings in settings.Communicators2) { builder.Services.AddScoped<CommunicationBase>( _ => new Communicator2(communicatorSettings.Parameter1, communicatorSettings.Parameter2)); } } Ahora puede inyectar IEnumerable<CommunicationBase> en su controlador:
using Microsoft.AspNetCore.Mvc; namespace WebApiApp.Controllers { [ApiController] [Route("[controller]")] public class CommunicatorsController : Controller { private readonly IEnumerable<CommunicationBase> communicators; public CommunicatorsController(IEnumerable<CommunicationBase> communicators) { this.communicators = communicators; } public IActionResult Get() { var result = this.communicators.Select(x => x.Communicate()); return this.Json(result); } } } Este es el resultado de la API web de /communicators :
[ "Type: Communicator1, parameter: First communicator1 parameter", "Type: Communicator1, parameter: Second communicator1 parameter", "Type: Communicator1, parameter1: First communicator2 parameter1, parameter2: First communicator2 parameter2", "Type: Communicator1, parameter1: Second communicator2 parameter1, parameter2: Second communicator2 parameter2" ]