C# 这是依赖注入的错误用法吗?

C# 这是依赖注入的错误用法吗?,c#,asp.net-mvc,dependency-injection,C#,Asp.net Mvc,Dependency Injection,我正在和一些同事开发一个MVC应用程序。所有控制器都使用Unity将ServiceFactory注入其中 public HomeController(IServiceFactory serviceFactory) { Services = serviceFactory // Where Services is a property of the Controller } 现在,我正在编写许多ViewModel,其中一些ViewModel需要从许多服务访问对象。所以,我有一个这样的

我正在和一些同事开发一个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)
    // ...
}
我的控制器包含这样的代码

public class MyViewModel
{
    public MyViewModel(IServiceFactory services)
    {
        // Do stuff
    }
}
public ActionResult SomeAction()
{
    var model = new MyViewNodel(Services)
    // ...
}
我的一些同事认为这不是DI的正确用法,但似乎无法解释为什么会这样


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

在我看来是正确的。
ViewModels
依赖于某些服务。因此,您不是在
ViewModel
中实例化这些服务,而是通过构造函数将它们注入。DI的一个非常简单的例子。

您的同事发出危险信号的原因是您正在成功地使用服务注入实践DI,而您正在控制器操作中新建一个MyViewModel实例,这对外部“隐藏”了实现细节

这就是说,由于该控制器不应该直接从其他代码中使用,而是从web中使用,因此您可以提出这样的论点:这是可以接受的。我个人的偏好是,既然您已经开始注入依赖项,现在就不要停止

编辑:

正如James在评论中提到的,通过独立的注入工厂解析viewmodel实例会更好。这样,您就可以在同一控制器类中解析多个视图

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

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

public ViewModelFactory(IServiceFactory factory)

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

如果将视图模型也用作“后置模型”(这是非常常见和方便的),那么让视图模型成为简单DTO之外的任何东西都会很快崩溃。MVC无法对复杂的视图模型进行模型绑定,除非您编写一个自定义绑定器将这些依赖项注入其中;或者至少告诉MVC如何构造该类型。这是非常不实际的,并且会导致每个视图模型都有大量额外的代码。虽然这个问题可以通过定义一个单独的“仅发布”视图模型来解决,但这同样会导致代码重复。这两个模型将很快失去同步(我是根据这方面的经验说的)

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

像现在这样注入这些依赖项,会用注入代码污染控制器;它不应该关心的事情。构造函数应该是一个实现细节(只关心类本身和组合根),但是现在每次视图模型的构造函数更改时,这个更改都会出现在控制器上

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

因此,一般来说,您应该遵循以下规则:

创建无状态服务的对象图并尽早构建它们。然后,通过图形传递纯数据


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

我不同意您的同事的意见。正确使用DI模式。在控制器的构造函数中注入依赖项,然后在方法中使用依赖项。DI的使用是正确的,但在ViewModel中使用依赖项是不正确的(在我看来)。虽然有点晚,但既然可以注入服务本身,为什么还要注入工厂呢?除非服务名为factory,并且您应该更改名称……我不同意,在这里创建一个新的具体视图模型是很好的,因为视图模型是专门为具有视图的1-1映射而构建的。“关于视图模型的DI,同样只是我的观点,完全是过火了”。@James我理解你的保留意见,在我的回答中我也有一点担心。也就是说,DI“纯粹主义者”会坚持你“一路向下”注入,这很可能是他的同事们对这种方法犹豫不决的原因。另外,我们要记住,我们可能误解了他对“视图模型”一词的正确使用。在这种情况下,它可能是直接注入视图的绑定模型,也可能是执行复杂计算的大规模BL抽象。我明白你的意思,也许更好的例子是注入
IViewModelFactory
实例,而不是将视图作为单个依赖项注入(我想在一个控制器中会有多个视图)。@james是正确的,我们在任何给定的控制器中都有许多不同的视图模型。@james这将是一个很好的替代方案,在这种情况下可能是更好的做法。