在C#中构建大型WinForms应用程序的项目和依赖关系结构化

24

更新:
这是我访问最多的问题之一,但我仍然没有为我的项目找到一个令人满意的解决方案。我在另一个问题的答案中读到的一个想法是创建一个工具,可以为您从列表中选择的项目“即时”构建解决方案。不过我还没有尝试过。


如何构建一个非常大的应用程序?

  • 在一个大型解决方案中使用多个较小的项目/程序集?
  • 使用几个大型项目?
  • 每个项目一个解决方案?

如果没有一个解决方案,如何管理依赖关系。 注意:我正在寻求基于经验的建议,而不是你在谷歌上找到的答案(我自己可以做到)。

我目前正在开发一个具有80个dll的应用程序,每个dll都在自己的解决方案中。管理依赖关系几乎是全职工作。有一个定制的内部“源代码控制”,增加了复制依赖dll到各个位置的功能。对我来说似乎不是最优解,但是否有更好的方法?实际上,处理80个项目的解决方案可能会很棘手,我担心。

(上下文:winforms,而不是Web)

编辑:(如果您认为这是一个不同的问题,请给我留言)

在我看来,以下方面之间存在相互依赖关系:

  • 应用程序的项目/解决方案结构
  • 文件夹/文件结构
  • 源代码控制的分支结构(如果使用分支)

但我很难将它们区分开来,以单独考虑它们,即使可能也不行。

我在这里问了另一个相关的问题。


相关:https://dev59.com/EXRB5IYBdhLWcg3wvpmc - Benjol
9个回答

5

源代码控制

我们有20或30个项目,分别构建为4或5个不同的解决方案。我们使用Subversion进行版本控制。

1)我们在SVN中有一个树形结构,按命名空间和项目名称逻辑组织所有项目。根目录下有一个.sln文件可以构建它们所有,但这不是必须的。

2)对于每个实际的解决方案,我们在SVN中有一个新的主干(trunks)文件夹,其中包含所有所需项目的SVN:External引用,以便从主树下的它们位置更新。

3)在每个解决方案中都有.sln文件加上其他一些必需的文件,以及任何仅属于该解决方案而不共享于其他解决方案的代码。

有许多较小的项目有时会有点麻烦(例如TortoiseSVN更新消息会因所有这些外部链接而变得混乱),但是确实具有巨大的优势,即不允许出现循环依赖项,因此我们的UI项目依赖于BO项目,但BO项目不能参考UI(也不应该!)。

架构

我们已经完全转向使用MS SCSF和CAB企业模式来管理我们各种项目在Win Forms界面中的组合和交互方式。我不确定您是否有相同的问题(多个模块需要在公共表单环境中共享空间),但如果确实存在这些问题,那么这可能会为您构建和组装解决方案带来一些理智和规范。

我提到这是因为SCSF倾向于将BO和UI类型的功能合并到同一个模块中,而以前我们保持了严格的三层策略:

FW - 框架代码。其功能与软件相关。 BO - 业务对象。其功能与问题域相关。 UI - 与UI相关的代码。

在这种情况下,依赖关系严格为UI->BO->FW

我们发现即使使用SCSF生成的模块,我们仍然可以维护该结构,所以世界上一切都很好:-)


2
为管理依赖项,无论您有多少程序集/命名空间/项目,您都可以看一眼NDepend工具。
个人而言,我鼓励使用少量大型项目,如有需要可以在一个或多个解决方案中组织。我在这里写了关于我的动机:从C#和VB.NET编译器的性能中获益

有趣的博客文章。当你说项目时,是指单独的项目还是项目中的解决方案? - Piotr Kula
我的意思是在一个或多个VS解决方案中有不同的VS项目。 这里有更多的信息:http://www.ndepend.com/WhiteBooks.aspx - Patrick from NDepend team

1

针对我曾经开发的一些系统,我们为不同的组件采用了不同的解决方案。每个解决方案都有一个共同的输出文件夹(带有Debug和Release子文件夹)

我们在解决方案内使用项目引用,在它们之间使用文件引用。每个项目都使用引用路径来定位其他解决方案中的程序集。我们不得不手动编辑 .csproj.user 文件,将 $(Configuration) msbuild 变量添加到引用路径中,因为 VS 坚持验证路径。

对于在 VS 之外进行的构建,我编写了 msbuild 脚本,递归识别项目依赖项,从svn中获取它们并构建它们。


1

我认为拥有一个包含所有80个项目的解决方案非常重要,即使大多数开发人员大部分时间使用其他解决方案。根据我的经验,我倾向于使用一个大型解决方案,但为了避免每次按F5时重新构建所有项目的痛苦,我会进入“解决方案资源管理器”,右键单击我现在不感兴趣的项目,并选择“卸载项目”。这样,该项目仍然留在解决方案中,但不会浪费我的任何东西。

话虽如此,80是一个很大的数字。根据这80个项目如何被分解成离散子系统,我可能还会创建其他解决方案文件,每个文件都包含一个有意义的子集。这将节省我大量右键单击/卸载操作的工作。尽管如此,您拥有一个大型解决方案的事实意味着始终存在其相互依赖的明确视图。

在我使用过的所有源代码控制系统中,它们的VS集成都选择将.sln文件放入源代码控制中,而且许多系统如果没有.sln文件就无法正常工作。我觉得这很有趣,因为.sln文件曾经被认为是个人的东西,而不是项目范围内的东西。我认为唯一值得纳入源代码控制的.sln文件类型是包含所有项目的“大型解决方案”。例如,您可以将其用于自动化构建。正如我所说,个人可能会为了方便而创建自己的解决方案,我并不反对将它们纳入源代码控制,但它们对个人来说比对项目更有意义。

另一个选项(也许更好?)是使用“生成配置管理器”,并将这些项目标记为不构建(取消“构建”复选框)。卸载项目意味着将其作为文本编辑项目文件的临时操作。如果您有其他引用它的项目,则这些引用会中断。 - Scott Dorman

1
我认为最好的解决方案是将其分解成更小的解决方案。在我目前工作的公司中,我们也有同样的问题;80个项目++在一个解决方案中。我们所做的是将其拆分为几个属于一起的较小解决方案和项目。来自其他项目的依赖dll被构建并链接到该项目中,并与项目一起检入源代码控制系统。这样做会使用更多的磁盘空间,但磁盘很便宜。通过这种方式,我们可以一直使用项目的版本1,直到绝对必要升级到版本1.5。不过,在决定升级到其他版本的dll时,您仍然需要添加dll。Google Code上有一个名为TreeFrog的项目,展示了如何构建解决方案和开发树。它还没有太多的文档,但我想你可以通过查看结构来了解如何做到这一点。

1
我见过的一个行之有效的方法是将所有项目都放在一个大型的解决方案中,以便进行全局构建和测试(虽然没有多少人会使用它进行构建,因为它太大了)。然后为开发人员提供各种相关项目分组的小型项目。
这些项目确实依赖于其他项目,但是,除非接口发生了变化或者它们需要更新正在使用的dll版本,否则他们可以继续使用较小的项目而不必担心其他一切。 因此,他们可以在工作时提交项目,然后对其进行固定(更改版本号后),当其他用户应该开始使用它们时。
最后,每周一次或更频繁地使用已固定的代码重新构建整个解决方案,以检查集成是否正常运行,并为测试人员提供良好的构建版本进行测试。
我们经常发现,巨大的代码段并不经常更改,所以总是加载它们是没有意义的(当你在处理较小的项目时)。
使用这种方法的另一个优点是,在某些情况下,我们有些功能需要数月时间才能完成,通过使用上述方法,这可以在不中断其他工作流的情况下继续完成。
我想,对于这个来说,一个关键的标准就是你的解决方案中没有大量的跨依赖项。如果有,那么这种方法可能不合适;但是,如果依赖项更有限,那么这可能是可行的方法。

我的代码的依赖关系图看起来像一盘意大利面条 :( - Benjol
也许这是值得研究的事情?我发现如果我们开始在每个项目中使用超过10个依赖项,那么就有些不对劲了。通常在这种情况下,我们可以通过将代码重构到我们的框架中来删除一些依赖项。 - Bravax

1

我放弃了项目引用(尽管您的宏听起来很棒),原因如下:

  • 在不同的解决方案之间切换并不容易,有时存在依赖项目,有时不存在。
  • 需要能够单独打开项目并构建它,以及独立于其他项目部署它。如果使用项目引用进行构建,有时会导致部署出现问题,因为项目引用使其寻找特定版本或更高版本等。它限制了混合和匹配不同版本依赖项的能力。
  • 此外,我的项目指向不同的.NET Framework版本,因此一个真正的项目引用并不总是发生。

(提示:我所做的一切都是针对VB.NET的,因此不确定C#的行为是否有微妙差异)

所以,我:

  • 我会针对解决方案中打开的任何项目以及未打开的项目,从全局文件夹(例如C:\GlobalAssemblies)构建。
  • 我的持续集成服务器会在网络共享上保持此文件夹的最新状态,并且我有一个批处理文件来将任何新内容同步到我的本地文件夹。
  • 我还有另一个本地文件夹,例如C:\GlobalAssembliesDebug,在其中每个项目都有一个后期构建步骤,将其bin文件夹的内容复制到此调试文件夹中,仅在DEBUG模式下进行。
  • 每个项目都将这两个全局文件夹添加到其引用路径中。(首先是C:\GlobalAssembliesDebug,然后是C:\GlobalAssemblies)。我必须手动将这些引用路径添加到.vbproj文件中,因为Visual Studio的UI将它们添加到.vbprojuser文件中。
  • 如果处于RELEASE模式,则我有一个预构建步骤,用于删除C:\GlobalAssembliesDebug中的内容。
  • 在任何作为主机项目的项目中,如果有非dll文件需要复制(输出到其他项目的bin文件夹中的文本文件),则我会在该项目上放置一个prebuild步骤,将它们复制到主机项目中。
  • 我必须在解决方案属性中手动指定项目依赖项,以按正确顺序构建它们。

所以,这个做的事情是:

  • 允许我在任何解决方案中使用项目,而不必烦恼项目引用。
  • Visual Studio仍然可以让我进入在解决方案中打开的依赖项目。
  • 在DEBUG模式下,它会构建针对已加载的项目。因此,首先它会查找C:\GlobalAssembliesDebug,如果没有,则查找C:\GlobalAssemblies
  • 在RELEASE模式下,由于它会从C:\GlobalAssembliesDebug删除所有内容,因此它只会查找C:\GlobalAssemblies。我想要这样做的原因是发布版本不会针对在我的解决方案中暂时更改的任何内容进行构建。
  • 很容易加载和卸载项目,不需要太多的努力。

当然,它并不完美。调试体验不如项目引用好。(无法像“转到定义”那样做,并使其正常工作),还有其他一些小问题。

总之,这就是我为了最大程度地使事情变得更好而尝试的地方。


非常有趣!我不知道我的宏是否适用于VB项目,但如果不行的话,调整起来应该很容易。 - Benjol

0

好的,我已经消化了这些信息,以及有关项目引用的此问题的答案,我目前正在使用这个配置,它似乎对我来说“可行”:

  • 一个大的解决方案,包含应用程序项目和所有依赖程序集项目
  • 我保留了所有项目引用,并对某些动态实例化的程序集进行了一些额外的手动依赖项调整(右键单击项目)。
  • 我有三个解决方案文件夹(_Working、Synchronised 和 Xternal)- 鉴于我的源代码控制没有与 VS 集成 (sob),这使我能够快速地在 _Working 和 Synchronised 之间拖放项目,以便我不会失去跟踪更改。XTernal 文件夹是为属于同事的程序集而设立的。
  • 我创建了一个“WorkingSetOnly”配置(在 Debug/Release 下拉菜单中的最后一个选项),它允许我限制在 F5/F6 上重新构建的项目。
  • 就磁盘而言,我将所有项目文件夹都放在几个文件夹中的一个中(因此只有一个项目上面的分类级别)
  • 所有项目都构建(dll、pdb 和 xml)到同一个输出文件夹中,并且具有相同的文件夹作为引用路径。(并且所有引用都设置为不复制)- 这使我可以选择从我的解决方案中删除一个项目,并轻松切换到文件引用(我有一个宏来实现这个功能)。
  • 在“Projects”文件夹的同一级别上,我有一个“Solutions”文件夹,其中维护了一些程序集的单独解决方案 - 连同测试代码(例如)和特定于程序集的文档/设计等。

目前这个配置对我来说似乎运行良好,但真正的考验将是尝试向我的同事推销它,并看看它是否适合作为团队设置。

目前未解决的缺点:

  • 我仍然有一个问题,关于单独的程序集解决方案,因为我并不总是想包含所有依赖项目。这会与“主”解决方案产生冲突。我用(再次)宏来解决这个问题,将损坏的项目引用转换为文件引用,并在重新添加项目时将文件引用恢复为项目引用。
  • 不幸的是,我还没有找到将构建配置链接到解决方案文件夹的方法 - 能够说“构建此文件夹中的所有内容”将非常有用 - 目前,我必须手动更新这个(很痛苦,也容易忘记)。 (您可以右键单击解决方案文件夹进行构建,但这无法处理F5场景)
  • 解决方案文件夹实现中存在一个(轻微的)错误,这意味着当您重新打开解决方案时,项目按添加顺序显示,而不是按字母顺序显示。(我已经向MS报告了错误,显然现在已经纠正,但我猜是针对VS2010的)
  • 我不得不卸载CodeRushXPress插件,因为它无法处理所有那些代码,但这是在修改构建配置之前,所以我要再试一次。

摘要 - 在提出这个问题之前我不知道但后来证明很有用的事情:

  1. 使用解决方案文件夹来组织解决方案而不会影响磁盘
  2. 创建构建配置以排除某些项目
  3. 即使使用文件引用,也能手动定义项目之间的依赖关系

这是我最受欢迎的问题,所以我希望这个答案对读者有所帮助。我仍然非常希望其他用户能提供进一步的反馈。


0

我们在源代码控制中有一个巨大的解决方案,位于主分支上。

但是,每个开发人员/团队都会在项目的较小部分上工作,并拥有自己的分支,其中包含一个只包含所需的几个项目的解决方案。通过这种方式,该解决方案足够小,易于维护,并且不会影响较大解决方案中的其他项目/dll。

然而,这里有一个条件:应该避免太多互相关联的项目存在于该解决方案中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接