2

I have a Docker DotNet Core console app that processes messages from a queue.

There is a class called SimulationEngine and I was expecting that ILogger is passed by Dependency Injection automatically

public class SimulationEngine
{
    ILogger<SimulationEngine> logger;

    public SimulationEngine(ILogger<SimulationEngine> logger)
    {
        this.logger = logger;
    }

This is how the instance is created:

   public class RunsQueueProcessor : ...
   {
        public RunsQueueProcessor(..., ILogger<RunsQueueProcessor> logger)
        {
        }

        protected override async Task ProcessMessage(...)
        {
            this.logger.LogInformation(...);

            // Here DI is not working
            var engine = new SimulationEngine();
        

DI works fine to inject logger into RunsQueueProcessor, but it fails when I try to new SimulationEngine() with the following error:

Error   CS7036  There is no argument given that corresponds to the required formal parameter 'logger' of 'SimulationEngine.Simui lationEngine(ILogger<SimulationEngine>)'

How can I tell DotNet to use DI for that constructor?

--- EDIT ---

I wrote this question because I am working on a PoC and I don't need Dependency Injection everywhere. I only need it in few places where I am doing Unit Testing to validate specific algorithms. A more concrete questions is if there is a way to configure DotNet DI framework to inject dependencies when using the new statement or manually instantiating objects.

Thanks to all comments and answers, Now I understand more about how DotNet DI works.

Carlos Garcia
  • 2,771
  • 1
  • 17
  • 32
  • `new SimulationEngine()` doesn't get the benefit of DI, your new'ing manually. You need either supply the logger in your `new` statement or request an instance from your service collection – JSteward Dec 14 '20 at 22:24
  • 1
    You are not using DI for `SimulationEngine`, you need to add a parameter to the constructor, for example: `public RunsQueueProcessor(..., ILogger logger, SimulationEngine engine)` – DavidG Dec 14 '20 at 22:25
  • Thanks both, my questions i more "when can, and when I cannot use DI". I was expecting dotNet to be able to do it everywhere, seems it only works for things like ASP controllers. Is that right? – Carlos Garcia Dec 14 '20 at 22:27
  • 1
    You are misunderstanding what DI is, I suggest you go read up on it. – DavidG Dec 14 '20 at 22:28
  • Define an Interface ISimulationEngine and implement SimulationEngine, then inject ISimulationEngine in your processor – Useme Alehosaini Dec 14 '20 at 22:29
  • @DavidG I know inversion of control. In other languages I can tell the DI engine what type of Interfaces it can inject and different type of injections (Singleton, Factory, etc) - I am just not sure how does DotNet is supposed to do it, and I found tons of docs with different things for Core, Framework, 5, etc. – Carlos Garcia Dec 14 '20 at 22:34

1 Answers1

4

According to your last comment, you want more focus on explaining the Dependency Injection: The Other option, which is the traditional and the right way to do is:

  1. Define a marker Interface like ISimulationEngine.
  2. Implement the interface ISimulationEngine in SimulationEngine
  3. Register the interface in IServiceCollection (in startup.cs)
  4. Inject it in the constructor of RunsQueueProcessor class so the app understands you want to use SimulationEngine.

The marker interface

public interface ISimulationEngine
{
    void DoSomthing();
}

The Service that implements the interface

public class SimulationEngine : ISimulationEngine
{
    private readonly ILogger<SimulationEngine> _logger;

    public SimulationEngine(ILogger<SimulationEngine> logger)  //<-- here you are injecting the registered Singleton of ILogger
    {
        _logger = logger;
    }

    public async void DoSomthing()
    {
        throw new NotImplementedException();
    }
}

Suppose another service implements the interface

public class AnotherService: ISimulationEngine
{
          public async void DoSomthing()
        {
            throw new NotImplementedException();
        }
}

The registration of service in startup.cs

public void ConfigureServices(IServiceCollection services)
{
    //...
    // this means whenever ISimulationEngine injected that class can use
    // SimulationEngine service

    services.AddTransient<ISimulationEngine, SimulationEngine>();
    //...
} 

Note that you registered the interface ISimulationEngine for SimulationEngine but not for AnotherService which is also implementing the same marker but because of the registration in the container says whenever you see ISimulationEngine injected go and use SimulationEngine

Now inject the ISimulationEngine in the class that you want to use

public class RunsQueueProcessor
{
    private readonly ILogger<RunsQueueProcessor> _logger;
    private readonly ISimulationEngine _service;

    public RunsQueueProcessor(ILogger<RunsQueueProcessor> logger,  //<-- here AGAIN you are injecting the registered Singleton of ILogger
                              ISimulationEngine service            //<-- her you inject the Marker Interface so you can use any class implement this interface
                              )
    {
        _logger = logger;
        _service = service;
    }

    protected async Task ProcessMessage()
    {
        _logger.LogInformation("fdfdfd");

        await _service.DoSomthing();

        // Here DI is not working
        //var engine = new SimulationEngine();
    }
}

As a conclusion: You can imagine the process of injecting like the class say go and check the DI container, which service is defined for this interface?! let me use it.

you may ask what is the different between AdTransient and AddSingleton (there is AddScoped as well) check the link AddTransient, AddScoped and AddSingleton Services Differences

Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26
  • Thank you very much @U8080, very clear! One last question. Why doesn't DotNet inject ILogger in my example, but it does inject ISimulationEngine in yours? or why does it inject ILogger in RunsQueueProcessor but not in SimulationEngine? Thanks! – Carlos Garcia Dec 15 '20 at 00:22
  • @Carlos Garcia, happy to hear that it is useful, about the question you ask, Because you are using the default ILogger of DotNet Core which is already registered and is designed to be used with generic type ILogger so that means you need to mention clearly that class. The other option is to use third party Loggers like SeriLog which can be used without mentioning the generic type. As result: it is how designed to be. – Useme Alehosaini Dec 15 '20 at 00:26
  • @Carlos Garcia , if you are asking why DI in C#, the answer is many benefits but one of the most important is the decoupling (eliminating using the `new` keyword) `new` keyword is making the consumer class depends on the server class. – Useme Alehosaini Dec 15 '20 at 00:34
  • @Carlos Garcia when you inject an interface, the arguments of the constructor of the service class implementing that interface are fed from the DI container which is filled in startup – Useme Alehosaini Dec 15 '20 at 00:35
  • I know the benefits of DI and inversion of control. I want to know why ILogger works but ILogger doesn't. – Carlos Garcia Dec 15 '20 at 03:07
  • 1
    I think I understand now, DI in DotNet Core only works when the framework instantiate the class. It will not do it when you manually instantiate the class using `new` – Carlos Garcia Dec 15 '20 at 03:09
  • Partially yes, if you instantiate the class then you need to pass the argument, and in such a case there will be no benefit from DI you have done. – Useme Alehosaini Dec 15 '20 at 10:54
  • 1
    Thank you @U8080 for your help!! Now I get how it works in C# :) – Carlos Garcia Dec 15 '20 at 10:56