如何在n层应用程序中使用MVC5和MEF2(基于约定)实现依赖注入?

时间:2014-06-14 06:11:33

标签: dependency-injection controller asp.net-mvc-5 repository-pattern mef2

我正在开始一个新的MVC项目,并且(几乎)决定给存储库模式和依赖注入提供支持。我需要一段时间来筛选变化,但我为我的应用程序提出了以下结构:

  1. 表示层:ASP.Net MVC前端(视图/控制器等)
  2. 服务层(业务层,如果您愿意):接口和DTO。
  3. 数据层:接口实现和实体框架类。
  4. 在我的解决方案中,它们是3个独立的项目。表示层仅具有对服务层的引用。数据层也只有对服务层的引用 - 因此这基本上遵循域驱动设计。

    以这种方式构建事物的关键是分离关注点,松散耦合和可测试性。如果有任何不合理之处,我很乐意就改进提出建议吗?

    我遇到困难的部分是将数据层中的接口实现对象注入表示层,表示层只知道服务层中的接口。这似乎正是DI的用途,IoC框架(据称!)使这更容易,所以我想我会尝试MEF2。但是在过去几天我读过的几十篇文章和问题和答案中,似乎没有任何东西能够以适合我的结构的方式解决这个问题。几乎所有都被弃用和/或是简单的控制台应用程序示例,它们在同一个程序集中具有所有接口和类,彼此了解所有并且完全违背松散耦合和DI的要点。我还看到其他人需要将数据层dll放在表示层bin文件夹中并配置其他类来查看 - 再次妨碍了松散耦合的想法。

    有一些解决方案可以探索基于属性的注册,但据推测这已被基于会议的注册所取代。我还看到很多将对象注入控制器构造函数的示例,该构造函数引入了它自己要解决的一组问题。我不相信控制器实际上应该知道这个,并且宁愿将对象注入到模型中,但是可能有这样的原因,因为很多例子似乎都遵循这条路径。我还没有深入研究过这个问题,因为我仍然试图将数据层对象放到任何地方的表示层中。

    我认为我的一个主要问题是不了解各种MEF2需要去哪一层,因为我发现的每个例子都只使用一层。有容器,注册和目录以及导出和导入配置,我无法确切知道所有这些代码应该去哪里。

    具有讽刺意味的是,现代设计模式应该抽象出复杂性并简化我们的任务,但如果我刚刚从PL引用DAL并开始工作,我现在已经完成了一半。应用程序的实际功能。如果有人可以说,我真的很感激,是的,我得到了你正在做的事,但是你错过了xyz。你需要做的是abc'。

    感谢。

2 个答案:

答案 0 :(得分:0)

是的,我得到你正在做的事情(或多或少)但是(据我所知)你错过了a)将合同和实施类型分离到他们自己的项目/组件中b)配置DI容器的概念,即配置哪些实现应用于接口。

有无限的方法来解决这个问题,所以我给你的是我个人的最佳实践。我现在已经用这种方式工作了很多,我仍然很满意,所以我觉得值得分享。

<强>一个。始终有项目:MyNamespace.SomethingMyNamespace.Something.Contracts

一般来说,对于DI,我有两个程序集:一个用于仅包含接口的契约,另一个用于实现这些接口。在您的情况下,我可能会有五个程序集:Presentation.dllServices.dllServices.Contracts.dllDataAccess.dllDataAccess.Contracts.dll

(另一个有效选项是将所有合同放在一个程序集中,我们称之为Commons.dll)

显然,DataAccess.dll引用DataAccess.Contracts.dll,因为DataAccess.dll中的类实现了DataAccess.Contracts.dll内的接口。 Services.dllServices.Contracts.dll相同。

不,解耦部分:Presentation引用Services.ContractsData.Contracts。服务参考Data.Contracts。如您所见,不依赖于具体实现。这就是整个DI的内容。如果您决定更换数据访问层,则可以DataAccess.dll交换DataAccess.Contracts.dll,而.Contracts保持不变。您的其他任何程序集都不直接引用DataAccess.dll,因此没有损坏的链接,版本冲突等。如果不清楚,请尝试绘制一个小的依赖关系图。你会看到,没有任何箭头指向任何名字中没有DataAccess.dll, ...的集会。

这对你有意义吗?如果有不清楚的地方,请询问。

<强>湾选择如何配置容器

您可以选择显式配置(XML等),基于属性的配置和基于约定的注册。虽然前者是一个痛苦的原因显而易见,但我是第二个的粉丝。我认为它比基于约定的配置更易读和易于调试,但这是一个品味问题。

当然,容器类型捆绑了您在应用程序体系结构中保留的所有依赖项。为了明确我的意思,请考虑为您的案例配置一个XML配置:它将包含&#39;链接&#39;到所有实现程序集DataAccess.Contracts.dll。尽管如此,这并没有破坏脱钩的想法。很明显,在交换实现程序集时需要修改配置。

但是,使用基于属性或约定的配置,您通常使用您提到的自动发现机制:&#39;搜索位于xyz&#39;中的所有程序集。这需要将所有程序集放在应用程序 bin 目录中。这没有什么不对,因为代码需要在某个地方,对吗?

你获得了什么?考虑您已部署应用程序并决定交换DataAccess层。比如说,您已经选择了基于会议的DI容器配置。您现在可以做的是在VS中打开一个新项目,引用现有的DataAccess.dll并以您喜欢的任何方式实现所有接口,只要您遵循这些约定即可。然后,您构建库,将其命名为DataAccess.dll,然后将其复制并粘贴到原始应用程序的程序文件夹中,替换旧的{{1}}。完成后,您已经交换了整个实现,而其他任何程序集都没有注意到。

我想,你明白了。使用IoC和DI确实是一种权衡。我强烈建议您在设计决策中务实。不要把所有东西都搞定,它只会变得混乱。决定自己,DI和IoC真正有意义并且不会受到社区宗教讨论的影响。不过,明智地使用,IoC和DI真的非常非常强大!

答案 1 :(得分:0)

好吧,我已经花了几天时间(现在总共大约一周)并且没有取得进一步的进展。我很确定我的容器设置正确,我的约定发现了要映射的正确部件等,但我无法弄清楚什么似乎是让控制器DI激活的缺失链接 - 我经常收到错误消息,指出我没有提供无参数构造函数。所以我已经完成了它。

但是,我确实设法推进了我的结构,并打算将DI与IoC一起使用。如果有人碰到了同样的墙壁,我想要一个替代解决方案:抛弃MEF 2并与Unity合作。最新版本(撰写本文时为3.5版)已经按惯例发现并且只是一种开箱即用的方式 - 它甚至还有一个相当详尽的手册和工作示例。还有其他的IoC框架,但我选择了Unity,因为它支持MS并且在性能基准测试中表现良好。从NuGet安装bootstrapper软件包,大部分工作都是为您完成的。最后我只需编写一行代码来映射我的整个DAL(他们甚至为你创建一个存根,以便你知道在哪里插入它):

        container.RegisterTypes(
            AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "xxx.DAL.Repository"),
            WithMappings.FromMatchingInterface,
            WithName.Default);