简体   繁体   English

一个人如何在MVC上下文之外使用依赖项注入?

[英]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. 出于某种原因,我看到的每个用于依赖项注入的示例都与MVC配对,就像在Web项目之外没有其他东西一样。 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. 我对此有疑问,因为使用依赖注入的MVC一直存在矛盾,但是它通过依赖关系解析器传递这些依赖关系 ,在我看来, 依赖关系解析器只是服务定位器的另一个名称。

DI in a simple console application 简单控制台应用程序中的DI

With all that being said, how do you use DI with a simple console application? 综上所述,如何在简单的控制台应用程序中使用DI?

  • When there isn't a convenient Dependency Resolver ready to use. 如果没有方便的Dependency Resolver可供使用。 How do I actually perform the injection part of DI? 我如何实际执行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. MVC之外没有一个明确的解决方案,它显然使用了可怕的 Service Locator。

DI introduces its own problems DI引入了自己的问题

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. 我不喜欢通过一个类传递SMTP客户端依赖项,而只是将其提供给另一个类。 You might be compelled to say "Inject the SmtpClient into ServiceClass then into EntryPoint". 您可能会被迫说“将SmtpClient注入ServiceClass,然后注入EntryPoint”。 In my example I cannot inject ServiceClass because it actually comes from a Factory pattern. 在我的示例中,我无法注入ServiceClass,因为它实际上来自于Factory模式。

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 这是与查询有关的最接近的SO问题: MVC项目外部的DbContext依赖注入

Outside of MVC you can use HostBuilder see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 在MVC之外,您可以使用HostBuilder参见https://docs.microsoft.com/zh-cn/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 ) 总体思路是,它的工作方式与网络版本非常相似(并且将支持控制台,Windows服务和linux守护程序等)

 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. 无论是Asp.Net MVC还是控制台应用程序都是实现细节。

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 . 在这种情况下,经过简化的示例并未遵循干净的设计,因为EntryPointServiceClass紧密耦合,并且还违反了“ 显式依赖原则”

EntryPoint in this example is not being genuine about its dependencies. 在此示例中, EntryPoint不是关于其依赖关系的真实EntryPoint If it does not use ISmtpClient directly then it should not explicitly depend on it just to pass it on. 如果它不直接使用ISmtpClient ,则不应明确地依赖它来传递它。

And if ServiceClass is coming from a factory then the factory should be applied at the composition root and then explicitly injected into EntryPoint 如果ServiceClass来自工厂,则应将工厂应用于组合根,然后显式注入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. 因此,即使您在组合根目录应用纯依赖项注入 ,也只会将实际依赖项注入到目标依赖项中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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