Eddie
Eddie

Reputation: 13

How should I instantiate multiple instances of a single class with .net core DI?

I'm new to Dependency Injection, and so in trying to implement it in my .net core 3.0 projects, I've run into a scenario where I need multiple instances of a single service registered in my Service Provider. As a workaround, I've resorted to injecting the IServiceProvider itself and just calling GetRequiredService<T> (of transient-scoped services) multiple times. That works, but appears to be an anti-pattern, and I'm not sure how I'm supposed to do it properly.

Part of the problem in researching this further is the insistence in every answer that the need for multiple instances of a single class is itself code smell, but as an example, let's say I'm providing instructions to an oven to automate the baking process. A user will define the temperatures and times for each recipe in a view, then the view will return a List<string> that represents that list of steps to the controller. A sample list of steps might look like:

//Bake for 500 degrees Fahrenheit for 30 minutes, then 225F for another 90
["500F,30","225F,90"]

I will then need to convert that to a List<CookingStep> in the controller to add to my Recipe object. I've been doing something like this:

IOven Oven;
IRecipe newRecipe;
IServiceProvider sp;

//public OvenController... (constructor with dependencies injected here)

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = sp.GetRequiredService<IRecipeStep>();
    recipeStep.ParseTimeAndTemperatureFromString(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

But that isn't really inversion of control and it doesn't make the code any more testable, so in researching best practices, I could do a factory pattern, but I'm not sure how that helps do anything but bury the ultimate instantiation in a different class.

So I can make a factory, even a generic one, but is this (admittedly overly simplistic example) really better than the service locator pattern above?

IOven Oven;
IRecipe newRecipe;
IRecipeStepFactory rsFactory;

//public OvenController... (constructor with dependencies injected here)

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = rsFactory.NewStepFromString(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

public class RecipeStepFactory : IRecipeStepFactory
{
  public IRecipeStep NewStepFromString(string s)
  {
    RecipeStep recipeStep = new RecipeStep();
    recipeStep.ParseTimeAndTemperatureFromString(s);
    return recipeStep;
  }
}

(I apologize in advance for any issues with the presentation or substance of my question, it's the first time I've had to ask a question of my own)

Upvotes: 0

Views: 2038

Answers (2)

ProgrammingLlama
ProgrammingLlama

Reputation: 38767

Yes, the factory is better. Another approach is defining a delegate like this:

public delegate IRecipeStep RecipeStepFactory(string steps);

and then registering it like so (Func<string, RecipeService> would also work, but a delegate is cleaner):

services.AddTransient<RecipeStepFactory>(sp =>
{
    // resolve any services you need from the resolved IServiceProvider below
    // since you don't need any in this case, I've commented it out
    // IServiceProvider provider = sp.GetRequiredService<IServiceProvider>();
    return s =>
    {
        RecipeStep step = new RecipeStep();
        step.ParseTimeAndTemperatureFromString(s);
        return step;
    };
});

Then you can inject it:

private readonly RecipeStepFactory _recipeStepFactory;

public OvenController(RecipeStepFactory recipeStepFactory)
{
    _recipeStepFactory = recipeStepFactory;
}

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = _recipeStepFactory(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

I like this approach because it keeps everything in my composition root, but depending on the complexity of the factory, your current approach may be better. Personally, I'm using both approaches.

I actually use Autofac (a different DI container) for this, because I can just define the delegate in the service I'm declaring and Autofac automatically wires it up (see delegate factories) but obviously Autofac is probably more than what's needed for small projects, so unless you need it, you can use the pattern above.

Upvotes: 2

Ak777
Ak777

Reputation: 396

IMHO, and with my experience, i would lean towards factory pattern (based on what i have understood and your above problem stmt/example only).

And that way, the controller doesnt know the actual implementation of the ReceipeFactory. In future if the receipe factory adds more different implementation or extensions, your controller wont be requiring a change. At all times, it would get the REceipeStep from the factory and send it to oven to bake.

you may want to learn and explore about Clean code architecture and that may be elaborate but would be wider to understand the different patterns and features for every problem statement.

One such that i'd love is available on youtube. and another (if you have Pluralsight account) at here.

These are my personal suggestions only. I hope based on this learning and knowledge, this may vary based on a realworld problem and solutions.

Upvotes: 1

Related Questions