这是依赖注入的错误使用吗?

Is this the wrong use of Dependency Injection?

我正在与一些同事一起开发 MVC 应用程序。所有控制器都使用 Unity 将 ServiceFactory 注入其中...

public HomeController(IServiceFactory serviceFactory)
{
   Services = serviceFactory 
   // Where Services is a property of the Controller
}

现在,我正在编写大量 ViewModel,其中一些 ViewModel 需要访问来自大量服务的对象。所以,我有这样的模式...

public class MyViewModel
{
    public MyViewModel(IServiceFactory services)
    {
        // Do stuff
    }
}

我的控制器包含这样的代码...

public ActionResult SomeAction()
{
    var model = new MyViewNodel(Services)
    // ...
}

我的一些同事认为这不是 DI 的正确用法,但似乎无法阐明原因。

他们说得对吗?如果是,为什么?

我觉得很合适。 ViewModels 依赖于某些服务。因此,不是在 ViewModel 中实例化这些服务,而是通过构造函数 注入 它们。一个非常简单的 DI 案例。

您的同事发出危险信号的原因是您成功地通过服务注入实践了 DI,但您在控制器操作中新建了一个 MyViewModel 实例,"hides" 实现细节从外部。

也就是说,由于此控制器不应直接从其他代码使用,而应从 Web 使用,因此您可以提出这是可以接受的论点。我个人的偏好是,既然你已经开始注入依赖,就不要停下来!

编辑:

正如 James 在评论中提到的那样,最好通过一个独立的注入工厂来解析您的视图模型实例。这样你就可以在同一个控制器中解析多个视图 class.

private readonly IViewModelFactory _factory;
public HomeController(IViewModelFactory factory)
{
    _factory = factory;
    var model = _factory.GetViewModelInstance(); 
    // Where Services is a property of the Controller
} 

注册时

IMyViewModelFactory

针对您的具体 class,解决容器中 IServiceFactory 的注入问题,将其突出显示为注入的依赖项。

public ViewModelFactory(IServiceFactory factory)

这在一定程度上取决于您对设计的看法,但在您的应用程序 IMO 中出现的问题是您将数据与行为混合在一起。您应该为视图提供的视图模型应该是 DTO;纯数据对象。您不希望这些对象有任何行为,因为这对应用程序隐藏了逻辑。视图呈现时执行的所有逻辑应尽可能简单。不仅使关于应用程序代码的推理更简单,而且还使您不必为视图编写自动化测试(这绝对是一件痛苦的事情)。

当您将视图模型用作 'post-back model' 时(这非常常见且方便),让视图模型成为简单 DTO 以外的任何东西都会很快崩溃。 MVC 不能模型绑定你的复杂视图模型,除非你编写一个自定义绑定器将这些依赖项注入其中;或者至少告诉 MVC 如何构造该类型。这是非常不切实际的,并且会导致每个视图模型都有很多额外的代码。虽然这个问题可以通过定义一个单独的 'post-only' 视图模型来解决,但这又会导致重复代码。这两个模型会很快失去同步(我是根据这方面的经验讲的)。

由于您在视图模型中混合了行为,因此您不得不在控制器中手动进行依赖注入(就像您现在所做的那样),或者您需要将此代码提取到工厂中。两者都有缺点。

像现在这样注入那些依赖项,注入代码会污染控制器;它不应该关心的事情。构造函数应该是一个实现细节(只有 class 本身和组合根是关心的),但现在每次你的视图模型的构造函数发生变化时,这个变化都会冒泡到控制器。

将其提取到工厂中可能会解决该问题,但会迫使您为每个控制器编写一个工厂。尽管有些容器包含为您自动生成这些工厂的功能,但您的代码中仍然有这种额外的抽象;如果您将视图模型保持为简陋的 DTO,则不需要抽象。

所以一般来说你应该遵循这个规则:

Create object graphs of stateless services and build them early. After that, pass pure data through the graph.

所以在你的例子中,视图模型就是数据。数据不应依赖于服务;服务应该依赖于数据。您让您的服务(您的控制器)创建该数据(视图模型)并将其传递(将其返回给视图)。