简体   繁体   中英

How does one use dependency injection outside of an MVC context?

Core issue

Every example I see for dependency injection is paired with MVC for some reason as if nothing else exists outside of web projects. I take issue with this because there is a contradiction going on with MVC utilizing dependency injection but it delivers those dependencies through a Dependency Resolver which to me is just another name for a Service Locator.

DI in a simple console application

With all that being said, how do you use DI with a simple console application?

  • When there isn't a convenient Dependency Resolver ready to use. How do I actually perform the injection part of DI?
  • I see the disapproving tone around Service Locators , but what else can you do?
  • You cannot pass the container because that's also bad practice, but again what else can be done?

Common confusion/frustration

I see a lot of programmers making these mistakes and honestly I can't blame them for it. There isn't a clear solution outside of MVC which is clearly using the dreaded Service Locator.

DI introduces its own problems

Something I don't feel good about doing is pass a dependency through a chain of objects to use it in a deeply nested piece of code. This just feels wrong.

Example

This is a watered down example of something I am working on to demonstrate my concern. I don't like passing the SMTP client dependency through a class, just to give it to another class. You might be compelled to say "Inject the SmtpClient into ServiceClass then into EntryPoint". In my example I cannot inject ServiceClass because it actually comes from a Factory pattern.

public static void Main(string[] args)
{
    var smtpClient = _container.GetDependency<ISmtpClient>();

    //When I do this manually I feel like it defeats the purpose of DI
    var ep = new EntryPoint(smtpClient);

    ep.RunAProcess();
}

public class EntryPoint
{
    private readonly ISmtpClient _smtpClient;

    public EntryPoint(ISmtpClient smtpClient)
    {
        //EntryPoint doesn't use this dependency
        _smtpClient = smtpClient;
    }

    public void RunAProcess()
    {
        /* More code here */

        //ServiceClass actually comes from a Factory, but I didn't 
        //want to make this example too long
        var svc = new ServiceClass(_smtpClient);

        svc.Send();
    }
}

public class ServiceClass
{
    private readonly ISmtpClient _smtpClient;

    public ServiceClass(ISmtpClient smtpClient)
    {
        //ServiceClass uses this dependency
        _smtpClient = smtpClient;
    }

    public void Send()
    {
        using (var mail = CreateMailMessage(message))
        {
            _smtpClient.Send(mail);
        }
    }
}

Almost related existing question

This is the closest SO question I found in relation to my query: DbContext Dependency Injection outside of MVC project

Outside of MVC you can use HostBuilder see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2

The general idea is that it works pretty much like the web version ( and will support console, windows services, and linux daemons etc )

 public static async Task Main(string[] args)
 {

    var host = new HostBuilder()        .
        .ConfigureServices(ConfigureServices)
        .UseConsoleLifetime()
        .Build();

    await host.RunAsync();
}

private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
{
    services
        .AddTransient<IThing, Thingy>()
        .AddTransient<Stuff>()
        .AddHostedService<MyService>();
}

Your Hosted Service is like your main entry point and things from there will be injected....

internal class MyService : IHostedService
{

    public MyService(Stuff stuff)  // injected stuff
    {

    }

    public Task StartAsync(CancellationToken cancellationToken)
    {

    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

This is more a matter of misunderstanding the design principles.

Something I don't feel good about passing a dependency through a chain of objects to use it in a deeply nested piece of code. This just feels wrong.

The core of your issue is about understanding how to apply a clean design which allows loose coupling and high cohesion. Whether it is Asp.Net MVC or console application is an implementation detail.

The watered down example in this case is not following a clean design as EntryPoint is tightly coupling to ServiceClass and also violates the Explicit Dependencies Principle .

EntryPoint in this example is not being genuine about its dependencies. If it does not use ISmtpClient directly then it should not explicitly depend on it just to pass it on.

And if ServiceClass is coming from a factory then the factory should be applied at the composition root and then explicitly injected into EntryPoint

Review the following refactoring to see what I am referring to

public static void Main(string[] args) {
    //ISmtpClient should be injected into ServiceClass
    //when resolved by the container or factoty
    IService service = _container.GetDependency<IService>();

    var ep = new EntryPoint(service);

    ep.RunAProcess();
}

public class EntryPoint {
    private readonly IService service;

    public EntryPoint(IService service) {
        this.service = service;
    }

    public void RunAProcess() {

        /* More code here */

        service.Send(message);
    }
}

public class ServiceClass : IService {
    private readonly ISmtpClient _smtpClient;

    public ServiceClass(ISmtpClient smtpClient) {
        //ServiceClass uses this dependency
        _smtpClient = smtpClient;
    }

    public void Send(Message message) {
        using (var mail = CreateMailMessage(message)) {
            _smtpClient.Send(mail);
        }
    }
}

So even if you apply pure dependency injection at the composition root, only the actual dependencies are injected into the target dependent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM