第三章 MVC模式\项目和约定

在深入探究ASP.NET Core MVC的细节之前, 应当熟悉MVC设计模式及其思想, 以及它如何转化成ASP.NET Core MVC项目. 你可能已经了解了我在前面提到的一些观点和约定, 特别是在进行过高级ASP.NET开发的情况下. 如果没有的话, 建议详细阅读这一章, 对MVC原理的较好理解可以帮助你掌握ASP.NET Core MVC框架特性.

MVC的历史

术语MVC(model-view-controller, 模型-视图-控制器)从二十世纪七十年代末就开始使用, 源于Xerox PARC的Smalltalk项目, 设想用于开发一些早期的GUI应用. 最初的MVC模式的一些小细节和Smalltalk语言的概念密切相关, 如screenstools, 但更广泛的概念仍然适用于应用程序, 特别是web应用.

理解MVC模式

从上层来看, MVC模式意味着MVC应用会被分为至少三层:

  • Models, 模型: 存储及表示用户工作所需的数据
  • Views, 视图: 用于在UI中渲染模型的一部分
  • Controllers, 控制器: 处理到来的请求, 在模型上执行操作, 并将视图呈现给用户

MVC架构的每个部分都是定义良好的\自包含的, 这被称为”关注点分离”. 处理模型中数据的逻辑只包含在模型中, 显示数据的逻辑只包含在视图中, 处理用户请求和输入的逻辑只包含在控制器中. 在每个部分之间有一个清晰和划分, 应用程序就更容易维护和扩展, 不管它有多大.

理解模型

模型容纳用户的工作数据. 有两种广义类型的模型:

  • view model 视图模型: 仅仅将数据从控制器传递到视图
  • domain model 领域模型: 包含业务领域的数据, 操作, 转换, 创建\存储并操纵数据的规则, 统称为模型逻辑

模型是应用程序工作环境的定义. 例如在银行应用程序中, 模型表示应用程序支持的银行中的所有内容, 如帐户, 账簿, 客户的信用限额, 以及可用于操纵数据的操作(如从账户中存取款). 模型还负责维护数据的整体状态和一致性, 如确保所有事务被记录到账簿上, 客户取款不能超过他的权限范围或比银行总存款更多.
对于MVC模式的每个组件, 我将描述它应当包含什么, 不包含什么.
MVC模式应用程序的模型应当

  • 包含领域数据
  • 包含创建\管理\修改领域数据的逻辑
  • 提供暴露模型数据和操作的干净API

MVC模式应用程序的模型不应当

  • 暴露模型数据如何被存储和管理的细节(换句话说, 数据的存储机制不应当被暴露给控制器和视图)
  • 包含基于UI的转换模型的逻辑(这是控制器的工作)
  • 包含向用户展示数据的工作(这是视图的工作)

确保模型独立于控制器和视图有两个好处, 一个是可以很容易对逻辑进行测试(我在第七章描述了单元测试), 另一个是维护和增强应用程序的功能更简单.

提示: 许多刚接触MVC的开发者被在数据模型中包含逻辑的理念搞懵了, 认为MVC模式的目标是将数据和逻辑分离. 理解错了, MVC模式的目标是将程序划分为三个功能区域, 每个区域都既包含逻辑也包含数据. 目的不是消除模型中的逻辑, 而是确保模型中只包含创建和管理模型数据的逻辑.

理解控制器

控制器是MVC模式的城乡结合部, 沟通数据模型和视图. 控制器定义行为, 行为提供操作数据的业务逻辑, 并将数据传递给视图.
MVC模式应用程序的控制器应当

  • 包含基于UI的用于更新模型的行为

MVC模式应用程序的控制器不应当

  • 包含管理数据外观的逻辑(这是视图的工作)
  • 包含管理持久数据的逻辑(这是模型的工作)

理解视图

视图包含将数据展示给用户的逻辑, 以及从用户处收集数据并发送给控制器行为进行处理.
MVC模式应用程序的视图应当

  • 包含要向用户展示数据的逻辑和标记

MVC模式应用程序的视图不应当

  • 包含复杂的逻辑(最好放在控制器中)
  • 包含创建\存储\操纵领域模型的逻辑

视图可以包含一些逻辑, 但必须是简单的, 而且需要谨慎. 将复杂逻辑放在视图中将是应用程序更难测试和维护.

MVC的ASP.NET 实现

框架如其名, ASP.NET Core MVC改编了抽象的MVC模式, 使其适应ASP.NET和C#开发. 在ASP.NET Core MVC中, 控制器是C#类, 通常继承自Microsoft.AspNetCore.Mvc.Controller类. 控制器类中的公有方法称为”行为方法”(action method), 与一个URL相关联. 当发送一个与行为关联的URL请求时, 执行行为方法中的语句, 操纵领域模型, 并向用户展示视图. 下图是控制器\模型\视图之间的交互

这里写图片描述

ASP.NET Core MVC使用Razor视图引擎, 用于处理视图, 生成发回浏览器的响应. Razor视图是包含C#逻辑的HTML模版, 用于处理模型数据, 生成响应数据变化的动态内容. 我将在第五章中解释Razor如何工作.
ASP.NET Core MVC在领域模型的实现上没有任何约束. 你可以使用常规C#对象创建模型, 使用任何数据库, ORM和.NET支持的其他数据工具来实现持久性.

!理解单页应用

Web应用的开发趋向于将浏览器视为简单的显示设备, 呈现HTML并相应鼠标点击. 这是众所周知的Web应用的往返样式. 每次用户单机一个连接, 一个HTTP请求就会发送到ASP.NET Core MVC应用程序. 在应用程序中, 控制器会选择Razor视图并将其发回浏览器. 这样就可以向用户显示一个新的HTML页面. 所有的逻辑\数据\状态都驻留在服务器中, 这意味着你不需要过多关注浏览器, 只需要保证浏览器能够正常显示HTML, 从而简化了开发.

相比之下, 单页应用程序将浏览器置入程序平台中, 服务器负责管理应用程序的数据, 通过Js代码请求数据\显示数据\响应用户交互. 在单页应用中, 模型\视图\控制器的职责在浏览器和服务器之间共享, ASP.NET Core MVC部分仅提供数据的访问接口(而不是完整的页面), 使用如Angular或React这样的JS框架来进行数据查询和展示.

单页应用程序比往返应用程序的响应更快, 但创建起来更复杂, 既需要C#技能, 也需要JS技能才能进行有效开发. 在第二十章中我演示了如何使用MVC在单页应用中提供数据, 但没有演示单页应用开发. 我更推崇的JS框架是Angular, 我在《Pro Angular》中有描述, 如果想在MVC中使用Angular, 可以看我的书籍《Essential Angular for ASP.NET Core MVC》

MVC和其他设计模式的对比

MVC当然不是软件开发的唯一模式, 还有许多更流行的模式. 在研究其他替代模式时也可以加深对MVC的理解. 接下来, 我将简要介绍另外几种模式, 并将他们同MVC对比. 一些是MVC模式的变种, 另一些则完全无关.
我不认为MVC是万能的模式, 具体问题需要具体分析. 有些情况下, 一些竞争的模式甚至比MVC更好. 在选择设计模式时, 需要做出审慎明智的选择.

Smart UI模式

最常用的设计模式之一是智能用户界面(smart UI), 大多数码农在职业生涯中都创建过smart UI应用. 如果你用过Windows Forms或ASP.NET Web Forms, 那么你也用过.
要构建一个smart UI应用, 开发者通常通过拖拽控件的方式来构造一个用户界面. 控件通过发出事件(如按下按钮, 键盘输入, 鼠标移动)来报告与用户的交互. 开发者在一系列事件处理程序中添加代码来相应这些事件, 这是处理特定控件的特定事件时调用的小代码块. 这将创建一个单片应用程序, 处理用户界面和业务的代码都混合在一起, 没有任何关注点的分离. 定义数据输入的可接受值\查询数据或修改用户帐户的代码最终会以小块的形式结束, 并与事件预期的顺序结合在一起.

这里写图片描述

Smart UI在处理小项目时很理想, 因为你可以快速得到一些好的成果(相比之下, MVC在交付之前需要一定的时间投资). Smart UI也适合用户界面原型的制作. UI设计工具非常棒, 如果你与客户坐在一起, 想要获取界面的外观和流程的需求, 这些工具可以快速生成并验证一些想法.
Smart UI的最大缺点是难以维护和扩展. 将领域模型和业务逻辑与用户界面混合导致了冗余, 在添加新组件时需要复制粘贴旧的业务逻辑代码. 找到所有重复的地方并进行修改很困难, 添加新功能而不破坏现有功能几乎是不可能的. 测试一个智能UI应用程序也很困难, 唯一的方法是模拟用户交互, 这远远不能满足需求, 也不能提供完整的测试覆盖范围.
在MVC中, smart UI常常被称为”anti-pattern”, 应该不惜一切代价避免这种情况. 这种反感来自, 人们花了一半的职业生涯尝试开发和维护smart UI应用程序, 结果却一团糟, 后来人们才发现MVC是一个更好的方案.
总的来说, 拒绝smart UI模式是错误的. 并非smart UI中的所有内容都那么糟糕, smart UI应用开发很快, 即使最没有经验的程序员也能在短短几个小时内做出像模像样的东西. smart UI最大的缺点——可维护性——在小型开发工作中不会出现. 如果正在制作一些简单的工具, smart UI应用程序可能是一个很好的解决方案.

模型-视图架构

在smart UI应用中, 维护的困难主要出现在业务逻辑中, 添加或修改功能的过程非常困难. 模型-视图架构提供了这方面的改进, 将业务逻辑拉到一个单独的领域模型中. 在此过程中, 数据\流程和规则都集中在应用程序的一个部分中.

这里写图片描述

模型-视图架构是对单片的smart UI模式的改进, 更容易维护, 但是出现了两个问题. 首先, 由于UI和领域模型是紧密耦合的, 因此很难对两者进行单元测试. 第二个问题来自实践, 而不是模式的定义. 模型通常包含大量的数据访问代码, 这意味着数据模型并不仅仅包含业务数据\操作和规则.

经典的三层架构

为了解决模型-视图架构的问题, 三层(three-tier/three-layer)模式将持久性代码从领域模型中分离, 并放入数据访问层中(data acces layer, DAL)中.

这里写图片描述

三层架构是业务应用程序中使用最广泛的模式. 它没有限制如何使用UI, 并在不太复杂的情况下提供了良好的关注点分离. 而且只要稍加注意就可以创建DAL, 这样单元测试就相对容易了. 在典型的三层应用程序和MVC模式之间有明显的相似之处, 不同之处在于当UI层直接耦合到GUI框架时(如Windows Forms或ASP.NET Web Forms)几乎不可能执行自动的单元测试. 而且由于三层结构的应用程序UI部分可能很复杂, 所以很多代码不能进行严格的测试.
在最坏的情况下, 三层模式在UI层中缺乏强制约束, 这意味着许多这样的应用程序都是伪装的smart UI应用程序, 并没有实现真正的关注点分离. 这将产生最坏的结果: 一个不可测试\不可维护\过于复杂的应用程序.

理解MVC变种

我已经描述了MVC应用的核心设计原则, 特别是如何与ASP.NET Core MVC结合. 一些人以不同的方式解释MVC模式的各个方面, 并添加\调整或以其他方式改变MVC, 以适应其项目的范围和主题. 我将简要概述MVC最流行的两种变体.

Model-View-Presenter模式

模型-视图-呈现器(MVP)是MVC的一个变体, 旨在更好地适应有状态的GUI平台(如Windows Forms和ASP.NET Core Forms). 这是一个有价值的尝试, 可以在没有引入新问题的情况下获得smart UI的最佳优点.
在此模式中, 呈现器和MVC控制器承担相同的责任, 但和状态视图关系更加紧密, 对用户的输入的值和操作可直接在UI组件中显示. 此模式有两种实现

  • 被动视图(passive view)实现: 视图不包含逻辑, 视图是直接由呈现器操作的UI控件容器
  • 监督控制器(supervising controller)实现: 视图承担一些呈现器的逻辑元素, 例如数据绑定, 并且已经从领域模型中获得了数据源.

这两种方式的区别在于视图的智能化程度. 无论如何, 呈现器从GUI框架中结构, 使呈现器逻辑更加简单, 更适合单元测试.

Model-View-View Model模式

模型-视图-视图模型(MVVM)模式是MVC的很新的一个变体, 它源于微软的WPF. 在MVVM模式中, 模型和视图承担和MVC中一样的责任. MVVM概念中的不同点是视图模型(view model), 用户界面的抽象表示. 视图模型通常是一个C#类, 它公开要在UI中显示的数据的属性, 和可以从UI中调用的数据的操作. 与MVC控制器不同, MVVM视图模型中没有视图(或特定的UI技术)存在. MVVM视图使用WPF绑定特性将视图中的控件的公开属性与视图模型的公开属性进行双向关联.

提示: MVC模式也使用属于”视图模型”, 但指的是一个仅从控制器向视图传递数据的模型类, 而不是领域模型(复杂的数据表示\操作\规则)

理解ASP.NET Core MVC项目

创建ASP.NET Core MVC项目时, VS会提供一些初始内容的选项. 这是为了简化新的开发者的学习过程, 并为常见的功能和任务采用一些节省时间的最佳实践. 我不喜欢这种千篇一律的项目或代码, 意图是好的, 但效果不佳. 我最喜欢ASP.NET和MVC的一点是可以灵活裁剪平台来适应我的开发风格. VS自动创建的项目\类\视图使我觉得必须按照其他人的风格来工作. 内容和配置也太普通乏味, 没有用处. 微软不可能知道需要什么样的应用程序, 所以涵盖了所有的基础方面, 我最终删除了这样的一般化方式的默认内容.
我的建议是从空项目开始逐渐添加需要的文件夹\文件和包. 这样不仅能了解MVC的工作方式, 还将完全控制应用内的内容.
但我的喜欢不应该影响你的开发, 你可能会发现模板更有用, 特别是ASP.NET开发新手, 还没有形成自己的开发风格. 你可能还会发现项目模版是有用的资源和想法的来源, 尽管在完全理解程序原理前应该谨慎添加功能.

创建项目

这里写图片描述

  • 空项目(Empty): 包含ASP.NET Core的管道, 但不包含MVC应用需要的库或配置
  • Web API: 包括ASP.NET Core和MVC, 其中包含一个示例应用程序, 演示如何使用API控制器(在第二十章中描述)从客户端接收并处理数据请求. 可以配置不同的用户验证模式
  • Web应用(MVC): 包括ASP.NET Core和MVC, 其中包含一个示例应用程序, 演示如何生成HTML内容. 可以配置不同的用户验证模式
  • Web应用: Razor页面的单页面应用程序框架, 允许代码和标记混合在一个文件中, 合并视图和控制器的角色, 为了开发简便放弃了MVC的一些好处
  • 其他: 提供单页面应用程序框架

项目模版可能会留下这样的印象: 需要遵循特定的路径来创建某种类型的应用程序, 但事实并不是, 你可以向使用任何模板创建的项目中添加所需的任何功能. 例如, 我在第二十章中解释了如何处理HTTP数据请求, 在第二十八到第三十章中解释了身份验证和授权, 这都是从空项目模板开始的.
项目模板之间的真正区别是VS在创建项目时添加的初始库\配置文件\代码和内容. 最简单的模板(Empty)和最复杂的模板(Web application(MVC))之间有很多不同之处, 如下图所示, 显示了创建项目之后的解决方案资源管理器。

这里写图片描述

左为empty, 右为mvc

MVC模板添加到项目中的额外文件令人望而生畏, 但其中一些只是占位符或常用功能的示例, 一些文件用来设置MVC或配置ASP.NET, 还有一些是前端库, 他们将被合并到生成的HTML. 文件列表现在可能看起来令人崩溃, 但在本书完成后你会明白一切.
无论使用哪个模版创建项目, 都会出现一些常见的文件夹和文件. 项目中的某些项地位特殊, 他们是硬编码到ASP.NET中或MVC中或VS提供支持的工具中. 其他的则受到大多数ASP.NET项目或MVC项目中使用的命名约定的约束. 下表中我描述了ASP.NET Core MVC项目中遇到的重要文件和文件夹, 其中有些文件和文件夹默认不存在于项目中, 但在后面的章节中我将介绍.

文件夹或文件 描述
/Areas 将大型应用程序划分成小块, 详见第十六章
/Components 定义用于显示自包含功能(如购物车)的视图组件类, 详见第二十二章
/Controllers 放置控制器类的地方, 这是一个约定, 控制器类都会被编译成同一个程序集, 详见第十七章
/Data 定义数据库上下文类的地方, 尽管我更喜欢放置在Models文件夹中, 详见第八章
/Data/Migrations EF Core迁移存储的位置, 详见第八到十一章
/Dependencies 项目依赖的包的信息. 第六章描述了包管理器
/Models 放置视图模型和领域模型的地方, 这是一个约定, 你可以在项目的任何位置或单独项目中定义模型类
/Views 包含视图和”部分视图”, 通常放置在以关联的控制器命名的文件夹中, 详见第二十一章
/Views/Shared 没有指定特定的控制器的布局或视图, 详见第二十一章
/Views/_ViewImports.cshtml 指定包含在Razor视图中的命名空间, 详见第五章; 也用于设置tag helper, 详见第二十四章
/Views/_ViewStart.cshtml 指定Razor引擎的默认布局, 详见第五章
/wwwroot 存储静态内容, 如css\图片, 也是Bower包管理器安装的JS和CSS包存储的地方
/appsettings.json 包含可针对不同环境定制的配置设置, 最常见的用途是定义数据库连接字符串和日志\调试设置, 详见第十四章
/bower.json 默认隐藏, 包含Bower包管理器安装的包列表, 详见第六章
/ProjectName.csproj C#项目配置, 包含项目的配置\应用程序所需的NuGet包, 详见第六章和第十四章
/Program.cs 配置了应用程序的托管平台, 详见第十四章
/Startup.cs 配置应用程序, 详见第十四章

理解MVC约定

在MVC项目中有两种约定. 第一种是关于如何构建项目的建议, 如通常将第三方JS包和CSS包放置在wwwroot/lib文件夹中, 但也可以自由更改到其他位置, 只要视图中的脚本和连接元素正确引用到您所设置的位置. 另一种约定源于”约定优于配置”(convention over configuration)的原则, 意味着不需要显示配置控制器和视图之间的关联, 只需要让文件遵循特定的命名约定, 一切就会正常工作, 但项目结构的灵活性会降低. 下面将解释在配置中使用的约定.

提示: 可以通过用自己的实现替换标准MVC组件来更改所有约定. 我在书中描述了替换组件的不同的方法, 来帮助解释MVC应用程序是如何工作的, 但在大多数项目中你都会遵循这些约定.

遵守控制器类约定

控制器类以Controller结尾, 例如ProductController, AdminController, HomeController, 在项目中的其他位置引用控制器的时候(如HTML helper方法), 只需要指定第一部分的名字(如Product), MVC会自动在末尾加上Controller.

提示: 你可以创建一个模型约定来改变它, 详见第三十一章.

遵守视图约定

视图被放入文件夹/Views/ControllerName中, 例如, 与控制器ProductController关联的一个视图位于Views/Product

提示: 省略了Controller部分, 是/Views/Product, 而不是/Views/ProductController

MVC期望一个行为的默认视图应该以该方法命名, 例如名为List的行为相关联的默认视图是List.cshtml. 因此, 对于ProductController.List(), 其关联的默认视图为/Views/Product/List.cshtml. 当调用无参View()方法时, 将调用默认视图, 如

    return View();

也可以指定视图名, 如

    return View("MyOtherView");

注意, 没有包含文件拓展名和视图的路径. 在查找视图时, MVC会现在以控制器命名的文件夹中查找, 然后在/Views/Shared文件夹查找, 这意味着可以在/Views/Shared文件夹中放置提供多个控制器使用的视图.

遵循布局约定

布局文件的命名约定是在文件前面加一个下划线(_), 布局文件放置在/Views/Shared文件夹中, /Views/_ViewStart.cshtml默认应用于所有视图. 如果不希望使用默认视图, 可以修改_ViewStart.cshtml中的设置(或直接删除文件)来指定视图使用另一个布局, 例如

@{
    Layout = "~/_MyLayout.cshtml";
}

或禁用一个给定视图的布局, 如

@{
    Layout = null;
}

总结

本章中, 我介绍了MVC架构模式, 将其同其他几种模式比较. 我讨论了领域模型的意义, 并引入了用于解耦的依赖注入(有吗??), 以实现应用程序各部分之间的分离. 下一章我将描述MVC web应用程序开发中使用的C#语言的基本特性.

猜你喜欢

转载自blog.csdn.net/crf_moonlight/article/details/81045515