IoC容器,在编译时检查错误

Mat*_*sca 12 .net error-handling dependency-injection build inversion-of-control

我有一个简单的问题.

假设我有一个.Net解决方案,有不同的项目,比如一些类库(bll,dal等)和一个可以是web应用程序或wpf应用程序的主项目,这没关系.

现在假设我想使用IoC容器(如Windsor,Ninject,Unity等)来解析验证器,存储库,通用接口实现等问题.

我把它们放在一起.编译并运行良好.然后,有一天,我添加了一个新服务,在我的代码中,我只是尝试通过IoC容器解决它.事实是,我忘记在IoC配置中注册它.

所有内容都会编译,应用程序将被部署并运行.一切正常,除了页面的代码要求容器的新服务,容器回答"嘿,我对此服务一无所知".

您将记录您的错误,以及用户友好的错误页面.您将检查错误,查看问题并进行修复.很标准.

现在让我们说我们想要改进这个过程,并且在某种程度上能够在编译时知道我们期望IoC容器处理的每个服务是否在代码中正确注册.

怎么能实现这一目标?有一件事,单元测​​试被排除在可能的答案之外,我正在寻找另一种方式,如果确实存在的话.

思考?

编辑 - 经过一些回答和评论,似乎单元测试确实是实现此功能的唯一方法.

我想知道的是,如果单元测试 - 由于任何原因 - 不可能,因此IoC无法在编译时进行测试,这是否会阻止您使用IoC容器并选择在代码中进行直接实例化?我的意思是,您是否认为使用IoC和后期绑定太不安全和冒险,并认为其优势被这个"缺陷"所超越?

Ste*_*ven 10

编译器无法验证整个程序的运行情况.您的程序编译的事实并不意味着它正常工作(即使不使用IoC).为此,您需要自动测试和手动测试.这并不意味着您不应该尽量让编译器尽可能多地执行此操作,但出于这个原因而远离IoC是不好的,因为IoC旨在使您的应用程序保持灵活,可测试和可维护.如果没有IoC,您将无法正确测试代码,如果没有任何自动化测试,几乎不可能编写任何合理大小的可维护软件.

然而,具有IoC提供的灵活性确实意味着某些特定代码片段具有的依赖性,编译器不再能够对其进行验证.所以你需要以另一种方式做到这一点.

一些DI框架允许您验证容器的正确性.例如,Simple Injector包含一个Verify()方法,它将简单地遍历所有注册并解析每个注册的实例.通过在应用程序启动期间调用此方法(或使用类似方法),您将在(开发人员)测试期间发现DI配置出现问题并且它将阻止应用程序启动.您甚至可以在单元测试中执行此操作.

但重要的是,测试DI配置不需要太多维护.如果您必须为您注册的每种类型添加单元测试以验证容器,那么您将失败,因为缺少注册(因此缺少单元测试)将首先成为失败的原因.

这为您提供了"差不多"的编译时支持.但是,您需要了解应用程序的设计以及将事物连接在一起的方式.以下是一些提示:

  1. 远离隐式属性注入,如果无法找到已注册的依赖项,则允许容器跳过注入属性.这将禁止您的应用程序快速失败并稍后导致NullReferenceException.显式属性注入,强制容器注入属性,但是,尽可能使用构造函数注入.
  2. 如果可能,请明确注册所有根对象.例如,Controller在容器中显式注册所有ASP.NET MVC 实例.这样,容器可以从根对象开始检查完整的依赖关系图.您应该以自动方式注册所有根对象,例如通过使用反射来查找所有根类型.例如,Simple Injector 的MVC3 Integration NuGet Package包含一个RegisterMvcControllers扩展方法,可以为您执行此操作.其他容器的集成包包含类似的功能.
  3. 如果无法或不可能注册根对象,请在启动期间手动测试每个根对象的创建.Page例如,使用ASP.NET Web Form 类,您可能会在其构造函数中调用容器(因为Page类不幸的是,它必须具有默认构造函数).这里的关键是使用反射一次性找到它们.通过使用反射查找所有Page类并实例化它们,您将发现(在应用程序启动或测试时),您的DI配置是否存在问题.
  4. 让您的IoC容器为您管理的所有服务都有一个公共构造函数.多个构造函数会导致歧义,并且可能以不可预测的方式破坏您的应用程序.拥有多个构造函数是一种反模式.
  5. 有些情况下,在应用程序启动期间尚无法创建某些依赖项.要确保应用程序可以正常启动并且仍然可以验证DI配置的其余部分,请在代理或抽象工厂后面抽象这些依赖项.