我正在开始一个新的MVC项目,并且(几乎)决定尝试一下存储库模式和依赖注入。我花了一些时间来筛选这些变化,但我为我的应用程序提出了以下结构:
- 表示层:ASP。. MVC前端(视图/控制器等)
- 服务层(业务层):接口和dto。
- 数据层:接口实现和实体框架类。
在我的解决方案中它们是3个独立的项目。表示层只有对服务层的引用。数据层也只有对服务层的引用——所以这基本上遵循了领域驱动设计。
以这种方式构建事物的要点是为了分离关注点、松耦合和可测试性。如果有任何不合理的建议,我很乐意接受。
我遇到困难的部分是将一个接口实现对象从数据层注入到表示层,表示层只知道服务层中的接口。这似乎正是DI的目的,而IoC框架(据说!)使这更容易,所以我想试试MEF2。但在过去几天里,我读了几十篇文章、问题和答案,似乎没有一篇文章以符合我的结构的方式解决了这个问题。几乎所有这些都已被弃用和/或都是简单的控制台应用程序示例,它们将所有接口和类放在同一个程序集中,彼此了解,完全无视松耦合和DI的要点。我还见过其他人要求将数据层dll放在表示层bin文件夹中,并配置其他类来查看该文件夹,这再次阻碍了松耦合的思想。
有一些解决方案探索基于属性的注册,但据说已经被基于约定的注册所取代。我还看到许多将对象注入控制器构造函数的例子,这引入了需要解决的一系列问题。我并不认为控制器应该知道这一点,而是更愿意将对象注入到模型中,但这可能是有原因的,因为许多例子似乎都遵循这一路径。我还没有太深入地研究这个问题,因为我仍然在尝试将数据层对象放到任何地方的表示层中。
我认为我的主要问题之一是不理解各种MEF2的东西需要放在哪个层,因为我发现的每个例子都只使用一个层。有容器和注册和目录和导出和导入配置,我一直无法弄清楚所有这些代码应该去哪里。
具有讽刺意味的是,现代设计模式应该抽象复杂性并简化我们的任务,但是如果我只是从PL中引用DAL并开始处理应用程序的实际功能,那么我现在已经完成了一半。如果有人能这样说,‘是的,我明白你在做什么,但是你遗漏了什么。你需要做的是abc。
谢谢。
是的,我知道你在做什么(或多或少),但是(据我所知)你错过了a)将契约和实现类型分离到它们自己的项目/程序集中,b)配置di容器的概念,即配置接口应使用哪些实现。
有无限的方法来处理这个问题,所以我给你的是我个人的最佳实践。我已经用这种方法工作了很长时间,我仍然很满意,所以我认为它值得分享。。总是有项目:MyNamespace.Something
和MyNamespace.Something.Contracts
一般来说,对于DI,我有两个程序集:一个用于只包含接口的契约,另一个用于这些接口的实现。在你的情况下,我可能有五个组件:Presentation.dll
, Services.dll
, Services.Contracts.dll
, DataAccess.dll
和DataAccess.Contracts.dll
。
(另一个有效的选择是将所有契约放在一个程序集中,我们称之为Commons.dll)
显然,DataAccess.dll
引用了DataAccess.Contracts.dll
,因为DataAccess.dll
中的类实现了DataAccess.Contracts.dll
中的接口。Services.dll
和Services.Contracts.dll
相同。
不,解耦部分:Presentation
引用Services.Contracts
和Data.Contracts
。服务引用Data.Contracts
。如您所见,没有对具体实现的依赖。这就是DI的意义所在。如果您决定交换您的数据访问层,您可以交换DataAccess.dll
,而DataAccess.Contracts.dll
保持不变。没有其他程序集直接引用DataAccess.dll,因此没有断开的链接,版本冲突等。如果这不是很清楚,试着画一个小的依赖关系图。您将看到,没有箭头指向任何名称中没有.Contracts
的程序集。
这对你有意义吗?如果有什么不清楚的地方,请问。
b。选择如何配置容器
可以在显式配置(XML等)、基于属性的配置和基于约定的注册之间进行选择。由于显而易见的原因,前者是一种痛苦,而我是后者的粉丝。我认为它比基于约定的配置更具可读性和易于调试,但这是一个品味问题。
当然,容器打包了所有的依赖项,您可以在应用程序架构中保留这些依赖项。为了弄清楚我的意思,考虑一个XML配置:它将包含指向所有实现程序集DataAccess.dll, ...
的"链接"。不过,这并没有削弱脱钩的观点。很明显,当交换实现程序集时,您需要修改配置。
然而,使用基于属性或约定的配置时,您通常使用您提到的自动发现机制:"在位于xyz的所有程序集中搜索"。这确实需要将所有程序集放在applications bin目录中。这没有什么错,因为代码需要在某个地方,对吗?
你得到了什么?假设您已经部署了应用程序,并决定交换DataAccess层。假设您已经选择了基于约定的DI容器配置。你现在能做的就是在VS中打开一个新项目,引用现有的DataAccess.Contracts.dll
,并以任何你喜欢的方式实现所有的接口,只要你遵循约定。然后构建库,将其命名为DataAccess.dll
,并将其复制并粘贴到原始应用程序的程序文件夹中,替换旧的DataAccess.dll
。完成后,您已经交换了整个实现,而其他程序集甚至都没有注意到。
我想,你明白了。使用IoC和DI确实是一种权衡。我强烈建议在设计决策时保持务实。不要把所有东西都连接起来,那样只会弄得一团糟。自己决定DI和IoC在哪里真正有意义,不要太受社区宗教讨论的影响。但是,如果使用得当,IoC和DI确实非常非常强大!
我在这上面又花了几天时间(现在总共花了一周左右),却没有什么进展。我相当确定我已经正确地设置了容器,我的约定发现了要映射的正确部分等,但是我无法找出让控制器DI激活的缺失环节-我不断收到错误消息,说明我没有提供无参数构造函数。所以我已经做完了。
但是,我确实设法继续使用我的结构和意图,将DI与IoC一起使用。如果有人遇到和我一样的问题,想要一个替代的解决方案:放弃mef2,使用Unity。最新版本(撰写本文时为3.5)已经按照惯例进行了发现,并且就像开箱即用一样-它甚至有一个相当完整的工作示例手册。还有其他的IoC框架,但我选择了Unity,因为它支持微软并且在性能基准测试中表现良好。从NuGet安装引导程序包,大部分工作就完成了。最后,我只需要写一行代码来映射我的整个DAL(他们甚至为你创建了一个存根,所以你知道在哪里插入它): container.RegisterTypes(
AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "xxx.DAL.Repository"),
WithMappings.FromMatchingInterface,
WithName.Default);