第三章 MVC 模式,项目和约定
在深入了解ASP.NET Core MVC的细节之前,我想确保您熟悉MVC设计模式背后的思路以及将其转换为ASP.NET Core MVC项目的方式。 您可能已经了解本章中讨论的一些想法和约定,特别是如果您已经完成了高级ASP.NET或C#开发。 如果没有,我鼓励你仔细阅读 - 深入地理解隐藏在MVC背后的东西可以帮助你在通读本书时更好地与MVC框架的功能联系起来。
MVC的历史
模型视图控制器模式起源于20世纪70年代后期,来自施乐PARC的Smalltalk项目,它被设想为组织一些早期的GUI应用程序。 原始MVC模式的一些细节与Smalltalk特定的概念(如屏幕和工具)有关,但更广泛的概念仍然适用于应用程序,并且它们特别适合于Web应用程序。
理解MVC模式
在高层次上,MVC模式意味着MVC应用程序将被分成至少三个。
- 模型,其中包含或表示用户使用的数据
- 视图,用于将模型的某些部分呈现为用户界面
- 控制器处理传入请求,对模型执行操作,并选择要呈现给用户的视图 每一块MVC架构都有明确的界定和独立的,这被称为分离问题。 在模型中操纵数据的逻辑只包含在模型中; 显示数据的逻辑只在视图中,处理用户请求和输入的代码只包含在控制器中。 每个部分之间有明确的划分,您的应用程序将更容易维护并延长其使用寿命,无论它最终变得多么复杂。
理解模型
模型 - MVC中的M - 包含用户使用的数据。有两种广泛的模型:视图模型,它们代表从控制器传递到视图的数据。领域模型:业务领域中的数据以及操作,转换和规则,用于创建、存储并操纵该数据,统称为模型逻辑。 模型是您的应用程序工作的环境的定义。例如,在银行应用程序中,该模型表示应用程序支持的银行中的所有内容,如帐户、总帐和客户的信用限额以及可用于存入资金并从账户中提款等类似模型中数据的操作。该模型还负责保存数据的整体状态和一致性 - 确保所有事务被添加到客户不会比银行有权获得更多的钱或者比银行更多的钱提取更多的钱。 对于MVC模式中的每个组件,我将描述它应该包括哪些东西和不应该包括哪些东西。
使用MVC模式构建的应用程序中的模型应该:
- 包含域数据
- 包含用于创建,管理和修改域数据的逻辑
- 提供一个清晰的API,以暴露模型数据和操作
模型不应该:
- 揭示如何获取或管理模型数据的细节(换句话说,细节的数据存储机制不应该暴露给控制器和视图)
- 包含根据用户交互转换模型的逻辑(因为这是控制器的工作)
- 包含向用户显示数据的逻辑(应由视图负责) 确保模型与控制器和视图隔离的好处是可以让测试变得更容易(我在第7章中描述单元测试),并且可以让整个应用程序更简单。
提示:许多刚接触MVC的开发人员对在数据模型中包含逻辑感到困惑,认为MVC模式的目标是将数据与逻辑分离。 这是一个误会:MVC模式的目标是将应用程序分成三个功能区域,每个功能区域可以包含逻辑和数据。 目标不是消除模型中的逻辑。 而是确保模型只包含用于创建和管理模型数据的逻辑。
理解控制器
控制器是MVC模式中的结缔组织,用作数据模型和视图之间的通道。 控制器定义提供在数据模型上运行的业务逻辑的动作,并向用户提供查看显示的数据。
使用MVC模式构建的控制器应该是:
- 根据用户交互包含更新模型所需的操作
控制器不应该:
- 包含管理数据外观的逻辑(即视图的工作)
- 包含管理数据持久性的逻辑(即模型的工作)
理解视图
视图包含向用户显示数据或从用户捕获数据所需的逻辑,以便可以通过控制器操作进行处理。
视图应该:
- 包含向用户呈现数据所需的逻辑和标记
视图不应该:
- 包含复杂的逻辑(放在控制器中更好)
- 包含创建,存储或操纵域模型的逻辑 视图可以包含逻辑,但应该简单,且须谨慎使用。除了最简单的方法调用或表达式,在视图中放置任何东西,会使整个应用程序更难测试和维护。
MVC的在ASP.NET上的实现
顾名思义,ASP.NET Core MVC将抽象MVC模式应用于ASP.NET和C#开发的世界。 在ASP.NET Core MVC中,控制器是C#类,通常派生自Microsoft AspNetCore.Mvc.Controller类。 从Controller派生的类中的每个公共方法都是与URL相关联的行动方法。 当请求发送到与行动方法相关联的URL时,执行该行动方法中的语句,以便对域模型执行某些操作,然后选择要向客户端显示的视图。 图3-1显示了控制器,模型和视图之间的交互。
图3-1 控制器,模型和视图之间的交互。
ASP.NET Core MVC使用一种称为Razor的视图引擎,它是负责处理视图的组件,以便为浏览器生成响应。 Razor视图是包含C#逻辑的HTML模板,用于处理模型数据以生成响应模型更改的动态内容。 我将在第5章中解释Razor的作用。 ASP.NET Core MVC不会对领域模型的实现施加任何限制。 您可以使用常规C#对象创建模型,并使用任何数据库,对象关联映射框架或.NET支持的其他数据工具来实现持久性。
MVC与其他模式的对比
当然,MVC不是唯一的软件架构模式。 还有很多其他的,其中有些是或至少已经非常受欢迎。 您可以通过查看替代方案了解有关MVC的许多内容。 在以下部分中,我简要介绍了构建应用程序的不同方法,并将其与MVC进行对比。 一些模式与MVC有些许的差异,而另一些则完全不一样。
我不是说MVC是最完美的模式。 我支持采取最佳方法解决手头问题。 有些情况下,某些模式与MVC一样好或可能更好。 我鼓励您在选择模式时做出明智的选择。 您正在阅读本书的事实表明,您已经决定使用MVC模式了,但是保持开阔的视野对你总是有好处的。
理解Smart UI 模式
最常见的设计模式之一就是智能用户界面(smart UI)。大多数程序员在职业生涯中的某个时候创建了一个智能UI应用程序 - 我当然有。如果您已经使用Windows窗体或ASP.NET Web窗体,那么您也这样做了。 要构建一个智能UI应用程序,开发人员通常将一组组件或控件拖到设计表面或画布上构建用户界面。控件通过发出按钮按压,击键,鼠标移动等事件来报告与用户的交互。开发人员添加了一系列事件处理程序来响应这些事件的代码;这些是在发出特定组件上的特定事件时调用的小块代码。这将创建一个单片应用程序,如图3-2所示。处理用户界面和业务的代码都是混合在一起的,没有任何分离的问题。定义数据输入的可接受值并且查询数据或修改用户帐户的代码最终以小部分结束,并按预期的顺序耦合在一起。
图3-2 Smart UI 模式
智能UI是简单项目的理想选择,因为您可以快速获得一些好的结果(相比之下,在MVC开发中,正如你将在第8章中看到的那样,需要在交付结果前进行大量的初始投资)。智能UI也适用于用户界面原型。这些设计UI工具可以真的很好,如果你和客户坐在一起,想要捕捉到外观和流程的要求,一个智能的UI工具可以快速有效地生成和验证不同的想法。Smart UI最大的缺点是难以维护和扩展。混合领域模型和业务逻辑代码与用户界面代码导致代码冗余,其中相同的业务逻辑被复制和粘贴以支持新添加的组件。找到所有重复的部分并修复可能很困难。添加新功能而不会破坏现有的功能几乎是不可能的。测试智能UI应用程序也可能很困难,唯一的方法是模拟用户交互,那非常不理想,提供全面测试覆盖更是不太可能。 在MVC的世界中,Smart UI通常被称为反模式:应该不惜一切代价避免。这种反感来自,人们花了一半的职业生涯尝试开发和维护智能UI应用程序,后来结果却一团糟,后来人们才发现MVC是一个更好的方案。 我的意思是说,拒绝智能UI模式是错误的。Smart UI模式并非都那么糟糕,它有好的方面。Smart UI应用程序快速且容易开发。组件和设计工具生产者已经为开发做出了很大的努力。开发体验是愉快的,甚至最无经验的程序员可以在短短几个小时内产生一些专业而又合理的东西。
Smart UI应用程序的最大弱点 - 可维护性 - 在小型开发工作中不会出现。 如果您正在为小客户制作一个简单的工具,Smart UI应用程序可以是一个很好的解决方案。 MVC应用程序太复杂太烦人。
理解模型-视图体系结构
在Smart UI应用程序中容易出现维护问题的部分是业务逻辑,它分散在整个应用程序中,使得修改或添加功能十分麻烦。 模型-视图架构改进了好多,它将业务逻辑转化为单独的域模型。 这样数据、进程和规则都集中在应用程序的一部分中,如图3-3所示。
图 3-3 模型-视图模式
模型视图架构可以是单片智能UI模式的改进 - 例如,更容易维护,但是会出现两个问题。 首先是因为UI和领域模型是紧密结合的,所以在这两者之间进行单元测试是很困难的。 第二个问题来自实践,而不是模式的定义。 该模型通常包含大量的数据访问代码 -虽然不总是这样,但通常是- 这意味着数据模型不仅包含业务数据,操作和规则。
理解经典的三层架构
为了解决模型视图架构的问题,三层模式将持久性代码与域模型分离,并将其放置在称为数据访问层(DAL)的新组件中。 如图3-4所示。
图 3-4 三层架构
三层架构是业务应用程序中使用最广泛的模式。 它对UI的实现没有任何限制,并且提供了很好的分离关注点,而不会太复杂。 而且可以创建DAL,使单元测试相对容易。 您可以看到经典三层应用程序和MVC模式之间的明显相似之处。 不同的是,当UI层直接耦合到点击事件GUI框架(如Windows窗体或ASP.NET Web窗体)时,几乎不可能执行自动化单元测试。 而且因为三层应用程序的UI部分可能很复杂,所以有很多代码不能被严格的测试。
在最糟糕的情况下,三层架构在UI层面缺乏执行规则意味着许多这样的应用程序最终都是虚拟伪装的Smart UI应用程序,并没有真正的关注分离。 这给了最糟糕的可能结果:一个非常复杂的,不可测试的,不可维护的应用程序。
理解MVC变种
我已经描述了MVC应用程序的核心设计原则,特别是适用于ASP。 NET Core MVC。 其他人以不同的方式来解释模式的各个方面,并添加到MVC中,以适应其项目的范围和主题。 在以下部分中,我简要介绍一下关于MVC主题的两个最流行的变体的概述。 了解这些变体对于使用ASP.NET Core MVC并不是必不可少的,但是为了知识的完整,我将它包含进来,因为这涉及到大多数软件模式讨论中使用的术语。
理解Model-View-Presenter 模式
模型视图呈现器(MVP)是MVC的一个变体,旨在更容易地与诸如Windows窗体或ASP.NET Web窗体之类的状态GUI平台相配合。这是值得尝试的,能够获得最好的Smart UI模式的优点而且不会带来问题。 在这种模式中,演示者(Presenter)与MVC中的控制器具有相同的职责,但与有状态视图的关系也更为直接,对用户的输入的值和操作可直接在UI组件中显示。 这种模式有两种实现方式:
- 被动视图实现,视图不包含逻辑。该视图是由演示者直接操纵的用于UI控件的容器。
- 监督控制器实现,视图负责展现逻辑的元素(如数据绑定),并且已经从域模型中引用了数据源。 这两种方法的区别在于视图的智能化程度。无论哪种方式,Presenter都从GUI框架中解耦,这使得Presenter逻辑更简单,适合于单元测试。
理解Model-View-View Model模式
模型视图模型(MVVM)模式是MVC最近的一个变化。 它源于Microsoft,并在Windows Presentation Foundation(WPF)中使用。 在MVVM模式中,模型和视图与MVC中具有相同的角色。 不同之处在于MVVM中视图模型的概念,它是用户界面的抽象表示,通常是一个C#类,用于显示要在UI中显示的数据的属性,以及可以从UI调用的数据的操作 。 与MVC控制器不同,MVVM视图模型不存在视图(或任何特定UI技术)的概念。 MVVM视图使用WPF绑定功能将视图中的控件(下拉菜单中的项目或按下按钮的效果)暴露的属性与视图模型公开的属性进行双向关联。
提示:MVC模式还使用术语view模型,但是指的是一个简单的模型类,仅用于将数据从控制器传递到视图,而不是域模型,这些模型是数据,操作和规则的复杂表示。
理解ASP.NET Core MVC 工程
当您创建一个新的ASP.NET Core MVC项目时,Visual Studio会为您提供项目中所需的初始内容的一些选择。这个想法是为了减轻新开发人员的学习过程,并为常见的功能和任务应用一些节省时间的最佳实践。我不是这种代码模具方式的粉丝。意图是好的,但执行总是很让人崩溃。我最喜欢ASP.NET和MVC的特点之一就是我在裁剪平台时有多大的灵活性来适应我的开发风格。 Visual Studio创建和填充的项目,类和视图使我感到是在别人的风格下工作。我也发现内容和配置太通用,太平淡,无法使用。微软不可能知道需要什么样的应用程序,所以它涵盖了所有的基础,但是以这样的一般化方式,我最终只是删除默认内容。 我的建议(给任何提出错误的人)是从一个空项目开始,并添加所需的文件夹,文件和包。不仅您将更多地了解MVC的工作原理,而且您可以完全控制您的应用程序所包含的内容。但是,我的想法可能不适合你。您可能会发现模板比我更加有用,特别如果您是ASP.NET的新手,并且尚未开发出适合您的开发样式。您可能还会发现项目模板是一个有用的资源和创意来源,尽管在完全了解应用程序的工作原理之前,您应该谨慎地向应用程序添加任何功能。
建立工程
当您首次创建新的ASP.NET Core项目时,您可以选择以下三个基本的起点:空模板,Web API模板和Web应用程序模板,如图3-5所示。
图3-5 ASP.NET 工程模板
空项目模板包含ASP.NET Core的管道,但不包括MVC应用程序所需的库或配置。 Web API项目模板包括ASP.NET Core和MVC,其中包含示例应用程序,演示如何从客户端接收和处理Ajax请求。 Web应用程序项目模板包括ASP.NET Core和MVC,其中包含演示如何生成HTML内容的示例应用程序。 Web API和Web应用程序模板可以配置不同的方案来验证用户并授权他们访问应用程序。 项目模板可以给人需要遵循特定的路径来创建某种ASP.NET应用程序的印象,但事实并非如此。模板仅仅是相同功能的不同起点,您可以使用任何模板添加所需的任何功能。例如,第20章中,我解释了如何处理的Ajax请求以及第28-30页的身份验证和授权,所有这些都是从空项目模板开始的。
因此,项目模板之间的真正区别是Visual Studio在创建项目时添加的库,配置文件,代码和内容的初始集合。最简单的模板(空)和最复杂(Web应用程序)之间存在很多差异,如图3-6所示,它显示了在创建项目之后的解决方案资源管理器。对于Web应用程序模板,我不得不将解决方案资源管理器集中在不同的文件夹上,因为单个列表对于打印页面来说太长。
图3-6 空项目模板和Web应用模板中的默认内容
Web应用程序模板添加到项目中的额外文件看起来令人望而生畏,但其中一些只是占位符或常用功能的示例实现。 一些其他文件设置MVC或配置ASP.NET。 还有一些是客户端库,它们将被并入应用程序生成的HTML中。 文件列表现在可能看起来很崩溃,但是在完成这本书时你会明白所做的一切。
无论您用于创建工程的模板如何,都会显示一些常见的文件夹和文件。 工程中的某些项具有特殊角色,他们是硬编码到ASP.NET中或MVC中或Visual Studio提供支持的工具中。 其他的则受到大多数ASP.NET项目或MVC项目中使用的命名约定的约束。 在表3-1中,我描述了您将在ASP.NET Core MVC项目中遇到的重要文件和文件夹,其中有些文件和文件夹默认不存在于项目中,但在后面的章节中我将介绍。
Table 3-1 MVC 工程中的文件项汇总
文件夹或文件 | 描述 |
---|---|
/Areas | 区域是将大型应用程序分割成较小的部分的一种方式。 我在第16章描述区域。 |
/Dependencies | “依赖关系”项目提供项目依赖的所有包的详细信息。我在第6章中描述Visual Studio使用的包管理器。 |
/Components | 这是定义用于显示自包含功能(如购物车)的视图组件类。 我在第22章中描述视图组件。 |
/controllers | 这是控制器类所在的地方。 这是一个约定,您可以将控制器类放在您喜欢的任何地方,因为它们都被编译成同一个程序集。 我在第17章详细描述了控制器。 |
Data | 这是定义数据库上下文类的地方,尽管我更倾向于忽略此约定,并在Models文件夹中定义它们,如第8章所示。 |
/Migrations | 这是存储数据库模式的详细信息,以便可以更新部署数据库。 我在第12章中演示了部署的过程。 |
/Models | 这里放置视图模型和域模型。 这是一个约定 您可以在项目中的任何位置或单独的项目中定义模型类。 |
/Views | 该目录包含视图和部分视图,通常在与它们相关联的控制器上命名的文件夹中组合在一起。 我在第21章详细描述了视图。 |
/Views/Shared | 此目录包含共享的布局和视图。 我在第21章详细描述视图。 |
/Views/_ViewImports.cshtml | 该文件用于指定将包含在Razor视图文件中的命名空间,如第5章所述。 它也用于设置标签助手,如第23章所述。 |
/Views/_ViewStart.cshtml | 该文件用于指定Razor视图引擎的默认布局,如第5章所述。 |
/bower.json | 此文件默认隐藏。 它包含由Bower软件包管理器管理的软件包列表,如第6章所述。 |
/project.json | 该文件指定了项目的一些基本配置选项,包括其使用的NuGet软件包,如第6章所述。 |
/Program.cs | 这个类配置了应用程序的托管平台,如第14章所述。 |
/Startup.cs | 该类配置应用程序,如第14章所述。 |
/wwwroot | 这是静态内容,如CSS文件和图像所在的地方。 Bower包管理器、JavaScript和CSS包也在这里,如第6章所述。 |
理解 MVC 约定
在MVC项目中有两种约定。第一类只是建议您如何组织您的项目。例如,通常将您所依赖的第三方JavaScript和CSS包放在wwwroot/lib文件夹中。这是其他MVC开发人员希望找到它们以及软件包管理器安装的地方。但是你可以自由地重命名lib文件夹,或者完全删除它,并将你的包放在别的地方。只要您的视图中的脚本和链接元素指向您所在的位置,MVC就会运行您的应用程序。 另一种惯例来自于约定大于配置的原则,这是Ruby on Rails受欢迎的主要卖点之一。例如,约定大于配置意味着您不需要显式地配置控制器及其视图之间的关联。您只需遵循一个特定的命名约定为您的文件,一切都正常。在处理这种惯例时,改变项目结构的灵活性较小。以下部分将说明用于替代配置的约定。
提示:所有的约定都是可以更改的,通过用你自己的MVC组件替换默认组件来实现。 我在本书中描述了不同的方法来解释MVC应用程序的工作原理,但这些是大多数项目中将要处理的约定。
遵循控制器类的约定
Controller类的名称以Controller结束,如ProductController,AdminController,和HomeController。 当从项目其他地方引用控制器时,例如使用HTML帮助器方法时,您可以指定名称的第一部分(例如Product),MVC会自动将Controller添加到名称中,并开始查找控制器类。
提示:您可以通过创建一个模型约定来改变这一点,我在第31章中描述。
遵循视图的约定
视图放在文件夹/Views/Controllername。 例如,与ProductController类相关联的视图放在/Views/ Product文件夹。
提示:请注意,Views文件夹中省略了"Controller"单词:/Views/Product,not/Views/ ProductController。 起初看起来似乎是违反直觉的,但它很快就变得自然了。 MVC希望以该方法命名操作方法的默认视图。 例如,与名为List的动作方法关联的默认视图应该称为List.cshtml。 因此,对于ProductController类中的List操作方法,默认视图预期为/Views/Product/List.cshtml。 在动作方法中返回调用View方法的结果时使用默认视图,如下所示:
return View();
你可以用名字指定不同的视图,像这样:
return View("MyOtherView");
请注意,我不包括文件扩展名或视图的路径。 当寻找一个视图时,MVC在控制器后面命名的文件夹中,然后在/Views/Shared文件夹中。 这意味着我可以在/Views/Shared文件夹中放置多个控制器使用的视图,而MVC会发现它们。
遵循布局的约定
布局的命名约定是使用文件前面加下划线(_
)字符,并且布局文件放在/Views/Shared文件夹中。 默认情况下,此布局将应用于所有视图/Views/_ViewStart.cshtml文件。 如果您不希望将默认布局应用于视图,则可以更改ViewStart.cshtml中的设置(或完全删除该文件)以在视图中指定其他布局,如下所示:
@{ Layout = "~/_MyLayout.cshtml"; }
或者你可以不使用任何布局,像下面这样:
@{ Layout = null; }
总结
在本章中,我向您介绍了MVC架构模式,并将其与您之前看到或听到的其他模式进行了比较。 我讨论了领域模型的重要性并引入了依赖注入,它允许您去分离组件以强制应用程序的各个部分之间的严格分隔。 在下一章中,我将解释Visual Studio MVC项目的结构,并描述MVC Web应用程序开发中使用的基本C#语言特性。