从一个公共模板应用程序继承 ASP.NET MVC 站点?(多租户)

13
我们正在构建大约10个ASP.NET MVC网站,这些网站具有公共的功能集(以及相应的URL、路由、控制器、操作和视图)。这些网站还将共享一组基础域对象(例如用户、公司)和这些对象上的基础属性(例如名称、地址等)。
但是每个网站也将从基础上高度定制和扩展。例如,我们面向大型公共公司的网站将在公司域对象上具有“子公司”和“股票符号”字段,而我们面向初创企业的网站将具有“创投公司”和“融资”属性。外观也会有很大变化,尽管我们试图保持HTML尽可能一致(除了额外的表单字段以获取额外的域对象属性等)。我们还将只适当地覆盖图像,以便我们可以在网站之间重用相同的按钮图形。
无论如何,我们正在努力找出最佳方式来因素化和架构事物,以便我们可以尽可能多地重用代码和测试,而不限制我们添加每个应用程序属性并在应用程序之间变化UI的自由。
我熟悉如何处理类似于StackOverflow / SuperUser / ServerFault(或MSDN / TechNet)中找到的有限自定义多租户,其中UI略有不同,数据模型基本相同。但是当模型和UI非常不同(但继承自公共基础)时,我不太确定如何继续。
我不太担心运营问题,因为我们可能会在单独的应用程序域中运行每个网站,并将它们托管在单独的数据库上。我更担心降低长期代码维护成本,增加敏捷性(例如,易于向基础添加新功能而不会破坏派生应用程序),以及在构建第2、3、4等网站时实现短期开发/测试成本节省。
我寻求高层次的指导和建议,还有使用现代ASP.NET MVC实践使这些指导变为现实的具体建议。

我知道这是一个非常泛泛的问题,但首先我正在寻找高层次的指导以及具体的技巧和诀窍,以便在ASP.NET MVC中应用该指导,包括以下内容:

  • 建议在哪里跨Visual Studio项目拆分基类/派生类
  • 源代码控制提示以避免分叉
  • 数据库架构提示(值得一提的是,我们的数据库都很小 - 每个表少于10K行,因此开发/测试成本比DB性能更重要)
  • 有关重用控制器/视图等与“基”模型属性相对应的提示,特别是有关重用UI的提示,例如“新客户”表单,其中将具有基本和派生属性的混合。

有人有关于如何设计这样的多租户应用程序的好建议吗?

4个回答

11

以下是我们的做法,目前已在大约8个网站上运作良好。

  • 定义核心MVC项目,包括控制器、ViewModels、HttpApplication、路由等,将其编译为一个DLL文件,构成网站的主体。

  • 创建一组基本默认视图、脚本、图像等,作为各个网站的默认设置。

  • 为每个客户创建自定义控制器、路由等,编译为另一个DLL。

  • 同样针对每个客户,重新创建任何需要使用的视图、脚本和图像。

为了使上述步骤协同工作,您需要编写一些“粘合剂”。第一件“粘合剂”的工作是创建一个自定义视图引擎。您需要自定义标准视图引擎以首先查找客户专用文件夹中的视图,然后再查找默认文件夹。这样就可以轻松地按客户覆盖默认布局。

第二种让所有内容协同工作的方法是从客户专用程序集中加载路由、控制器等到核心应用程序中。为此,我使用托管可扩展框架(Managed Extensibility Framework,MEF)来公开一个Register方法。在客户程序集代码上调用此方法,即可注册路由和任何其他客户特定需求。

以下是我的网站文件夹结构的一般概览,SiteContent文件夹首先用于查找视图:

  - AppContent
  - AppContent/Static
  - AppContent/Static/Images
  - AppContent/Static/Scripts
  - AppContent/Static/Styles
  - AppContent/Views
  - AppContent/Views/Shared

  - SiteContent
  - SiteContent/Static
  - SiteContent/Static/Images
  - SiteContent/Static/Scripts
  - SiteContent/Static/Styles
  - SiteContent/Views
  - SiteContent/Views/Shared

  - web.config
  - Global.asax

我有一些帮助程序可以在视图中使用,例如SiteImage和AppImage。此外,我让每个客户站点都使用特定名称的主页,这些名称在我的AppContent默认值中没有定义。

我意识到这只是一个大概述,但它现在对我们来说已经运作良好。


1
希望你能回答这个问题。你如何处理路由?我遇到了一个错误,路由找到了控制器的多个实例(一个在AppContent项目中,另一个在SiteContent项目中)。 - Chris

2
我目前参与了一个类似的项目组,主要关注允许客户在线申请产品,但收集信息的要求非常相似,区别仅在于产品特定信息或略有不同的法规要求。
我们尝试创建可重用的页面(模型、视图和控制器的组合),以便任何应用程序可以使用该页面来捕获信息并重定向到下一页。要实现这一点,我们使用抽象基本控制器作为模板方法模式,其中包含基本上所有所需的控制器逻辑(包括其应用的操作过滤器的操作方法),但然后使用抽象方法来处理特定的内容,例如重定向到流程中的下一页。这意味着具体应用程序页面流所使用的控制器的具体实现可能只包含一个方法,该方法返回对应于流程中下一页的RedirectToActionResult。
还有很多其他处理后退和导航等内容的东西,但是通过操作过滤器的帮助,您可以将其设置好,在完成后无需再考虑它。
还有基本模型对象,其中包含常见功能,如验证逻辑或状态持久性逻辑。
在申请过程中捕获的数据以xml序列化模型对象的形式保留在数据库中,一旦完成申请,就可以将其提取出来并进行反序列化,然后以任何格式输出到后端操作人员用于处理申请的系统中。
这意味着我们的项目结构由包含顶层抽象类、接口和实用程序类以及html助手、操作过滤器等的基本dll组成。然后我们有包含基本控制器、模型等具体实现以及视图和主页的mvc项目。
最难的是共享视图,我认为我们还没有完全解决这个问题。但是,由于MVC 2.0包含了区域,我认为这将不再是一个问题,但我还没有好好玩过它。(请参阅Scott Gu在2.0上的文章:http://weblogs.asp.net/scottgu/archive/2009/07/31/asp-net-mvc-v2-preview-1-released.aspx)
我POC(概念验证)的一件事看起来像是可行的,那就是使用基本MVC项目来包含公共视图,然后扩展默认视图引擎以在寻找要呈现的视图时搜索该项目(这很容易做到)。不过,区域是一个更好的解决方案。

关于源代码控制,我们正在使用svn,我认为您对分支感到担忧是合理的。这不是我们必须处理的事情,但我们可能会选择git,因为它似乎使分支和合并的过程变得更加轻松。

不确定这是否对您有所帮助,但我绝对建议记住抽象控制器和模型,并查看如何使用html助手和部分视图来组合类似的功能块。


感谢Dean的详细回复!我将深入研究您的建议,并可能构建一些原型来进行调查。 - Justin Grant

1

一种实现这个的方法是在源代码控制系统中使用分支。

主分支用于通用功能。然后您可以创建一个用于定制的分支,并将更改合并到定制分支或返回到主分支。


1
有趣的是,到目前为止我一直是这样做的(基于原型),但最初我认为这种方法相对于更传统的软件开发方法(如继承)会显得很不专业和脆弱。您是否曾经在具有重大复杂度的项目中使用源代码控制来实现自定义? - Justin Grant
是的,我知道有一家公司在做这个。他们有项目的不同版本和每个版本的客户定制。他们还建立了一个系统来跟踪安装在每个客户位置的系统以及需要在版本之间运行哪些升级脚本。 - Shiraz Bhaiji

1

1
我并不感到印象深刻...他说了很多好听的话,但都是没有具体细节的空谈,而且在他的开源项目中根本没有提到多租户策略;看起来他还没有抽出时间来实现它。 - Robert Harvey

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