Home > Net >  .NET Core Dependency Injection how to handle multiple objects
.NET Core Dependency Injection how to handle multiple objects

Time:12-29

As the title says I have a .NET Core application that I am trying to convert over to and take advantage of the built in Microsoft Dependency Injection.

I have an object and a base class for the object, call it CommunicationBase and Communicator. When my app starts up and reads the configuration file, I can have N number of objects to instantiate.

Previously, before switching to Dependency Injection, somewhere in my startup routine, where I read the configuration file, I would have a List<CommunicationBase> variable that I would instantiate and add Communicator objects to and at the same time, set some of the base properties, which changed based on how many were in my configuration and each ones properties in config.

How would I achieve this with DI?

I understand that in my services, I would register the type so it can be injected into other class constructors.

For example, services.AddTransient<CommunicationBase, Communicator>(); but as I understand it, this just registers the types with DI. I can inject it into a class and have a random instance of one of them.

How would I then have N number of instances and be able to set properties of each one as I create the instance?

Or, is this a scenario where DI is not necessary or won't work and I need to just do it the way I was doing it before?

Thanks!

CodePudding user response:

I would slightly modify approach shown here. So I would define some enum that would then be used to decide what instance to return.

Sample classes setup and the enum:

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) { }
}

Now, in the place where you have access to service collection (e.g. in ASP.NET the place would be Stratup.RegisterServices method) you define your objects of concrete class and register them, as in the sample code below (at the bottom, there are also test classes using CommunicationBase object for testing puproses):

public class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();

        // This part could and should be extracted to separate method,
        // which will take care of instantiating and registering objects.
        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);

        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>();
    }
}

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);
    }
}

CodePudding user response:

Firstly do you need to has clear the differences between Transient, Scoped, Singleton lifetime. To understand how works with the list of Communicator objects that will be read from your configuration file.

One approuch to resolve your question is

  1. Create an interface ICommunicatorList with one method to get a List, i mean you can envolve the list of communicators.
  2. Create a clase that inherits from ICommunicatorList (for example called CommunicatorList), with a private field for your list of Communicators. On the constructor method set your private field with the list of communicator, o here you can receive like a parameter from the section of the config file to iterate and full your private field.
  3. on this class implement your code to return the list of communicators.
  4. Now, in your startups file you can now create the service services.AddTransient< ICommunicatorList>(x => new CommunicatorList(parameters));

CodePudding user response:

I would do it the following way.

First you have communicators and settings classes:

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; }
    }

}

In appsettings.json you have the configuration of communicators:

{
  "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"
      }
    ]
  }
}

So you have two instances of Communicator1 with different parameters and two instances of Communicator2 with different parameters as well.

Then, you configure the container. The following is the content of program.cs for .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));
    }
}

Now you can inject IEnumerable<CommunicationBase> into your controller:

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);
        }
    }
}

This is the result for /communicators web API:

[
    "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"
]
  • Related