使用 Ninject、MVC 3 和使用服务定位器模式的依赖注入

Posted

技术标签:

【中文标题】使用 Ninject、MVC 3 和使用服务定位器模式的依赖注入【英文标题】:Dependency Injection with Ninject, MVC 3 and using the Service Locator Pattern 【发布时间】:2011-06-04 04:37:55 【问题描述】:

自从我阅读了关于另一个 *** 问题的答案(现在我不知道确切的问题)的答案后,我一直在烦恼的事情,其中​​用户说诸如“如果您正在调用服务定位器,那么您正在这样做错了。

这是一个享有盛誉的人(我想有几十万)所以我倾向于认为这个人可能知道他们在说什么。自从我第一次开始了解 DI 以及它与单元测试的关系以及不相关的情况以来,我一直在我的项目中使用 DI。这是我现在相当满意的事情,我认为我知道我在做什么。

但是,我在很多地方一直在使用服务定位器来解决项目中的依赖关系。一旦主要示例来自我的 ModelBinder 实现。

典型模型绑定器示例。

public class FileModelBinder : IModelBinder 
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext) 
        ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");

        IDataContext db = Services.Current.GetService<IDataContext>();
        return db.Files.SingleOrDefault(i => i.Id == id.AttemptedValue);
    

不是真正的实现——只是一个简单的例子

由于在第一次请求 Binder 时,ModelBinder 实现需要一个新实例,因此不可能在构造函数上为这个特定实现使用依赖注入。

在我的很多课程中都是这样。另一个例子是缓存过期进程,只要缓存对象在我的网站中过期,它就会运行一个方法。我运行了一堆数据库调用,什么都没有。我也在使用服务定位器来获取所需的依赖项。

我最近遇到的另一个问题(我在这里发布了一个问题)是我的所有控制器都需要一个 IDataContext 实例,我使用了 DI 来实现 - 但是 one 操作方法需要一个不同的实例数据上下文。幸运的是,Ninject 带来了一个命名依赖项。然而,这感觉像是一个杂物,而不是真正的解决方案。

我认为我至少对关注点分离的概念有相当好的理解,但我对依赖注入和服务定位器模式的理解似乎存在根本性的错误——我不知道那是什么。

我目前理解它的方式 - 这也可能是错误的 - 是,至少在 MVC 中,ControllerFactory 为 Controller 查找构造函数并调用服务定位器本身以获取所需的依赖项,然后传递它们但是,我可以理解,不是所有的类和什么都没有工厂来创建它们。所以在我看来,一些服务定位器模式是可以接受的......但是......

    什么时候不能接受? 当我应该重新考虑如何使用服务定位器模式时,我应该注意哪种模式? 我的 ModelBinder 实现有错吗?如果是这样,我需要学习什么来解决它? 在另一个问题中,一位用户 Mark Seemann 推荐了一个抽象工厂 - 这有什么关系?

我想就是这样 - 我真的想不出任何其他问题来帮助我理解,但非常感谢任何额外的信息。

我知道 DI 可能无法解决所有问题,而且我在实现它的方式上可能有些过火,但是,它似乎以我期望的方式与单元测试一起工作,而并非如此。

我不是在寻找代码来修复我的示例实现 - 我在寻找学习,寻找解释来修复我有缺陷的理解。

我希望 ***.com 能够保存草稿问题。我也希望回答这个问题的人在回答这个问题时获得适当的声誉,因为我认为我要求很多。提前谢谢。

【问题讨论】:

我认为您指的是 Mark Seeman 和这篇博文:blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx 【参考方案1】:

考虑以下几点:

public class MyClass

  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass(IMyInterface myInterface, IMyOtherInterface myOtherInterface)
  
    // Foo

    _myInterface = myInterface;
    _myOtherInterface = myOtherInterface;
  

通过这种设计,我能够表达我的类型的依赖要求。类型本身不负责知道如何实例化任何依赖项,它们由使用的任何解析机制(通常是 IoC 容器)提供(注入)给它。鉴于:

public class MyClass

  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass()
  
    // Bar

    _myInterface = ServiceLocator.Resolve<IMyInterface>();
    _myOtherInterface = ServiceLocator.Resolve<IMyOtherInterface>();
  

我们的类现在依赖于创建特定实例,但通过委托给服务定位器。从这个意义上说,Service Location 可以被认为是一个反模式,因为你没有暴露依赖关系,但是你允许可以通过编译捕获的问题冒泡到运行时。 (一个很好的阅读是here)。你隐藏了复杂性。

两者之间的选择实际上取决于您的建筑基础及其提供的服务。通常,如果您从头开始构建应用程序,我会一直选择 DI。它提高了可维护性,促进了模块化并使测试类型变得更加容易。但是,以 ASP.NET MVC3 为例,您可以轻松地将 SL 实现为融入设计。

您始终可以选择可以将 IoC/DI 与 SL 结合使用的复合设计,就像使用 Common Services Locator 一样。您的组件可以通过 DI 连接,但通过 SL 暴露。您甚至可以将组合加入其中并使用托管可扩展性框架(它本身支持 DI,但也可以连接到其他 IoC 容器或服务定位器)。这是一个很大的设计选择,通常我的建议是尽可能使用 IoC/DI。

我不会说你的具体设计是错误的。在这种情况下,您的代码不负责创建模型绑定器本身的实例,这取决于框架,因此您无法控制您对服务定位器的使用可能很容易更改访问 IoC 容器。 但是在 IoC 容器上调用 resolve 的动作……你不会考虑那个服务位置吗?

使用抽象工厂模式,工厂专门用于创建特定类型。您没有注册类型以进行解析,您实际上注册了一个抽象工厂并构建您可能需要的任何类型。使用服务定位器,它旨在定位服务并返回这些实例。从约定的角度来看类似,但在行为上却大不相同。

【讨论】:

这就是我目前使用它的方式。 (公共服务定位器)当我可以将它们传递进来时,我的所有类都没有实例化它们自己的依赖项。你的最后一句话是我只是让自己感到困惑的那句话,因为这是我多次问过自己的同一个问题.我只是讨厌到处看到Resolve 的想法。它只是似乎我做错了什么...... 在这种情况下可能是不可避免的,您希望保持解耦实现的健壮性,并且您无法控制类型的创建,服务位置是一种选择...

以上是关于使用 Ninject、MVC 3 和使用服务定位器模式的依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

我在使用 Ninject 和 MVC 5 Web api 2.2 时遇到问题

使用 Ninject.MVC3 在测试项目中添加绑定

ninject 继承安全规则违反了类型:mvc4 中的“Ninject.Web.Mvc.Filter.FilterContextParameter”

在 MVC3 应用程序中将 Ninject 与自定义角色提供程序一起使用

Ninject + MVC3 没有注入控制器

MVC3 EF 工作单元 + 通用存储库 + Ninject