Ioc / DI – 为什么我必须引用入口应用程序中的所有层/组件?
(与这个问题相关, EF4:为什么启用延迟加载时必须启用代理创build? )。
我是新来的DI,所以忍受着我…我明白,容器负责实例化所有我注册的types,但为了这样做,它需要参考我的解决scheme中的所有DLL和它们的引用。
如果我没有使用DI容器,我不需要在我的MVC3应用程序中引用EntityFramework库,只能引用我的DAL / Repo层的业务层。
我知道在一天结束的时候,所有的DLL都包含在bin文件夹中,但是我的问题是通过VS中的“添加引用”显式引用它,以便能够发布具有所有必要文件的WAP …
如果我没有使用DI容器,我不需要在我的MVC3应用程序中引用EntityFramework库,只能引用我的DAL / Repo层的业务层。
是的,这正是DI工作难以避免的情况:)
使用紧密耦合的代码,每个库可能只有几个引用,但是这些引用又有其他引用,创build了一个深层的依赖关系图,如下所示:
由于依赖关系图很深,这意味着大多数库都会沿着许多其他依赖关系进行拖拽 – 例如,在图中, 库C沿库H,库E,库J,库M,库K和库N拖动。 这使得更难以独立重用每个库,例如在unit testing中 。
但是,在一个松散耦合的应用程序中,通过移动对合成根的所有引用, 依赖关系图被严重展平 :
如绿色所示,现在可以重用库C,而不会拖拽任何不需要的依赖关系。
但是,所有这一切,与许多DI容器,你不必添加所有必要的库的硬引用。 相反,您可以使用基于约定的程序集扫描(首选)或XMLconfiguration的forms使用后期绑定 。
但是,当你这样做的时候,你必须记得将程序集复制到应用程序的bin文件夹中,因为这不会再自动发生。 就我个人而言,我很less觉得这值得额外的努力。
如果我不使用DI容器,我不必在我的MVC3应用程序中引用EntityFramework库
即使在使用DI容器时,您也不必让MVC3项目引用EF,但是您(隐式地)select通过在MVC3项目中实现Composition Root (构build对象图的启动path)来完成此操作。 如果您对使用程序集来保护您的架构边界非常严格,则可以将Composition Composition或(MVC)演示文稿移动到类库中。
在第一个选项中,让MVC3项目引用这个单独的“引导程序”程序集,它将引用您的解决scheme中的所有其他程序集,并引用您的DI容器库。 与此问题是,此引导程序项目不能引用位于MVC3项目中的types(因为它会导致循环程序集依赖项)。 这些types必须移到引导程序项目(可能需要引用System.Web.Mvc),或者需要在MVC3应用程序中保留一小部分容器configuration。 另外请注意,您的MVC项目仍然通过新的引导程序程序集间接引用所有其他程序集,因为程序集依赖关系是可传递的。
尽pipe将Composition Root放在一个单独的程序集中是一件有效的事情,但是大多数DI纯粹主义者(包括我)通常只会在有多个terminal应用程序(例如,一个web应用程序+ web服务+ windows服务)使用相同的业务层。 当我有一个单一的应用程序,我把组合根保存在我的最终应用程序。
第二个select是将所有MVC相关类(视图,控制器等)从启动项目移动到类库。 这允许这个新的表示层组件与应用程序的其余部分保持断开连接。 您的Web应用程序项目本身将成为具有所需启动逻辑的非常薄的shell。 Web应用程序项目将是引用所有其他程序集的组合根。
将表示逻辑提取到类库可能会使MVC工作变得复杂。 因为控制器和视图,图像,css文件等都不在启动项目中,所以把所有东西都连接起来会比较困难。 这可能是可行的,但需要更多时间来设置。
这两个选项都有其缺点,这就是为什么我通常build议只保留Web项目中的组合根。 许多开发人员不希望他们的MVC程序集依赖于DAL程序集,但这不是一个真正的问题。 不要忘记,程序集是部署工件; 您将代码拆分为多个程序集以允许单独部署代码。 另一方面,架构层是一个合乎逻辑的工件。 在同一个程序集中有很多层(很普遍)。 在这种情况下,我们最终将在同一个Web应用程序项目中(因此在同一个程序集中)具有组合根(层)和表示层。 即使该程序集引用包含DAL的程序集,表示层仍然不引用数据访问层 。 这是一个很大的区别。 当然,当我们这样做时,我们在编译时就失去了编译器检查这个架构规则的能力,但是这不应该是一个问题。 大多数架构规则实际上不能由编译器检查,总是有一些常识。 如果你的团队没有常识,你总是可以使用代码评论(每个团队都应该IMO总是做btw)。 您也可以使用NDepend(这是商业的)工具,这可以帮助您validation您的架构规则。 当您将NDepend与构build过程集成在一起时,可能会在有人检查代码时发出警告,这违反了这样的架构规则。
看看Composition Root的概念。
如果我没有使用DI容器,我不必在我的MVC3应用程序中引用EntityFramework库,只有我的业务层会引用我的DAL / Repo层。
您可以创build一个名为“DependencyResolver”的独立项目。 在这个项目中,你必须引用你所有的库。
现在的UI层不需要NHibernate / EF或任何其他非UI相关的库,除了Castle Windsor被引用。
如果你想从你的UI层隐藏Castle Windsor和DependencyResolver,你可以写一个HttpModule来调用IoCregistry的东西。
我只有一个StructureMap的例子:
public class DependencyRegistrarModule : IHttpModule { private static bool _dependenciesRegistered; private static readonly object Lock = new object(); public void Init(HttpApplication context) { context.BeginRequest += (sender, args) => EnsureDependenciesRegistered(); } public void Dispose() { } private static void EnsureDependenciesRegistered() { if (!_dependenciesRegistered) { lock (Lock) { if (!_dependenciesRegistered) { ObjectFactory.ResetDefaults(); // Register all you dependencies here ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry())); new InitiailizeDefaultFactories().Configure(); _dependenciesRegistered = true; } } } } } public class InitiailizeDefaultFactories { public void Configure() { StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type); ... } }
DefaultControllerFactory不直接使用IoC容器,而是委托给IoC容器方法。
public class StructureMapControllerFactory : DefaultControllerFactory { public static Func<Type, object> GetController = type => { throw new InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!"); }; protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { return base.GetControllerInstance(requestContext, controllerType); } return GetController(controllerType) as Controller; } }
GetController
委托在StructureMapregistry中设置(在Windsor中它应该是一个安装程序)。
+有一个依赖:如果一个对象实例化另一个对象。 +没有依赖关系:如果一个对象需要抽象(构造器注入,方法注入…)
+ Assembly引用(引用dll,webservices ..)独立于依赖概念,因为要parsing抽象并能够编译代码,图层必须引用它。