零、前言
编写 web API 是最受欢迎的编程技能之一,因为它提供了轻量级 HTTP 服务,可以覆盖广泛的客户机。设计良好的 web API 可用于各种客户端,如桌面、web 和移动应用;作为其 HTTP 服务,它可以跨平台使用。
ASP.NET Web API 2 是构建基于 REST 的 API 的理想平台,被广泛采用,并取得了很大成功。微软通过引入跨平台的.NET Core 和跨平台的 ASP.NET Core 技术,进入了开源世界。
ASP.NET Core 为开发 web 应用开辟了一个激动人心、功能丰富且轻量级的新天地。有了这项新技术,我们不再局限于 Windows 操作系统的世界来构建应用。它确实是跨平台的,因为我们不再需要使用 VisualStudioIDE 来开发应用。
ASP.NET Core 为构建 web API 提供了一种非常创新的方法。在本书中,您将了解 ASP.NET Core 剖析,通过探索中间件的概念创建 web API,与数据库集成,应用各种安全机制,并在流行的 web UI 框架中使用它们。
本书考虑了经验丰富的开发人员和新开发人员。有开发 web API 的经验将是一个额外的优势,但这不是一个先决条件。它将帮助您构建一个真正跨平台的 ASP.NET Core Web API 并掌握它。在编写本书时,我们正在使用.NET Core 2.0 Preview 2 和 ASP.NET Core 2.0 Preview 2,以及 Visual Studio 2017 Preview 3,我们确实计划在 ASP.NET Core 2.0 的最终版本中更新本书。
这本书涵盖的内容
第一章微服务和面向服务架构简介,讨论了行业中面向服务架构的发展趋势,以及微服务架构带来了什么。
第 2 章理解 HTTP 和 REST更新了 web 架构的概念,描述了 HTTP 背后的核心技术和概念及其方法,并向您介绍 REST 架构风格。
第 3 章ASP.NET Core Web API 剖析带您踏上了解 Web API 被接受的旅程,并让您开始创建 ASP.NET Core Web API 并了解其剖析。
第 4 章控制器、动作和模型涵盖了请求如何与控制器交互、如何与控制器调度流程协同工作、如何定制控制器调度流程以及如何与动作方法结果协同工作的核心概念。
第 5 章实现路由,帮助您了解路由如何将传入 HTTP 请求映射到其相应控制器的动作方法。
第 6 章、中间件和过滤器深入探讨了 ASP.NET Core 的一个显著特征——中间件和过滤器。
第 7 章执行单元和集成测试介绍了如何为 web API 编写单元测试和执行集成测试。
第 8 章Web API 安全探讨了 Web API 的标识、身份验证和授权的概念。
第 9 章与数据库的集成,通过 EF 6、EF Core、Dapper 等 ORM 与各种数据库进行集成。
第 10 章错误处理、跟踪和日志记录探讨了 ASP.NET Core 内置的日志记录功能,并向您展示了如何编写高效的错误处理代码。
第 11 章优化与性能解释了编写 web API 的异步方式,以及如何应用缓存技术来提高 web API 的性能。
第 12 章托管部署在 IIS、单机版、Docker、Azure、Linux 等多种平台上部署 ASP.NET Core Web API。它展示了其真正的跨平台性质。
第 13 章、现代 Web 前端在 UI 框架中使用从前面章节开发的 Web API,如 Angular、Ionic、React 等。
这本书你需要什么
完成本书中给出的练习练习需要以下软件:
- Windows 7 或更高版本、任何 Linux 风格的计算机或 macOS
- .NET Core 2.0 预览版 2 SDK
- Visual Studio 2017 预览版 3(任何版本)
- 非 Windows 计算机的 Visual Studio 代码
- 用于 VisualStudio 代码的 OmniSharp
- NodeJS 构建现代 UI 框架
- SQLServerExpress 版
- 码头工人工具箱
- 邮递员:跨平台 REST 客户端
- 您最喜欢的浏览器
这本书是给谁的
本书面向希望掌握 ASP.NET Core(Web API)的.NET 开发人员,他们对以前的 ASP.NET Web API 略知一二,但对它没有深入的了解。您需要了解 VisualStudio 和 C#,并具备一些 HTML、CSS 和 JavaScript 知识。
习俗
在本书中,您将发现许多文本样式可以区分不同类型的信息。下面是这些风格的一些例子,并解释了它们的含义。
文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄如下所示:“运行dotnet build命令将执行例程构建并生成bin和obj文件夹。”
代码块设置如下:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
}
任何命令行输入或输出的编写方式如下:
docker run -it -d -p 85:80 packtcontantsAPI
新术语和重要词语以粗体显示。您在屏幕上(例如,在菜单或对话框中)看到的文字将显示如下文本:“打开 Visual Studio 2017 IDE,单击“新建项目”以打开“项目模板”对话框。”
Warnings or important notes appear like this. Tips and tricks appear like this.
读者反馈
我们欢迎读者的反馈。让我们知道你对这本书的看法你喜欢还是不喜欢。读者反馈对我们来说很重要,因为它可以帮助我们开发出您将真正从中获得最大收益的标题。要向我们发送总体反馈,只需发送电子邮件feedback@packtpub.com,并在邮件主题中提及该书的标题。如果您对某个主题有专业知识,并且您有兴趣撰写或贡献一本书,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
既然您是一本 Packt 图书的骄傲拥有者,我们有很多东西可以帮助您从购买中获得最大收益。
下载示例代码
您可以从您的帐户下载本书的示例代码文件 http://www.packtpub.com 。如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support 并注册,将文件直接通过电子邮件发送给您。您可以通过以下步骤下载代码文件:
- 使用您的电子邮件地址和密码登录或注册我们的网站。
- 将鼠标指针悬停在顶部的“支持”选项卡上。
- 点击代码下载和勘误表。
- 在搜索框中输入图书的名称。
- 选择要下载代码文件的书籍。
- 从您购买本书的下拉菜单中选择。
- 点击代码下载。
下载文件后,请确保使用以下最新版本解压或解压缩文件夹:
- WinRAR/7-Zip for Windows
- 适用于 Mac 的 Zipeg/iZip/UnRarX
- 适用于 Linux 的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上的https://github.com/PacktPublishing/Mastering-ASP.NET-Web-API 。我们在上还提供了丰富的书籍和视频目录中的其他代码包 https://github.com/PacktPublishing/ 。看看他们!
勘误表
虽然我们已尽一切努力确保内容的准确性,但错误确实会发生。如果您在我们的一本书中发现错误,可能是文本或代码中的错误,如果您能向我们报告,我们将不胜感激。通过这样做,您可以使其他读者免于沮丧,并帮助我们改进本书的后续版本。如果您发现任何错误,请访问进行报告 http://www.packtpub.com/submit-errata ,选择您的书籍,点击勘误表提交表单链接,然后输入勘误表的详细信息。一旦您的勘误表得到验证,您的提交将被接受,勘误表将上载到我们的网站或添加到该标题勘误表部分下的任何现有勘误表列表中。要查看之前提交的勘误表,请转至https://www.packtpub.com/books/content/support 并在搜索字段中输入图书名称。所需信息将出现在勘误表部分下。
盗版行为
在互联网上盗版版权材料是所有媒体都面临的一个持续问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上发现任何形式的非法复制品,请立即向我们提供地址或网站名称,以便我们采取补救措施。请致电copyright@packtpub.com与我们联系,并提供可疑盗版材料的链接。我们感谢您在保护我们的作者方面提供的帮助以及我们为您带来有价值内容的能力。
问题
如果您对本书的任何方面有任何问题,可以通过questions@packtpub.com与我们联系,我们将尽力解决该问题。
一、微服务和面向服务的架构简介
随着互联网可用性的增加,数据通信技术正在不断发展。架构的改进非常具有创新性、可扩展性,并且可以跨环境采用。需要在互联网上提供具有通用接口的软件组件,以便在不同平台和编程语言之间进行通信。
这导致了创建易于部署且具有可伸缩性的服务的概念,并通过 internet 公开这些服务。
服务功能设计被广泛采用;以服务的形式向异构客户机提供特性是一个好主意。这种使用服务的概念导致了SOA(面向服务的架构。
在本章中,我们将研究以下主题:
- SOA 中的服务
- 单片建筑
- 微服务简介
SOA 中的服务
服务是向系统内或系统外的其他软件提供功能的软件。
其他软件(客户端)可以是任何东西,从 web 应用(网站)到移动应用(本机或混合),或桌面应用,甚至是使用其他服务来执行特定类型功能的其他服务。
在电子商务网站上下文中,当用户下订单时,web 应用与服务通信,对数据库执行创建、读取、更新和删除(CRUD操作。
软件组件(客户端)和服务之间的通信通常通过具有某种通信协议的网络进行,例如,通过互联网与服务通信的移动应用。
以这种方式使用一个或多个服务的系统具有面向服务的架构。
这种架构背后的主要思想是,它不在每个客户端应用中使用模块,而是让我们使用一个或多个服务来为它们提供功能。这允许我们有许多使用相同功能的客户端应用。
SOA 之所以成功,是因为它具有以下特点:
- 它允许我们在需求增加时扩展软件,使其能够在多台服务器上拥有服务的副本,因此当流量进来时,负载平衡器将该请求重定向到服务的特定实例,我们可以拥有服务的多个实例。因此,当需求增加时,增加服务器上的实例数量可以帮助我们扩展它。
- SOA 拥有标准化的契约或接口。当客户端应用调用服务时,它通过调用方法调用服务。该方法的签名通常不会在服务更改时更改,因此只要合同和接口不变,我们就可以升级服务,而无需升级客户机。
- 事实上,服务是无状态的,因此当从网站向我们的服务发出请求时,该服务实例不必记住来自该特定客户的前一个请求。它基本上拥有请求中的所有信息,以便检索与服务中以前的请求相关联的所有数据,因此,服务不必记住客户端以前对该服务的特定实例进行的调用。
服务实现
SOA 因其服务的实现而广受欢迎,这些服务可以通过独立于操作系统平台和编程语言的标准 internet 协议访问。
来自开发人员 POV 的服务只不过是托管在 web 服务器上的 web 服务,使用SOAP(简单对象访问协议或 JSON 进行通信。很有意思的是,web 服务可以用作遗留系统的包装器,使它们能够实现网络功能。
实现服务(SOA)的一些流行技术如下:
- 基于WSDL(Web 服务描述语言和 SOAP 的 Web 服务
- 消息传递,例如,使用 ActiveMQ、JMS 和 RabbitMQ
- WCF(微软的 Web 服务实现)
- 阿帕奇节俭
- 巫师
- RESTful HTTP
当单片架构方法的经验证明比之前想象的更痛苦时,面向服务的架构开始获得动力。让我们简要了解什么是单片系统,以及它们导致采用 SOA 的缺点。
单片建筑
基于单片架构的系统在 SOA 或微服务运动之前就存在了。这些类型的系统与 SOA 试图实现的恰恰相反。
典型的单片系统是基于企业的应用,该应用可能是一个大型网站的形式,所有工作模块都打包在一个包中,也可能是一个与网站对话的服务的形式。它可以打包为部署在计算机上的大型可执行文件。
在这些系统中,我们向应用添加了不同的组件以保持增长;没有大小限制,也没有划分。总有一个包包含所有内容,因此,我们最终得到了一个庞大的代码库。
单片系统的高级架构图如下所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00072.jpeg
Typical Monolithic architecture
单片建筑的管理费用
从长远来看,企业在将单片架构应用于其系统时面临以下缺点:
- 由于代码库太大,团队花了更长的时间在应用中开发新功能。
- 大型系统的部署也可能具有挑战性,因为即使对于一个小的 bug 修复,我们也必须部署整个系统的新版本,因此,这会产生更大的风险。
- 这是一个庞大的代码库,因此,我们也只能使用一个技术堆栈。
- 这会降低整个系统的竞争力,因为我们不能轻易地采用可能给我们带来竞争优势的新技术。
- 由于代码在一个大的包中,我们可能还具有高度的耦合,这意味着如果在系统的一个部分中进行更改,它可能会影响系统的另一个部分,因为代码是相互交织的。这种耦合可能存在于模块之间,也可能存在于不同的服务之间。
- 扩大这项服务以满足需求效率很低。例如,如果系统的 Orders 模块有需求,我们必须创建整个包、整个服务的副本,以便仅扩展 Orders 部分。
- 需要购买功能更强大的服务器,才能高效地运行大量单片应用。
- 对如此庞大的代码库进行单元测试需要时间,而 QA 的回归测试也是一个耗时的过程。
The only one advantage that a Monolithic system has is the fact that we can run the entire code base on one machine, so, when developing and testing, we could probably replicate the entire environment on a machine.
单片系统的一个示例可以是 ASP.NET MVC 站点,其中站点本身是 UI 层,然后在业务层中,您拥有业务逻辑和数据访问层。多年来,如果我们继续采用同样的方法,那么它将成为一个整体系统。
引入微服务
微服务架构基本上是面向服务的架构。在使用面向服务的架构多年之后,软件开发人员已经意识到面向服务的架构应该是什么样子,这基本上就是微服务架构——它是面向服务架构的演变。
微服务是小型的、自治的服务,它可以很好地执行一项功能,同时也可以与其他服务协同工作。
Microservices 引入了一组新的附加设计原则,它们教会我们如何正确地调整服务的大小。以前,没有关于如何确定服务大小以及服务中包含什么的指导。传统的面向服务的架构导致了单一的大型服务,并且由于服务的大小,这些服务的扩展变得效率低下。
让我们看看使用微服务的优势。
轻量级但可扩展
微服务提供的服务具有更高的可扩展性和灵活性,并且可以在需要性能的领域提供高性能。
基于微服务架构的应用通常是由多个微服务驱动的应用,其中每一个都为应用的特定部分提供一组功能或一组相关功能。微服务架构通常为应用、客户端应用和客户端服务提供一组相关功能。
微服务架构还使用客户端和服务之间或两个或多个服务之间的轻量级通信机制。通信机制必须是轻量级和快速的,因为当一个微服务架构的系统执行一个事务时,它是一个由多个服务完成的分布式事务。因此,服务需要通过网络以快速有效的方式相互通信。
技术不可知论
微服务的应用接口,或者我们与微服务的通信方式,也需要与技术无关。这意味着服务需要使用开放式通信协议,这样它就不会指定客户端应用需要使用的技术。通过使用开放通信协议,例如 HTTP REST(基于 JSON),我们可以很容易地拥有一个与基于 Java 的微服务对话的.NET 客户端应用。
独立可变
微服务的另一个关键特征是它是独立可变的。我们可以升级、增强或修复特定的微服务,而无需更改任何客户端或系统内的任何其他服务。
在微服务架构中,每个微服务都有自己的数据存储。通过修改一个微服务,我们应该能够在系统中独立地部署该更改,而无需部署任何其他内容。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00073.jpeg
Sample Microservices architecture app
上图描绘了微服务系统的高级架构图。这是一个典型的电子商务系统示例,正如您在左侧看到的,客户的浏览器中运行着一个购物网站,或者它可能是一个使用 API 网关的移动应用。
浏览器通过 internet 连接到演示购物网站——演示购物网站可能是在 IIS 上运行的 ASP.NET MVC 网站。与网站的所有交互所需的所有处理实际上都是由大量后台运行的微服务执行的。
每个微服务都有一个焦点,或一组相关功能,有自己的数据存储,并且可以独立地更改和部署。例如,我们可以升级 Orders 服务,而无需升级此系统的任何其他部分。
每种类型的微服务也可能有多个实例。例如,如果 Orders 服务有需求,我们可能有几个 Orders 服务实例来满足需求。为了将来自购物网站的请求定向到订单服务的正确实例,我们有一个 API 网关,用于管理请求并将其路由到系统中正确的微服务。
因此,在本例中,当客户下订单时,购物网站可能会在这些服务中使用多个服务和多个功能来满足该交易。这就是为什么在微服务架构中,一个事务通常是一个分布式事务,因为该事务实际上由多个软件(即微服务)来满足。
微服务的好处
以下是微服务的好处:
- 微服务架构满足了快速响应变化的需要。当今软件市场竞争激烈。如果您的产品不能提供所需的功能,它将很快失去市场份额。
- 它满足了业务领域驱动设计的需要。应用的架构需要与组织结构或组织内业务功能的结构相匹配。
- 微服务架构使用自动化测试工具。我们已经看到,在微服务架构中,事务是分布式的,因此,一个事务在完成之前将由多个服务处理。这些服务之间的集成需要测试,手动测试这些微服务可能是一项相当复杂的任务。自动化测试工具帮助我们执行此集成测试,减少了手动负担。
- 云兼容的微服务可以减轻部署和发布管理的负担。
- 微服务架构提供了一个采用新技术的平台。由于系统由多个运动部件组成,因此我们可以轻松地将一个部件(即微服务)从一个技术堆栈更改为另一个技术堆栈,以获得竞争优势。
- 通过使用异步通信,分布式事务不必等待单个服务完成任务后才能完成。
- 微服务的开发时间更短。因为系统被分成更小的活动部分,我们可以单独处理一个活动部分,可以让团队同时处理不同的部分,而且因为微服务的规模较小,而且它们只有一个焦点,所以团队在范围方面不必太担心。
- 微服务架构还为我们提供了更多的正常运行时间,因为在升级系统时,我们可能会一次部署一个微服务,而不会影响系统的其余部分。
Netflix adopted the Microservices architecture; the lessons learnt on architectural designs are summarized in this link along with a video: https://www.nginx.com/blog/microservices-at-netflix-architectural-best-practices/.
总结
在过去的十年中,随着互联网带宽、机器处理能力、更好的框架等方面的改进,建筑服务的发展经历了许多变化。
从开发人员的角度来看,微服务是使用 ASP.NET、Java、PHP 或其他工具的基于 REST 的 Web API。在接下来的章节中,我们将学习开发基于 ASP.NET Core 的 Web API 应用的各个方面。
二、理解 HTTP 和 REST
REST 表示代表性状态转移。REST 架构风格是 Roy T.Fielding 的一篇博士论文,题为架构风格和基于网络的软件设计。这篇论文在经过 6 年的研究后于 2000 年首次发表。我们可以感谢菲尔丁先生的研究工作和发现。
现代的 API 是以 REST 为模型的,你会听到人们提到,它不是 RESTful 或被质疑的,你的 API 是 RESTful 吗?
要创建定义良好的 API 并对其建模,您需要对 REST 有充分的了解。出于这个原因,我们将深入研究 Roy T.Fielding 的研究。
罗伊·T·菲尔丁着手解决 1993 年出现的几个问题。许多作者在网络上发表他们的作品,他们希望合作。网络成为了一个分享和讨论研究工作的好地方。然而,它一流行就变得麻烦了。
就文件的发布方式和编辑方式而言,似乎缺少标准。还有一些与基础设施和速度有关的问题,编辑和访问文档的速度很慢。
在本章中,我们将探讨以下主题:
- 软件架构
- 休息原则
- REST 建筑元素
- 超文本传输协议
- HTTP/2
- 理查森成熟度模型
软件架构
软件架构(architecture)是软件系统在运行阶段的运行时元素的抽象。一个系统可能由多个抽象层次和多个操作阶段组成,每个阶段都有自己的软件架构。
软件架构由架构元素、组件、连接器和数据的配置定义,这些元素、组件、连接器和数据在它们的关系中受到约束,以实现所需的架构属性集:
- 组件:是软件指令和内部状态的抽象单元,通过接口提供数据转换
- 连接器:这是一种抽象机制,用于调解组件之间的通信、协调或协作
- 数据:这是一个信息元素,通过其连接器从组件传输或由组件接收
REST 架构样式是几种网络架构的组合:
-
数据流样式:
- 管道和过滤器
- 均匀管和过滤器
-
复制样式:
- 复制存储库
- 隐藏物
-
分层样式:
- 客户端服务器
- 分层系统和分层客户机服务器
- 客户端无状态服务器
- 客户端缓存无状态服务器
- 分层客户端缓存无状态服务器
- 远程会话
- 远程数据访问
-
移动代码样式:
- 虚拟机
- 远程评估
- 按需编码
- 分层按需代码客户端缓存无状态服务器
- 移动代理
-
点对点样式:
- 基于事件的集成
- C2
- 分布式对象
- 代理分布式对象
休息原则
REST 的建模方法是从零开始,然后添加约束。我们将对软件架构应用约束,您的架构将变得 RESTful。
客户机-服务器
注意,在 Roy T.Fielding 的整个工作中,他没有提到 REST 必须应用于 HTTP 协议。在我们的例子中,客户端服务器将作为浏览器作为客户端,IIS 作为服务器。
注意,客户机和服务器的分离允许抽象。这两个组件可以独立构建,也可以独立部署。
无国籍
下一个要添加的约束是无状态的。服务器不应包含任何工作流状态。这样,客户机就是其所需信息的驱动程序。当客户端向服务器请求数据时,客户端需要将所有相关信息传递给服务器。这种设计软件的方法创建了一个抽象,其中服务器不知道客户机;它创造了一个松散的耦合设计,这有利于改变。在本章后面,我们将通过扩展幂等概念来进一步研究无状态。
客户端必须跟踪其状态。缺点是客户机必须在每次请求时向服务器发送更多数据。
拥有无状态服务器允许扩展,因为服务器不存储任何特定于客户端的数据。
隐藏物
缓存是下一个约束。每当服务器传输不会更改的数据时,我们将这些数据称为静态数据。服务器可以缓存数据。
当发出第一个请求时,服务器将访问数据库以获取数据。然后,应将该数据缓存为应用层。对该数据的每个后续请求都将从缓存中提取,从而将服务器的请求保存到数据库中,从而更快地将响应返回到客户端。
统一界面
这是使 REST 不同于其他网络架构模式的约束。组件公开的接口是通用的。服务器不了解其消费者。它以相同的方式处理来自客户端的所有请求。您得到的是粗粒度的数据,因为并非所有的消费者都需要这样数量的数据。
要获得统一的界面,必须应用四个约束:
- 确定资源
- 操纵资源
- 自描述性消息
- 作为应用状态引擎的超媒体
我们稍后将研究这些问题。
分层系统
通过对组件进行分层,我们确保每个组件不知道其邻居连接到的层。这促进了良好的安全性,以便有良好的边界墙。它还允许在您的架构中使用旧系统时对其进行保护,并允许您保护新系统:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00074.jpeg
With the layered approach, it leads to many hops between systems, but you have the security boundaries, and components can be updated individually.
按需编码
这可能是 REST 最不受欢迎的特性。它允许服务器通过客户端可以执行的小程序或脚本向客户端提供代码。这允许服务器在部署后向客户端提供更多功能。约束是可选的,我们将不详细探讨它。
REST 建筑元素
如前所述,REST 不是一个协议,可以在没有实现的情况下讨论它。REST 的关键元素是向组件、连接器和数据添加约束的能力。
数据元素
选择需要将数据从服务器传输到客户端的超链接时,客户端需要解释数据并将其呈现为用户所需的格式。REST 原则是如何做到这一点的?REST 组件将数据和元数据传输到客户机,并提供帮助客户机组合其请求的资源的说明:
| 数据元 | 现代网络示例 |
| 资源 | 超文本引用的预期概念目标 |
| 资源标识符 | URL,URN |
| 代表 | HTML 文档,JPEG 图像 |
| 表示元数据 | 媒体类型,上次修改时间 |
| 资源元数据 | 源链接、替换、更改 |
| 控制数据 | 如果自之后进行了修改,则缓存控制 |
资源和资源标识符
资源是对您希望共享的任何信息的引用。它可以是您希望与朋友共享的图片或文档。罗伊·T·菲尔丁非常准确地总结了一个资源。资源是到一组实体的概念映射,而不是在任何特定时间点对应于映射的实体。更准确地说,资源 R 是一个随时间变化的隶属函数Mr(r),对于时间 t,它映射到一组等价的实体或值。集合中的值可以是资源表示和/或资源标识符。
当在组件之间使用资源时,REST 使用资源标识符来知道它是哪个资源。
在组件之间使用资源时,您的资源应该有一个资源标识符,REST 使用该标识符来标识您的资源。
陈述
表示是要共享的数据和与其关联的元数据的组合。表示的格式称为媒体类型。本章后面将用一些具体的例子更详细地讨论媒体。当服务器发送一些数据供客户端渲染时,媒体类型很重要;理想情况下,服务器将首先发送媒体类型,该类型将向客户端描述应如何呈现数据。当客户机接收到数据时,它可以开始呈现表示,从而获得更好的用户体验。这与客户端接收所有数据,然后接收有关如何呈现表示的指示进行比较。
连接器
连接器的类型有客户端、服务器、缓存、解析器和隧道。您可以将连接器视为接口。它们抽象了组件的通信方式。在 REST 架构中,连接器的工作是支持检索资源表示以及公开资源。其余的是无国籍的;每个请求都必须包含服务器处理来自客户端的请求所需的所有信息。
让我们看看 REST 用来处理请求的模型。该请求可以与存储过程进行比较:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00076.jpeg
Control Data defines the purpose of a message between components, such as the action being requested or the meaning of a response.
-Roy T.Fielding 的《架构风格和基于网络的软件架构设计》,第 5.2.1.2 节,第 109 页
组件
REST 架构中的一个组件是客户端上的 web 浏览器和服务器上的 IIS。
超文本传输协议
HTTP 代表超文本传输协议。第一个版本是 0.9;然后是 1.0 版。
1.0 和 1.1 之间的关键区别在于客户端与服务器建立连接,并且该连接被重用,而在 HTTP 1.0 中,该连接被丢弃,对于每个请求,都会创建一个新的连接。HTTP 1.1 也是通过将 REST 约束应用于 1.0 而派生的。
基本 HTTP 消息由头和正文组成。
当客户端与服务器通信时,它通过 HTTP 进行通信。服务器用消息和代码响应客户机。
HTTP/1.1 状态代码
有一系列广泛的状态代码,向客户端指示服务器处理的请求发生了什么:
-
2xx:成功 -
200:好的 -
201:已创建 -
3xx:重定向 -
4xx:客户端错误 -
400:请求错误 -
401:未经授权 -
403:禁止 -
404:未找到 -
409:冲突 -
5xx:服务器错误 -
500:内部服务器错误
我们将集中讨论最常见的代码,以及在本书后面实现 API 时将使用的代码。
API 示例
我已经使用 GithubAPI 来显示基本的 HTTP 方法。如果您希望探索 API,可以注册到 GitHub 并获得身份验证令牌。在下一章中,我们将创建自己的 API。在这些示例中,我们充当 API 的使用者。在这些示例中,我使用 Fiddler 发出请求。你可以使用任何你喜欢的工具;其他常用的工具是 Postman,它内置在 Chrome 浏览器或高级 Rest 客户端中。幂等元是 RESTAPI 使用的术语;简单地说,当您调用一个方法时,无论您调用它多少次,它都将返回相同的数据。在下面的示例中,我将列出哪些方法是幂等的。
HTTP POST 示例
我们的 HTTP 方法是 POST 和https://api.github.com/gists 是我们的资源。我们在请求中也有一个头值。User-Agent为表头键,取值为Awesome-Octocat-App。这是文档中指定的内容。
您可以在以下屏幕截图中记录请求正文:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00077.jpeg
这是我们对POST方法的要求。
我们对这一请求的回应如下所示。服务器已响应201,表示我们的请求有效,服务器已成功执行操作:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00055.jpeg
服务器还向我们发回一个资源。一个新的资源已经诞生,我们可以从中获取数据。
POST不是幂等的。REST 世界中的幂等式意味着,作为客户机,当我多次调用端点时,我希望收到相同的行为,或者希望返回相同的数据。考虑一个示例,您必须创建一个与唯一电子邮件地址的联系人。当您第一次使用此电子邮件地址和其他联系方式呼叫POST时,服务器将以201进行响应,这意味着已经创建了联系人并发布了唯一的资源,您可以在其中获取该数据。
如果您使用相同的电子邮件地址调用POST方法,会发生什么?服务器应返回冲突,409。该电子邮件存在于数据存储中。所以POST不是幂等的:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00054.jpeg
HTTP 获取示例
使用来自服务器的资源,我们对资源执行GET:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00068.jpeg
服务器以200状态响应,状态为 OK:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00053.jpeg
服务器返回我们请求的数据:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00052.jpeg
GET是幂等的,当我们发出第一次请求时,我们得到一个响应。发出相同的请求将返回相同的响应。这也与无国籍的 REST 原则有关。为了让这个GET请求返回相同的数据,服务器应该是无状态的。
HTTP PUT 示例
我们可以使用以下 URL 对我们的表示进行更新。注意 HTTP 动词PUT。
文档中说,我们可以在下面的资源中调用PUT方法,并将/star作为 URI 的一部分。PUT用于修改我们的表述。通常,PUT会有一个主体。在 GitHub 的 GistAPI 中,他们简化了它。一般来说,PUT的概念与POST相似,只是 URI 包含调用POST方法时收到的标识符:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00050.jpeg
当资源不存在时,PUT的行为类似于POST。在我们来自POST的示例中,如果您必须在第一次呼叫PUT时使用POST请求来创建联系人,那么您将收到201,通知您资源已创建。然后,如果您必须再次在PUT上调用请求,您将返回200并获得相同的数据。这样,PUT就是幂等的。
HTTP 删除示例
DELETE与GET非常相似。我们的 HTTP 方法是DELETE,我们想撤销我们用PUT创建的星 put。DELETE通常没有主体:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00051.jpeg
DELETE是幂等的;当您呼叫DELETE时,您将返回200,表示该资源已被删除。再次发出此请求将导致404,未找到资源,因为数据已被删除。再次拨打此电话将导致404。虽然响应已从200更改为404,但您仍然可以恢复相同的行为,服务器也没有做任何不同的事情。
HTTP 的第 2 版
HTTP/2 是对 HTTP 1.1 的优化。许多浏览器已经支持 HTTP/2;你的 Chrome 浏览器已经做到了。
HTTP/2 是两个规范的组合:超文本传输协议版本 2(RFC7540)和 HTTP2 的 HPACK-Header 压缩(RFC7541)。
在传输层安全(TLS上使用 HTTP/2 时,使用“h2”表示协议。
当通过明文 TCP 使用 HTTP/2 或 HTTP.1.1 升级时,使用“h2c”字符串。
GET请求的示例如下:
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
-RCF 7540 第 3.2 节
此请求来自不知道是否支持 HTTP/2 的客户端。它发出 HTTP 1.1 请求,但在标头“h2c”中包含一个升级字段,以及至少一个 HTTP2 设置标头字段:
A server that does not support HTTP/2 will respond as follows:
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
-RCF 7540 第 3.2 节
这看起来像一个常规的 HTTP/1.1 响应:
A server that does support HTTP/2 will respond as follows:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
[ HTTP/2 connection …
-RCF 7540 第 3.2 节
在 HTTP2 中引入了一个框架作为基本单元。您可以将帧视为通过导线传输的数据包。对于请求和响应,使用HEADER和DATA帧作为构建块,对于 HTTP/2 特性,使用SETTINGS、WINDOWS_UPDATE和PUSH_PROMISE帧。
单连接
服务器和客户端都可以使用服务器和客户端之间的单个连接来传输多个请求。假设您有一个包含多个组件的页面,所有这些组件都向服务器发出独立请求,比如说,一个将获得今天的天气,一个将获得最新的股票价格,还有一个将获得最新的标题。它们都可以通过一个连接而不是三个单独的连接来实现。这也适用于服务器。你最终得到的是更少的连接被创建。
服务器推送
服务器可以将数据推送到客户端。当客户机从服务器请求数据时,服务器可以确定客户机还需要一些其他数据。服务器可以将此数据推送到客户端。客户端总是可以通过向服务器发送应禁用推送数据的信号来拒绝数据。服务器发送给客户端的数据称为PUSH_PROMISE帧。如果客户端实现了 HTTP 缓存,则数据存储在客户端缓存中。
多路复用和流
流就像一个隧道,有许多汽车在两个方向上通过,汽车被帧取代,流在客户端和服务器之间是独立的。在 HTTP/2 中,一个连接可以有多个流,来自一个请求的帧分布在多个流上,尽管帧的顺序很重要。
这是对 HTTP 1.1 的重大改进,HTTP 1.1 使用多个连接来呈现单个页面。
流优先级
拥有多个流是很好的,但是有时候,您希望一个流在另一个流之前被寻址。在 HTPP/2 中,客户端可以在HEADERS帧中指定流的优先级。客户端可以使用Priority帧更改流的优先级。通过这种方式,客户机可以向其对等方指示它希望如何处理其请求。
二进制消息
与文本相比,采用二进制格式的消息处理速度更快。由于它们是有线传输的本机二进制格式,因此不需要通过 TCP 协议将文本转换为二进制。
头压缩
随着 web 的发展,更多的数据从服务器发送到客户端,从客户端发送到服务器。HTTP 1.1 不压缩头字段。HTTP 在 TCP 上工作,并通过此连接发送请求,其中头文件较大且包含冗余数据。TCP 采用网络拥塞避免算法实现的慢启动,该算法将数据包放置在网络上。如果报头被压缩,更多的数据包可以通过网络发送。HTTP/2 通过报头压缩修复了这个问题,报头压缩利用了 TCP,从而提高了数据传输速度。
媒体类型
通常称为MIME(多用途互联网邮件扩展类型),媒体类型用于标识 HTTP 消息体的格式。媒体类型为{type/subtype}格式;举例如下:
- 文本/html
- 图像/png
- 音频/mpeg
- 视频/视频
请求可以如下所示:
GET:
Host:
Accept:application/json, text/javascript
客户端正在指定它可以接收数据的格式。
理查森成熟度模型
理查森成熟度模型(RMM由 Leonard Richardson 开发。通常称为 RMM,用于升级 API 的标准。
0 级
这是传统的基于 soap 的 web 服务或 XML-RPC 服务。它使用 HTTP,但有一个方法和一个 URI。此方法通常为POST,将返回大量数据集。我相信我们所有人都曾经使用过这种类型的 web 服务,或者在某个时候可能会遇到它。整个数据库作为数据集包装在此输出中。
一级
资源是公开的,但您仍然有一个 HTTP 方法。如果您处于级别 0,那么更改 web 服务以返回资源将使您从级别 0 转到级别 1。您仍然有一个 HTTP 方法,但当调用您的方法时,您的服务将传回一个资源:
Request:
POST
diet/ate
Response
diet/ate/12789
Request:
POST
diet/ate
Response:
diet/ate/99000
仍然有一个端点diet/ate,它返回许多资源。
二级
级别 2 用于使用 HTTP 谓词。因此,在级别 1 中,我们介绍了资源,级别 2 介绍了动词。
使用前面的例子,当你在上午 10 点发布你吃的东西时,服务器会给你一个资源。使用此资源,您可以在该资源上执行GET,并查看您在上午 10 点吃了什么的详细信息:
GET: diet/ate/12789
Response
{
'time':'10:00',
'apple':'1',
'water':'2'
}
然后您可以使用PUT更新这些详细信息;请注意,我们使用的是同一资源。
请求如下:
PUT: diet/ate/12789
{
'time':'10:00',
'tea':'1',
'muffin':'3'
}
如果您稍后意识到您在上午 10 点没有吃饭,您也可以删除此资源:
DEL : diet/ate/12789
我们使用相同的资源,但使用了不同的动词。
当我们在 1 级创建资源时,我们将POST更改为在创建资源时返回 201,如果资源存在,则返回409冲突。
第 2 级部分使用响应代码,不会在每次操作中返回200。
三级
在第三级,超媒体被引入我们的响应中,通常称为HATEOAS(超文本作为应用状态的引擎)。
让我们回到POST示例:
POST : diet/ate
Response:
{
"id":"12789",
"links":[{
"rel":"self",
"href":"http://yoursitename/diet/12789
},
{
"Rel":"self",
"href":"http://yoursitename/diet/12789"
},
"rel":"rating",
"href":"http://yoursitename/diet/12789/rating/"
]
}
链接的要点是,它让消费者知道它可以执行哪些操作。
虽然两个端点看起来相同,但消费者会发现一个是DELETE,另一个是PUT。
最后一个链接是对您添加的膳食进行评分的资源。
总结
我们研究了 REST 的定义以及 REST 是如何派生出来的。当您查看 REST 架构时,您应该能够将其分为三类,正如 Roy T.Fielding 所解释的那样。一个是流程视图,它描述了数据如何从客户机流向多个组件。第二,连接器视图专门用于在特定于资源和资源标识的组件之间交换消息。第三,我们称之为表示的数据如何从服务器传输到客户端的数据视图。对 REST 原则有一个很好的理解是非常重要的,REST 被应用于 HTTP1.0,以便派生 HTTP1.1。
HTTP 是 REST 原则的一个活生生的例子。像GET和POST这样的动作是无状态的,这是休息的原则。这些示例展示了如何构造 HTTP 请求以及服务器作为响应返回的内容。有了 HTTP/2,我们就有了新的特性,这使得我们的传输速度更快,应用响应更快。
Richardson 成熟度模型解释了 API 是如何分类的;作为一名开发人员,您应该以 3 级模型为目标。如果您是 API 的消费者,可能需要在几个选项中进行选择。RMM 将帮助您做出明智的决定。
在本章中,我们没有关注具体的技术;在下一章中,我们将深入研究 ASP.NET Core 及其作为框架提供的内容,以构建 web API。
三、ASP.NET Core Web API 剖析
本章首先简要回顾一下 MVC。当我们使用 web API 时,您可能会惊讶于为什么我们需要 MVC。这是因为 ASP.NET Web API 是基于控制器、模型和视图的 MVC 原则设计的(在 Web API 的情况下,返回的响应可以被视为一个无面视图)。
本章的重点是了解为什么我们需要 Web API 形式的基于 HTTP 的轻量级服务技术,它的发展以满足不断变化的行业需求,以及 Microsoft 以.NET Core 和 ASP.NET Core 应用的形式进入开源世界,并且不局限于开发 ASP.NET web 应用的 Windows 操作系统世界。
在本章中,我们将研究以下主题:
- 快速回顾 MVC 框架
- web API 的诞生及其发展
- NET Core 简介
- ASP.NET Core 架构概述
- 使用 Visual Studio IDE 创建 ASP.NET Core 项目
- 在 Linux/macOS 中创建 ASP.NET Core 项目
- 检查 ASP.NET Core 项目文件和结构
- 理解请求处理
- MVC 与 webapi 的统一
- 运行 ASP.NET Core Web API
快速回顾 MVC 框架
模型视图控制器(MVC)是一种功能强大且优雅的分离应用中关注点的方法,它非常适合于 web 应用。
对于 ASP.NET MVC,MVC 代表以下内容:
- 模型(M):这些是表示域模型的类。其中大多数表示存储在数据库中的数据,例如,员工、客户等。
- 视图(V):这是一个动态生成的 HTML 页面作为模板。
- 控制器(C):这是一个管理视图和模型之间交互的类。视图上的任何操作都应该在控制器中具有相应的处理,如用户输入、呈现适当的 UI、身份验证、日志记录等。
Web API 的诞生及其发展
回顾基于 ASP.NET ASMX 的 XML web 服务被广泛用于构建面向服务的应用的日子,创建基于SOAP(简单对象访问协议)的服务是最简单的方法,该服务可供.NET 应用和非.NET 应用使用。它只在 HTTP 上可用。
在 2007 年底,微软发布了 Tyt T0. Windows 通信基金会 T1 T1(Po.T2。WCF 过去和现在都是构建基于 SOA 的应用的强大技术。这是微软.NET 世界的一次巨大飞跃。
WCF 足够灵活,可以配置为 HTTP 服务、远程处理服务、TCP 服务等。使用 WCF 的契约,我们将保持整个业务逻辑代码基础不变,并通过 SOAP/非 SOAP 将服务公开为基于 HTTP 或非基于 HTTP。
直到 2010 年,基于 ASMX 的 XML web 服务(或 WCF 服务)被广泛应用于基于客户机-服务器的应用中;事实上,一切都很顺利。
但是.NET 和非.NET 社区的开发人员开始感到有必要为客户机-服务器应用开发一种全新的 SOA 技术。这背后的一些原因如下:
- 随着应用投入生产,通信时使用的数据量开始爆炸式增长,通过网络传输这些数据会消耗带宽。
- SOAP 在某种程度上是轻量级的,开始显示出有效负载增加的迹象。几 KB 的 SOAP 数据包将变成几 MB 的数据传输。
- 由于 WSDL 和代理生成,在应用中使用 SOAP 服务导致了巨大的应用规模。在 web 应用中使用时,情况更糟。
- 对 SOAP 服务的任何更改都会导致更新服务代理以反映更改。对于任何开发人员来说,这都不是一项容易的任务。
- 基于 JavaScript 的 web 框架已经发布,并为更简单的 web 开发方式奠定了基础。使用基于 SOAP 的服务并不是那么理想。
- 平板电脑和智能手机等手持设备开始流行。他们有更专注的应用,需要一种非常轻量级的面向服务的方法。
- 基于浏览器的单页应用(SPA)发展非常迅速。对这些 SPA 来说,使用基于 SOAP 的服务是相当繁重的。
- 微软发布了基于 REST 的 WCF 组件,这些组件可以配置为以 JSON 或 XML 响应,但它仍然构建在 WCF 的重型技术之上。
- 应用不再只是大型企业服务,需要一个更加专注、轻量级和易于使用的服务,它可以在几天内启动并运行。
任何看过基于 SOA 的技术(如 ASMX、WCF 或任何基于 SOAP 的技术)不断发展的开发人员都觉得需要更轻的、基于 HTTP 的服务。
基于 HTTP 的、与 JSON 兼容的POCO(普通的旧 CLR 对象)轻量级服务是当下的需要,Web API 的概念开始获得发展势头。
介绍 webapi
使用 HTTP 谓词通过 web 访问的任何方法都称为 web API。它是一种通过 HTTP 传输数据的轻量级方式,很容易被各种客户端(如浏览器、桌面应用、手持设备,甚至其他 SOA 应用)使用。
要使 web API 成为一个成功的基于 HTTP 的服务,它需要一个强大的 web 基础设施,如托管、缓存、并发、日志记录、安全性等。最好的 web 基础设施之一就是 ASP.NET。
ASP.NET 以 Web 表单或 MVC 的形式被广泛采用,因此 Web 基础设施的坚实基础已经足够成熟,可以扩展为 Web API。
微软通过创建 ASP.NETWebAPI 来回应社区的需求——这是一个超级简单但功能强大的框架,用于构建仅限 HTTP 的默认 JSON Web 服务,而无需 WCF 的繁琐工作。
ASP.NET Web API 可用于在几分钟内构建基于 REST 的服务,并且可以轻松使用任何前端技术。
它于 2012 年推出,具有基于 HTTP 的服务的最基本需求,如基于约定的路由、HTTP 请求和响应消息。
后来,微软在 Visual Studio 2013 中发布了更大更好的 ASP.NET Web API 2 和 ASP.NET MVC 5。
ASP.NET Web API 2 通过以下功能以更快的速度发展:
- 通过使用 NuGet,WebAPI2 的安装变得更加简单;您可以创建空的 ASP.NET 或 MVC 项目,然后在 NuGet Package Manager 控制台上运行以下命令:
Install-Package Microsoft.AspNet.WebApi
- WebAPI 的初始版本基于基于约定的路由,这意味着我们定义了一个或多个路由模板,并围绕它展开工作。它很简单,没有太多麻烦,因为路由逻辑在一个地方,并且它适用于所有控制器。
- 现实世界的应用更为复杂,因为资源(控制器/操作)具有子资源,例如,客户有订单,书籍有作者,等等。在这种情况下,基于约定的路由是不可伸缩的。
- WebAPI2 引入了属性路由的新概念,它使用编程语言中的属性来定义路由。一个直接的优点是,开发人员可以完全控制 web API 的 URI 的形成方式。
- 下面是属性路由的一个快速片段:
Route("customers/{customerId}/orders")]
public IEnumerable<Order>GetOrdersByCustomer(int customerId) { ... }
For more details on this, read Attribute Routing in ASP.NET Web API 2 at https://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2.
- ASP.NET Web API 位于 ASP.NET framework 上,这可能会让您认为它只能托管在 IIS 上。但是,使用 OWIN self-host,也可以在没有 IIS 的情况下托管它。
- 如果任何 web API 都是使用.NET 或非.NET 技术开发的,并且要跨不同的 web 框架使用,那么启用 CORS 是必须的。
A must read on CORS and ASP.NET Web API 2 can be found at this link: https://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api.
- IHTTPActionResult 和 WebAPI OData 改进是帮助 WebAPI 2 发展成为开发基于 HTTP 的服务的强大技术的其他几个显著特性。
- 随着 C 语言的改进,ASP.NET Web API 2 在过去几年中变得更加强大,如使用 Async/Await、LINQ、实体框架集成、依赖注入 DI 框架等进行异步编程。
ASP.NET 进入开源世界
每种技术都必须随着硬件、网络和软件行业的需求和进步而不断发展,ASP.NET Web API 也不例外。
从开发人员社区、企业和最终用户的角度来看,ASP.NET Web API 应该进行的一些更改如下:
- 虽然 ASP.NET MVC 和 web API 是 ASP.NET 堆栈的一部分,但它们的实现和代码库不同。统一的代码库减少了维护它们的负担。
- 众所周知,web API 由各种客户端使用,例如 web 应用、本机应用、混合应用和使用不同技术的桌面应用(.NET 或非.NET)。但是,如何以跨平台的方式开发 web API,开发人员不必总是依赖 Windows OS/Visual Studio IDE。
- ASP.NET 堆栈应该是开源的,以便在更大范围内采用。
- 最终用户受益于开源创新。
我们看到了 web API 被接受的原因,它们是如何演变成一个强大的基于 HTTP 的服务的,以及一些必要的演变。有了这些想法,微软通过发布.NETCore 和 ASP.NETCore 进入了开源世界。
NET Core 简介
.NETCore 是一个跨平台的开源托管软件框架。它构建在 CORECRL 之上,CORECRL 是 CLR 的一个完整的跨平台运行时实现。
.NET Core 应用可以在 Windows、Linux 和 macOS 系统等跨平台上开发、测试和部署。
.NET Core 具有以下重要组件:
- CORECRL:这是一个.NET Core 执行引擎,执行 GC、编译机器代码等基本任务。
- CoreFX:它包含用于.NETCore 的集合、文件系统、XML、异步等的类库。
- SDK 工具:这是一套用于日常开发体验的 SDK 工具。创建项目、构建、运行和测试是开发人员的常见需求,也是这些 SDK 工具的一部分。
.NET Core 共享原始.NET Framework 的一个子集,另外它还附带了自己的一组 API,这些 API 不是.NET Framework 的一部分。这就产生了一些可供.NET Core 和.NET 框架使用的共享 API。
.NET Core 应用可以轻松地在现有的.Net 框架上工作,但反之亦然。
.NET Core 为操作系统的执行入口点提供CLI(命令行界面,并提供编译、包管理等开发人员服务。
以下是有关.NET Core 的一些有趣的知识点:
- .NET Core 可以安装在 Windows、Linux 和 macOS 等跨平台上。它可用于设备、云和嵌入式/物联网场景。
- Visual Studio IDE 不是与.NET Core 一起工作所必需的,但在 Windows 操作系统上工作时,我们可以利用现有的 IDE 知识。
- .NET Core 是模块化的,这意味着开发人员处理的不是程序集,而是 NuGet 包。
- .NETCore 依靠其软件包管理器接收更新,因为跨平台技术不能依赖 Windows 更新。
- 要学习.NETCore,我们只需要一个 shell、文本编辑器,并在运行时安装它。
- .NET Core 具有灵活的部署。它可以包含在您的应用中,也可以在用户或机器范围内并排安装。
- .NET Core 应用也可以作为独立应用自托管/运行。
.NET Core 支持四种跨平台方案:ASP.NET Core web 应用、命令行应用、库和通用 Windows 平台应用。
它不支持 Windows 窗体或 WPF,这些窗体或 WPF 在 Windows 上呈现桌面软件的标准 GUI。
目前,只有 C#编程语言可以用来编写.NET Core 应用。F#和 VB 支持正在进行中。
我们将主要关注 ASP.NET Core web 应用,其中包括 MVC 和 web API。将简要介绍 CLI 应用和库。由于其跨平台性,必须安装 VisualStudioIDE 才能创建应用不是强制性的。在本节中,我们将安装.NET Core,构建一个非常基本的.NET Core 应用,并了解.NET Core 的不同命令。
安装.NET Core SDK
打开.NET Core(https://www.microsoft.com/net/core/preview 网站,根据您选择的平台下载 SDK。在撰写本文时,.NET Core 2 预览版 2 已可用。
对于 Windows 环境,.NET Core 2.0 SDK 可以通过两种方式安装:
- .NET Core 2.0 和 Visual Studio 工具:在 Visual Studio 2017 安装期间,提供了安装所需工具的选项,或者.NET Core SDK 也可以安装它们。CLI 与此一起安装。
- .NET Core 2.0 Windows SDK:这是使用.NET Core 应用的 CLI 模式。
If you’re using Windows OS, and prefer Visual Studio 2017 IDE, then it’s better to leverage your IDE experience…
要使用代码,还可以安装一个文本编辑器,如 VisualStudio 代码。它是微软为 Windows、Linux 和 macOS 开发的轻量级代码编辑器。可从下载 https://code.visualstudio.com/ 。也可以使用其他文本编辑器,如 Vim、Atom 和 Sublime。
对于非 Windows 计算机,请提供适当的.NET Core SDK(请参阅此链接了解您选择的操作系统:https://www.microsoft.com/net/core/preview 应安装用于处理代码的和 Visual Studio 代码(推荐)。
Visual Studio for Mac is an exclusive IDE for macOS users, and can be used for .NET Core and ASP.NET Core apps. Download it from https://www.visualstudio.com/vs/visual-studio-mac/.
创建和运行基本的.NET Core 应用
我们将重点学习.NETCore 的一些基本概念,以及如何使用命令行。以下步骤是跨平台学习.NET Core 的步骤。有关更多详细信息,请参阅中的文档链接 https://docs.microsoft.com/en-us/dotnet/core/tools/.
首先,让我们确保一切都已正确安装。打开 Console/Shell(根据您选择的操作系统),输入以下命令以查看 CLI 命令和工具版本、产品信息以及运行时环境:
> dotnet -info
.NET Core CLI 提供了以下要使用的命令:
| new | 初始化基本.NET 项目 |
| restore | 恢复.NET 项目中指定的依赖项(大多数情况下自动运行) |
| build | 构建一个.NET 项目 |
| publish | 发布用于部署的.NET 项目(包括运行时) |
| run | 编译并立即执行.NET 项目 |
| test | 使用项目中指定的测试运行程序运行单元测试 |
| pack | 创建一个 NuGet 包 |
还有一些命令,也可以去寻找它们。
在命令行中,键入以下命令:
> dotnet new console --name DemoCoreApp
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00058.jpeg
.NET Core Command in action
让我们了解在前面的屏幕截图中发生了什么:
dotnet new;在目录上下文中创建.NET Core C#控制台项目。它有两个文件:program.cs包含 C#代码,它的项目文件csproj。DemoCoreApp.csproj是一个常见的.NET 项目文件,以 XML 格式包含项目的所有详细信息。然而,在.NETCore 中,由于使用 netcoreapp2.0 作为目标框架,项目被高度精简。- 从.NETCore2.0 开始,无论何时创建、构建或发布项目,
dotnet restore都会自动运行。
如前面的屏幕截图所示,演示项目在 VS 代码中打开;查看program.cs查看控制台上输出文本的 C#代码。
就像在传统的.NET 项目中一样,我们构建一个 C#项目,同样的方式,运行dotnet build命令,将执行例行构建并生成bin和obj文件夹。
现在dotnet run将运行 C#console 应用,并在控制台上显示结果Hello World。
此 C#项目可以通过运行dotnet publish发布并用于部署。这将在bin目录下创建publish文件夹。这个publish文件夹可以移植到任何安装了.NET Core SDK 的机器上。
我们看到了一个控制台应用的构建;我们可以使用相同的dotnet new命令创建库、web 和 xunittest 项目,如下所示:
dotnet new [--type]
--type选项指定要创建的项目的模板类型,即 console、web、lib 和 xunittest。
使用.NET CLI 命令dotnet new web,您可以创建一个使用.NET Core 的 web 应用,该应用称为 ASP.NET Core。
介绍 ASP.NET Core
ASP.NET Core 是一个新的开源跨平台框架,用于使用.NET 构建基于云的现代 web 应用。
ASP.NET Core 是完全开源的,您可以从 GitHub(下载 https://github.com/aspnet/Mvc )。它是跨平台的,这意味着您可以在 Linux/macOS 上开发 ASP.NET Core 应用,当然也可以在 Windows 操作系统上开发。
ASP.NET 最早是在大约 15 年前与.NET framework 一起发布的。从那时起,它已被数百万开发人员用于大型和小型应用。
由于.NET Core 是跨平台的,ASP.NET 在开发和部署 web 应用方面跨越了 Windows 操作系统环境的界限。让我们深入了解跨平台 ASP.NET 的更多细节。
ASP.NET Core 概述
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00131.jpeg
ASP.NET Core Architecture overview
ASP.NET Core 的高级概述提供了以下见解:
- ASP.NET Core 在完整的.NET framework 和.NET Core 上运行。
- 具有完整.NET framework 的 ASP.NET Core 应用只能在 Windows 计算机上开发和部署。
- 当使用.NETCore 时,可以在所选的平台上开发和部署它。Windows、Linux 和 macOS 的徽标表明您可以在这些系统上使用 ASP.NET Core。
- ASP.NET Core 在非 Windows 计算机上运行时,使用.NET Core 库运行应用。很明显,您不会拥有所有的.NET 库,但大多数都是可用的。
- 在 ASP.NET Core 上工作的开发人员可以轻松地切换到不限于 Visual Studio IDE 的任何机器上工作。
- ASP.NET Core 可以与不同版本的.NET Core 一起运行。
除了跨平台之外,ASP.NET Core 还有许多其他基础性改进。以下是使用 ASP.NET Core 的优点:
- ASP.NET Core 采用完全模块化的方法进行应用开发——构建应用所需的每个组件都被很好地分解到 NuGet 包中。我们只需要通过 NuGet 添加所需的包,以保持整个应用的轻量级。
- ASP.NET Core 不再基于
System.Web.dll。 - Visual Studio IDE 用于在 Windows OS box 上开发 ASP.NET 应用。现在,由于我们已经超越了 Windows 世界,我们需要在 Linux/macOS 上开发 ASP.NET 应用所需的 IDE/编辑器/工具。Microsoft 为几乎所有类型的 web 应用开发了功能强大的轻量级代码编辑器,称为 Visual Studio 代码。
- NET Core 就是这样一个框架,我们不需要 Visual Studio IDE/代码来开发应用。我们也可以使用 Sublime 和 Vim 等代码编辑器。要在编辑器中使用 C 代码,请安装 OmniSharp 插件。
- ASP.NET Core 与 Angular、Ember、NodeJS 和 Bootstrap 等现代 web 框架进行了强大的无缝集成。
- 使用 bower 和 NPM,我们可以使用现代 web 框架。
- ASP.NET Core 应用通过配置系统实现了云端准备——它只是无缝地从内部部署过渡到云端。
- 内置依赖注入。
- 可以托管在 IIS 上,也可以在您自己的进程或 Nginx 上自托管(它是一个免费、开源、高性能的 HTTP 服务器和 LINUX 环境的反向代理)。
- 新的轻量级和模块化 HTTP 请求管道。
- web UI 和 web API 的统一代码库。当我们探索 ASP.NET Core 应用的剖析时,我们将看到更多关于这方面的内容。
使用 Visual Studio IDE 创建 ASP.NET Core 项目
现在,我们将使用 Visual Studio 2017 IDE 创建一个 ASP.NET Core Web API 应用。在开始之前,请确保此先决条件:
- 安装 Visual Studio 2017(安装时请选择.NET Core SDK 选项)。我们将一直使用社区版。本书通篇使用 ASP.NET Core 2.0 预览版 2
让我们开始逐步构建 ASP.NET Core Web API:
- 打开 Visual Studio 2017 IDE,单击新建项目以打开“项目模板”对话框。
- 在 Visual C#Templates 下,单击.NET Core,然后选择 ASP.NET Core Web 应用,如以下屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00078.jpeg
Create ASP.NET Core project in Visual Studio 2017 IDE We can also create an ASP.NET Core web application targeting the full .NET framework by web template under the Visual C# section
- 提供适当的项目名称,如
MyFirstCoreApi,单击“确定”。
选择应用类型
ASP.NET Core 为我们提供了不同的应用模板来开始开发应用。这些模板为我们提供了一个最佳的项目结构,以保持一切井然有序。我们有以下几种:
- Empty:这是项目模板的最简单形式,只包含
Program.cs和Startup.cs类。由于 ASP.NET Core 的完全模块化特性,我们可以将这个空项目升级到任何类型的 web 应用。 - Web API:这将创建带有控制器、
web.config等的 Web API 项目。我们的重点将放在这个应用模板上。 - Web 应用:这将创建一个 ASP.NET Core MVC 类型的项目,其中包含控制器、视图、客户端配置、
Startup.cs和web.config。 - Web 应用(Razor 页面):这将使用 Razor 页面创建 ASP.NET Core Web 应用。
- Angular、React.js 和 React.js 与 Redux:这将创建基于 JavaScript 框架的 ASP.NET Core web 应用。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00080.jpeg
ASP.NET Core Project Templates
遵循 ASP.NET Core 提供的模板项目结构不是强制性的。在处理大型项目时,最佳做法是将它们拆分为单独的项目以实现可维护性。默认项目结构足以理解各个组件之间的交互。
选择身份验证类型
每个应用都需要某种类型的身份验证,以防止未经授权访问该应用。在前面的屏幕截图中,更改身份验证将提供以下身份验证选项:
- 无身份验证:选择此选项不会向应用添加任何身份验证包。但是,我们可以在需要时添加这样的包来完全保护我们的应用数据。
- 个人用户帐户:连接到 Azure AD B2C 应用将为我们提供所有身份验证和授权数据。
- 工作或学校帐户:使用 Office 365、Active Directory 或 Azure Directory 服务对用户进行身份验证的企业、组织和学校可以使用此选项。
- Windows 身份验证:在 Intranet 环境中使用的应用可以使用此选项。
在更改身份验证选项中,选择无身份验证,如此屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00081.jpeg
Select Authentication Type for Web API
单击 OK 创建一个 ASP.NET Core Web API 项目;VisualStudio 工具将立即开始恢复所需的包。
执行dotnet restore命令以恢复所有 NuGet 包。
我们了解了 Visual Studio IDE 工具如何帮助我们在 Windows 操作系统上创建 ASP.NET Core 应用。这与创建 ASP.NET(MVC4/5 和 ASPX)应用时的操作类似。
在 Linux/macOS 上创建 ASP.NET Core web 应用
ASP.NET Core 是一种跨平台技术,在 Linux/macOS 上创建 web 应用时,我们需要类似的用户体验。众所周知,Visual Studio IDE 不能安装在 Linux/macOS 上,因此,在非 Windows 操作系统上使用 ASP.NET Core 应用有一种不同的方法。
以下是 Linux/macOS 机器的软件要求:
- 安装最新的 NodeJS 版本(https://nodejs.org/en/ )。
- 安装 Visual Studio 代码–一个跨平台的轻量级代码编辑器。也可以使用升华、Vim、原子(https://code.visualstudio.com/#alt-下载。
- 安装 Linux/macOS 的.NET Core SDK(https://www.microsoft.com/net/download )。
On Windows machines too, we can use NodeJS, Visual Studio Code, and .NET Core SDK for working with ASP.NET Core and avoid Visual Studio IDE.
使用 Yeoman 创建 ASP.NET Core web 应用
Yeoman 是现代网络应用的网络脚手架工具。它是一个开源工具,使用命令行选项与 VisualStudio 模板类似。Yeoman 命令行工具 yo 与 Yeoman 生成器一起工作。
Yeoman 生成一个完整的项目,其中包含运行应用所需的所有文件,就像 VS IDE 一样。阅读链接http://yeoman.io/ 了解更多。
要安装 Yeoman,请确保从前面给出的软件先决条件中的链接安装 NodeJS 和 NPM。
打开命令行运行命令安装Yeoman(yo)。选项-g全局安装 npm 包,以便可以从任何路径使用。
npm install -g yo
一旦 Yeoman 安装成功,我们需要为 yo 安装 ASP.NET Core 生成器。它将有助于项目创建和构建 web 应用的不同组件。在命令行中,运行以下命令:
npm install -g generator-aspnet
Yeoman 脚手架只能与 ASP.NET Core web 应用用于.NET Core。
使用 Yeoman 创建 ASP.NET Core Web API
确保所有内容都已正确安装,打开命令行,键入yo aspnet以查看与 Visual Studio IDE 类似的不同项目模板。我们将创建一个 web API 应用,提供一个合适的名称,如yowebapidemo,然后点击输入来创建项目。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00083.jpeg
Create ASP.NET Core apps using Yeoman
一旦 Yeoman 生成 web API 项目,它将显示创建的项目文件列表和要执行的指令。
We can even use the .NET Core CLI commands to create an ASP.NET Core Web API project by referring to the link https://docs.microsoft.com/en-us/dotnet/core/tools/.
ASP.NET Core Web API 应用结构
我们已经使用 Windows 上的 Visual Studio IDE、Yeoman generator 或 Linux/macOS 上的.NET Core CLI 创建了一个 web API 项目——应用结构将是相同的,包括文件、文件夹和配置设置。让我们详细了解应用结构:
| 文件和文件夹 | 目的 |
| /Controllers文件夹 | 这就是我们放置处理请求的Controller类的地方 |
| Program.cs文件 | 这是使用Main方法执行应用的入口点。 |
| Startup.cs文件 | 这是设置配置和连接应用将使用的服务所必需的 |
| .csproj文件 | 这是一个 C#项目文件(.csproj,它更轻量级、更健壮、更易于使用。 |
| Appsettings.json文件 | 这是基于键/值的配置设置文件 |
| Web.config文件 | 这严格用于 IIS 配置,并在 IIS 上运行时调用应用 |
What about Model classes?
Domain models or POCO classes can be added in the Models folder, or we can create a separate class library for models. The preceding project structure is a good starting point; on larger projects, we can split the controllers, repositories, and domain models into separate class libraries for maintainability.
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00084.jpeg
Asp.Net Core Web API project structure
让我们详细了解每个文件的内容。
Program.cs
从根本上说,ASP.NET Core web 应用是控制台应用——知道这一点不奇怪吗?就像每个控制台应用都需要Main()才能执行一样,.NET Core 应用也有包含Main()方法的Program.cs文件。
ASP.NET Core 是建立在.NET Core 之上的,这就是我们刚才创建的应用结构中有program.cs的原因。签出此文件:
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace MyFirstCoreAPI
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
您可以按如下方式分解前面的代码:
- 就像任何.NET 控制台应用都需要
Main()方法来开始执行一样,.NET Core 运行时将通过调用Main()方法来启动应用。 - 需要使用
IWebHost接口将这些控制台应用作为 web 应用(MVC 或 web API)运行。 WebHost类有CreateDefaultBuilder,它预先配置了运行应用的默认值。这意味着 web 应用需要一个 web 服务器(Kestrel)、一个 IIS 集成设置、用于读取各种值的配置文件以及Content Root文件夹。所有这些需求都在该方法中预先配置。- 虽然我们可以实现一个自定义方法,但是提供的默认实现足以启动。通过此链接了解 ASP.NET Core GitHub 回购协议中的
CreateDefaultBuilder代码https://github.com/aspnet/MetaPackages/blob/rel/2.0.0-preview1/src/Microsoft.AspNetCore/WebHost.cs 。让我们从 GitHub repo 中了解这些方法:UseKestrel ()是一种扩展方法,其作用类似于运行 ASP.NET Core 应用的 web 服务器。它基于 libuv。Kestrel 通常被称为负责运行应用的内部 web 服务器。它重量轻、速度快、跨平台。在有更多关于这方面的内容 https://github.com/aspnet/KestrelHttpServer.UseContentRoot()是一种扩展方法,用于指定 web 主机要使用的内容根目录。这通常是当前的工作目录;可以将其配置为指向另一个文件夹,其中包含运行应用所需的所有文件。- 当使用 ASP.NET Core 应用时,IIS 被视为一个外部 web 服务器,暴露于 internet 以接收请求。
UseIISIntegration()配置服务器在AspNetCoreModule之后运行时应监听的端口和基本路径。 ConfigureAppConfiguration读取配置文件,并添加UserSecrets和环境变量。ConfigureLogging设置控制台日志记录和调试窗口。UseStartup(StartUp)设置类来配置各种服务,如点击请求-响应管道等。Startup 是一个没有任何基类的简单类,有两个方法,Configuration和Configure。Build()构建并准备WebHostBuilder运行 web 应用。Run();在 Kestrel web 服务器下运行 ASP.NET Core web 应用。
Startup.cs
在 ASP.NET Core 中,webHostBuilder从Main()运行时调用 Startup 类,所有应用都需要该类。
它是任何请求或响应返回时的第一行执行。它执行一系列操作,如提供依赖项注入、添加和使用各种中间件组件等。
Startup.cs类必须定义Configure和ConfigureServices方法;当主机开始运行时,将调用它们。
配置方法
ASP.NET Core 是完全模块化的,也就是说,如果您真的需要组件,您可以添加它们。通过这种方法,web 应用在部署和性能方面变得轻量级。
Startup.cs类的Configure方法的主要目的是配置 HTTP 请求管道。在下面的Startup.cs代码示例中,您可以看到IApplicationBuilder使用扩展方法来配置管道。
UseMvc扩展方法将路由中间件添加到请求管道中,并将 MVC 配置为默认处理程序,如下所示:
app.UseMvc();
我们已经创建了一个简单的 web API,为此,我们使用 MVC。您可能想知道,当我们使用 web API 时,为什么要使用 MVC?原因是 ASP.NET Core 具有统一的 MVC 和 web API。
IApplicationBuilder定义一个类,该类提供配置应用请求管道的机制。您可以将自定义管道配置构建为中间件,并使用扩展方法将其添加到IApplicationBuilder。第 6 章中间件和过滤器专门针对此。
默认情况下,IApplicationBuilder、IHostingEnvironment和ILoggerFactory由服务器在 web API 或 MVC 项目中注入:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace MyFirstCoreAPI
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to
the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the
HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
}
}
在下一节中,我们将详细讨论 ASP.NET Core 请求管道处理以及中间件的角色。
ConfigureServices 方法
ConfigureServices方法使用依赖项注入(DI设置应用运行的所有可用服务。将服务列表添加到IServiceCollection实例中,在Configure之前调用。
运行时首先调用此方法的原因是,在准备好请求管道处理之前,需要添加一些特性,如 MVC、标识、实体框架等。因此,在前面的代码中,我们看到了services.AddMvc()。
ConfigureServices方法有助于在 ASP.NET Core 应用中实现依赖项注入模式。让我们看一个简单的例子,假设我们通过实现INewsOfDay接口来编写NewsOfDay类。我想使用 DI 模式,以便任何其他类都可以轻松地注入此接口以获得当天的报价。
public void ConfigureServices(IServiceCollection services)
{
// using DI to inject the interface
services.AddSingleton<INewsOfDay, NewsOfDay>();
}
ASP.NET Core 默认支持依赖注入;我们不需要使用任何第三方 DI 容器,如 Unity、StructureMap、Autofac 等。但是,如果开发人员觉得需要使用其他 DI 容器,他们可以覆盖默认实现。
*csproj 先生
任何.Net 开发人员都会熟悉.Net 项目中的*.csproj文件;在 ASP.NET Core 应用中,我们确实可以找到此文件。与传统的.NET 应用相比,它是一个非常精简的版本。
在 ASP.NET Core 的初始版本中,基于 JSON 的project.json文件用于包管理,但为了与其他.NET 应用保持同步,并与 MSBUILD 系统配合良好,该文件被删除。
The .csproj file can be now edited in Visual Studio 2017 IDE without reloading the entire project. Right-click on the project file, click on Edit to make changes.
我们来看看 ASP.NET Core 项目的*.csproj文件的内容:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>aspnet-MyFirstCoreAPI-D0B356AB-BC35-4D73-9576-
997BC358BEE9</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All"
Version="2.0.0-preview2-final" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference
Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools"
Version="2.0.0-preview2-final" />
</ItemGroup>
</Project>
您可以将*.csproj文件分解如下:
TargetFramework标签指向netcoreapp2.0。这是.NET 标准 2.0 的 ASP.NET Core 名称。我建议您访问此链接以了解有关.NET 标准 2.0 的更多信息:https://blogs.msdn.microsoft.com/dotnet/2016/09/26/introducing-net-standard/ 。- 指示
Folder在构建过程中包含wwwroot目录。 PackageReference是将包含在项目中的 NuGet 软件包或任何自定义库。Microsoft.AspNetCore.All元包引用了所有 ASP.NET Core 包,只有一个版本号。只有更改此版本,才能更新任何新版本。虽然您可以将它们作为单独的包添加,但建议使用Microsoft.AspNetCore.All。
为什么选择 Microsoft.AspNetCore.All 元软件包?
.NETCore2.0 带来了一个称为运行时存储的新功能。从本质上说,这让我们可以在中央位置的机器上预安装软件包,这样我们就不必将它们包含在单个应用的发布输出中。
ASP.NET Core 请求处理
ASP.NET(MVC 和 ASPX)的所有请求处理都依赖于system.web.dll。它曾经完成所有繁重的浏览器-服务器通信工作,并且与 IIS 紧密耦合。
NET Core 是通过完全删除system.web.dll来实现跨平台设计的;这导致了一种完全可插拔的不同请求处理技术。这种删除也有助于统一 ASP.NET 中的 MVC 和 web API 堆栈。
ASP.NET Core 没有区分 MVC 和 web API,因此请求处理现在将很常见。在下一节中,我们将了解有关统一的更多信息。
下图显示了 ASP.NET Core 请求处理的概述:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00086.jpeg
ASP.NET Core request processing
让我们一步一步地了解 ASP.NET Core 请求处理。
各种客户端,如 web 应用(浏览器)、本机应用和桌面应用,都会访问托管在外部 web 服务器(如 IIS/Nginx)上的 web API。有趣的是,IIS 现在是外部 web 服务器的一部分,因为它不运行 ASP.NET Core 应用。它只充当通过 internet 公开的托管 web 服务器。Nginx 是 Linux 机器上 IIS 的对应物。
IIS/Nginx 调用计算机上安装的 dotnet 运行时来启动处理请求。现在,该处理属于.NET Core。为此,web.config文件仍存在于 ASP.NET Core 应用中。
Dotnet 运行时调用 kestrelweb 服务器(内部 web 服务器)来运行应用。Kestrel 是一个基于 libuv 的开源轻量级跨平台 web 服务器;这是使 ASP.NET Core 应用真正跨平台的重要步骤之一。
Kestrel 然后通过应用中的Main ()方法启动应用。请记住,ASP.NET Core 应用是控制台应用。Program.cs中的Main ()方法是.NET Core 应用的起点。
然后,Main ()方法构建并运行webHostBuilder。然后,请求被推送到Startup.cs类的Configure方法;HTTP 请求管道在此处配置。我们之前创建的默认 web API 模板项目只在管道中添加了app.UseMvc ()。我们可以以中间件的形式定制 HTTP 请求管道处理逻辑(详见第 6 章、中间件和过滤器:
- MVC 中间件构建在通用路由中间件之上,用于处理请求。此时,请求被处理,并发送到相应的控制器进行处理。
- 当请求处理完成时,响应将以相反的顺序通过相同的管道发送。如果请求无效,定制中间件可以帮助我们返回响应。
ASP.NET Core 请求管道处理完全可插拔;Configure方法应该只包括所需的中间件,而不是 ASP.NET web 堆栈中存在的繁重的system.web.dll。
ASP.NET Core 中统一的 MVC 和 Web API
主要的架构演进之一是在 ASP.NET Core 中统一 MVC 和 web API。ASP.NET Core 中的 MVC 和 web API 控制器之间没有区别。
在以前的 ASP.NET 堆栈中,MVC 和 web API 中的控制器从各自的基本控制器派生,如下所示:
// ASP.NET MVC 5 Controller
public class HomeController : Controller
{
// Action Methods
}
// ASP.NET MVC 5 Controller
public class ValuesController : ApiController
{
// API Action Methods
}
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00087.jpeg
MVC and Web API unification
ASP.NET MVC 4/5 和 web API 2 都具有控制器、操作、过滤器、模型绑定、路由和属性,但由于以下原因,它们具有不同的代码基:
- ASP.NET MVC(4/5)依赖于
system.web.dll,该system.web.dll绑定到 IIS 进行托管。如果没有 IIS,您将托管一个 MVC 应用。 - ASP.NET Web API 设计为自托管;它不依赖 IIS 托管。
ASP.NET Core 的设计思想之一是使其自托管,并独立于 IIS 进行托管。通过这种方式,system.web.dll被删除,因此它可以在没有 IIS 的情况下托管。这导致了 MVC 和 WebAPI 的合并,形成了一个单一的代码库。
在 ASP.NET Core 中,MVC 和 web API 共享同一个基本控制器,因此,MVC 和 web API 之间没有实现差异。
运行 ASP.NET Core Web API 项目
我们的 ASP.NET Core Web API 是由 Visual Studio 2017 IDE 为 Windows 环境和 Yeoman generator 为 Linux/macOS 创建的。我们将使用 IIS Express 或 Kestrel 服务器运行该应用。
在运行应用之前,让我们进一步了解Values控制器(默认创建)。在Controllers文件夹下有一个名为ValuesController.cs的 C#类文件:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace MyFirstCoreAPI.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
您可以按如下方式分解前面的代码:
- Web API 基于模型、值和控制器概念。
ValuesController.cs是客户端通过 HTTP 访问的 C#类。 - 它派生自控制器基类,使任何类都成为 MVC 或 WebAPI 控制器
[Route("api/[controller]")]定义路由策略。基于此配置可以访问控制器ValuesController提供了以下可通过 HTTP 访问的方法:
| 方法名称 | HTTP 动词 | 备注 |
| Get() | HttpGet | 返回字符串的 IEnumerable |
| Get(int id) | HttpGet | 返回基于值的字符串 |
| Post([FromBody]string value) | httpost | 使用 POST 插入字符串 |
| public void Put(int id, [FromBody]string value) | HttpPut | 使用 PUT 根据 Id 更新字符串 |
| public void Delete(int id) | HttpDelete | 使用 DELETE 删除字符串记录 |
在 VisualStudioIDE 中运行应用
在使用 Visual Studio 2017 IDE 时,我们有两种运行 ASP.NET Core 应用的方法:
- 使用 IIS Express:如果您想使用 IIS Express。按F5开始运行应用;它将打开选定的 web 浏览器。
- 使用红隼服务器:如果您想运行红隼服务器,请从运行选项中选择
MyFirstCoreAPI。按F5开始运行应用;它将打开所选的 web 浏览器以及控制台窗口。
在这些环境中,我们将使用命令行选项来运行应用。他们使用 Kestrel 服务器来运行应用。
在 Linux/macOS 上运行 ASP.NET Core Web API
从 Linux/macOS 机器的项目根目录中打开 console/shell,如下所示:
dotnet run
上述命令编译、构建和运行应用;它还开始监听http://localhost:5000上的请求。打开任何浏览器,粘贴 URLhttp://localhost:5000/api/values以查看 web API 从值控制器返回响应。
您将看到应用正在运行,并在浏览器中显示响应,如以下屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00089.jpeg
Response after Web API on browser
我们可以使用 Postman 发送请求并接收来自所示 web API 的响应。端口是自动生成的,并且会根据机器的不同而有所不同。
Postman 是一个用于使用 API 各种活动的工具——我们将使用它进行 API 测试。您可以从下载 https://www.getpostman.com/ 。
在 ASP.NET Core Web API 项目中,响应格式化程序默认设置为 JSON。但是,我们可以根据需要定制。
How about debugging using Visual Studio Code? If you need a similar experience for debugging .NET Core (C#) in Visual Studio Code,
Omnisharp–C# extension for .NET core–should be installed. Refer to this link to set up debugging: https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger.md
进一步阅读
有关的详细信息,请参阅以下链接。净核心:
有关 ASP.NET Core 的更多信息,请参阅以下内容:
- https://docs.asp.net/en/latest/
- https://github.com/aspnet/Mvc
- 第 9 频道的 ASP.NET 怪物系列 MSDN
- ASP.NET 社区站在上 https://live.asp.net/
总结
在本章中,我们已经介绍了很多方面。我们首先介绍了 MVC,然后介绍了 ASP.NET Web API 在过去十年中是如何成熟的。有了背景知识,我们学会了如何使用.NET Core 和跨平台框架的强大功能。
我们了解 ASP.NET Core 及其架构,并使用.NET Core SDK 设置系统。我们还使用 VisualStudio 工具和 Yeoman 生成器创建了一个 web API。我们详细了解了 ASP.NET Core 请求处理以及将 web API 和 MVC 统一到单个代码库中的过程。
在下一章中,您将了解有关模型、视图和控制器的更多信息。
四、控制器、动作和模型
服务的入口点和实体是控制器。虽然处理程序是 ASP.NET Core 管道中的初始类之一,但一旦请求通过 ASP.NET 并找到合适的路由,它将被定向到控制器。
现在,您可以控制要作为响应发送的数据。控制器可以包含许多方法。尽管这些可能是公共方法,但并非所有方法都可用。在这些方法上启用 HTTP 操作将使这些方法变成操作。
在本章中,您将更好地了解控制器以及它们如何与 ASP.NET 管道相结合。我们将创建一些控制器以及这些控制器的操作。
在本章中,我们将介绍以下主题:
- 控制器简介
- 行动
- 使用模型创建控制器
控制器简介
当您向项目中添加新控制器时,ASP.NET 会自动为您加载该控制器并使其准备好使用。以下是一些您可能想知道的指针,以便您不会陷入困境,或者如果您想创建一个新项目并将所有控制器都放在其中:
- 您的控制器需要以单词
Controller结尾。 - 确保你的班级是
public;不用说,接口和抽象类将无法工作。从 Microsoft 控制器类继承它们。 - 不同名称空间中不能有相同的控制器名称。ASP.NET 允许同一控制器使用多个名称空间,但不会解析两个控制器。最佳做法是为控制器指定唯一的名称。
行动
web API 有很多动作,其中一些动作在第 2 章理解 HTTP 和 REST中介绍,并附有示例。作为复习,我们将再次讨论它们,因为我们希望在创建控制器时使用这些操作。Action属性将用于装饰一个方法。
每一个行动都应该从消费者的角度来考虑;例如,对于Post,客户机正在发布一些内容。
如果我们已经创建了ShoesController,那么其路径如下:
[Route("api/[controller]")]
邮递
当我们想要创建一些东西时,使用此操作。邮件正文将包含需要保存到数据存储中的数据:
[Route("")]
[HttpPost]
public IActionResult CreateShoes([FromBody] ShoeModel model)
第一行是路由,应该始终声明路由。它们让正在阅读代码或调试代码的人更好地理解正在发生的事情以及流程是如何进行的。
在第二行中,我们陈述了Action属性;在这种情况下,它是Post,您不需要一直设置它。宣布行动是一种良好的做法。
收到
Get用于检索数据。在大多数情况下,Get未明确说明:
[Route("")]
[HttpGet]
public IHttpActionResult GetShoes()
这可以声明如下:
[Route("")]
public IHttpActionResult Get()
注意动作的省略。
放
Put用于更新数据或创建一些不存在的数据:
[Route("{id}")]
[HttpPut]
public IHttpActionResult Update(ShowModel model)
您会注意到 ID 是路由的一部分,它意味着调用者知道他们想要更新哪个实体。我们所做的就是创建名为Update的方法;名称可以是您想要的名称。
色斑
Patch与Put类似,区别在于您发送的只是改变了增量的数据,而不是整个模型:
[Route("{id}")]
[HttpPatch]
public IHttpActionResult PatchUpdate(ShowModel model)
删去
Delete用于删除数据。您所需要的只是 ID:
[Route("{id}")]
[HttpDelete]
public IHttpActionResult Delete(string id)
这些是我们将在控制器中使用的操作。我已经将Actions属性与控制器分开进行了研究,因此我们可以在不担心实现的情况下对其进行一些关注。
控制器
我启动了 VS2017 社区版并创建了一个新的 web 项目。请注意,您可以选择要针对哪个模板。我选择了 ASP.NET Core Web 应用。创建一个名为Puhoi的新项目,这是新西兰的一个小镇,生产一些乳制品。我的目标是为他们的一些产品创建一个控制器。创建一个有形的、真实的工作示例是很好的。我倾向于远离书籍控制器或产品控制器之类的东西。
创建项目后,系统将提示您选择模板;通过选择 ASP.NET Core 2.0,选择以下屏幕截图中突出显示的模板:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00091.jpeg
我创建了一个新的控制器,保存在Controllers文件夹中:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00093.jpeg
另外,记下控制器的路线;它不包含控制器的名称:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00095.jpeg
让我们谈谈我们想要创造的东西和一点关于 Puhoi 奶酪的知识。他们有一些产品,如牛奶、奶酪和酸奶。
像任何公司一样,您希望列出所有产品、添加新产品和删除产品。因此,如果有一个前端,一个网站,或一个应用,那么他们会将自己连接到这个 API 中以获取相关信息。让我们开始构建一些逻辑。我们不会为此创建后端,因为它超出了本章的范围。
让我们构建 stores controller,它将列出一些顶级产品,例如百货商店。然后,让我们深入并创建一个子管道控制器,例如列出各种奶酪的东西。如果你不喜欢奶酪,你就不会喜欢这一章;我事先道歉。
模型
我们在一个单独的项目中创建模型,因为我们不想用一切污染 API 项目,它不是一个垃圾场。当我们创建类时,一个类有一个职责,当它们有多个职责时,我们就创建一个新类。项目应该包含具有并共享相同职责的类。我们还创建了一个BaseModel类,该类具有一些我们希望所有模型都具有并且应该具有的公共属性,因为它们是相关的。
public class BaseModel
{
public Guid Id { get; set; }
}
创建模型项目后,我们有一个包含模型类和我们的BaseModel类的文件夹:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00097.jpeg
StoreModel类继承BaseModel类,属性表示模型属性:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00101.jpeg
现在我们有了我们的模型,让我们将其添加到控制器并开始。别忘了将 web API 中的引用添加到Models项目中。
稍后,您还将看到我们如何分别添加模型。我已经重构了我们的控制器以使用存储模型,现在看起来是这样的:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00103.jpeg
总之,我们在Get上返回一个或多个StoreModel,并且Post和Put方法将我们的模型作为输入参数。
现在我们已经准备好了,剩下的是我们应该如何返回Get的数据并传输Put和Post要存储的数据。
我们可以创建内存中的数据存储并将其用作持久存储。但这是有点黑客的,你并不是真的在生产系统中这样做。也许我们应该创建一个数据库,并将数据从 API 一直传递到数据库。这是一个很大的努力,这一章是关于控制器。因此,我们可能会创建一个到数据存储的接口,并将数据从 API 传递到接口,当您准备实现 API 时,这应该是一个很好的模式。
为了创建一个分层良好的架构并隐藏一些将 API 连接到数据层的代码,我们将创建一个新的类库项目并向该库添加一些类。这些类将引导数据进出数据存储接口。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00105.jpeg
Minimum-layered web API architecture
我已经用最少数量的组件制作了通用图。如果需要,可以创建更多组件。在应用中考虑边界点很重要。您的 API 项目不应该引用数据项目,数据项目也不应该引用 API 项目。
商业
使用应用的名称创建一个新的类库项目,然后创建.Business,如下所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00107.jpeg
还有它的文件夹结构。
这是IDataStoreManager的接口:
namespace Puhoi.Business.Interfaces
{
public interface IStoreManager
{
HttpModelResult Add(BaseModel model);
HttpModelResult Update(BaseModel model, Guid id);
HttpModelResult Get(Guid id);
HttpModelResult Delete(Guid id);
HttpModelResult GetAll();
}
}
您需要对模型项目的引用,并添加对System.Web的引用。
请注意,我们正在返回HttpStatusCode。有人可能会争论为什么业务引用System.Web,在过去,这些可能是一个有效的论点。与我们的设计图一样,Business是 web API 和数据层之间的一层。它必须了解这两个方面,它是一个业务层,但它是一个 WebAPI 业务层。
HttpModelResult类如下所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00109.jpeg
我们现在创建一个实现IStoreManager的具体类。但是,我们需要对其进行注册。许多人会使用依赖项注入来创建对象,并使用像 Autofac 这样的库。
有了 ASP.NET Core,这是内置的。
依赖注入
以下是我注册StoreManager课程的方式:
我已经导航回我们的 PuhoiAPI 项目,并在Startup.cs类中添加了以下代码:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00111.jpeg
可用的选项如下所示:
AddTransientAddScopedAddInstanceAddSingleton
如果您是依赖注入(DI的新手,以下内容应该能让您更好地理解:
- 瞬态:每次需要时都会创建一个新对象。这最适合于无状态对象。
- 范围:为每个请求创建一个新对象。
- 单例:与单例模式类似。第一次需要该对象时,将创建一个新对象,并且该对象的每个后续依赖项都将使用该对象。
- 实例:最好的描述方式是它的行为类似于单例,只是单例是延迟加载的。
既然管理器已经包含在我们的 DI 中,那么让我们将其合并到控制器中。
通过身份证
StoreController类被注入IStoreManager接口,作为其构造函数中的依赖项:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00113.jpeg
对于控制器上的Get,我们将对其进行重构,因此不要过于关注实现:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00115.jpeg
注意路线;我们使用的是 ID,我们的 ID 是Guid,这是我们的唯一标识符。然后,我们有storemanager,我们将 ID 传递给我们的商店经理,并从控制器返回一个模型。
相当容易;这在实际通话中是什么样子的,我们如何称呼它?
我创建了一个简单的实现,它将返回一个包含所请求内容的模型。
小提琴手的要求如下:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00044.jpeg
小提琴手的反应如下:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00045.jpeg
我们的状态代码是 200,这是成功的。结果非常好,因为我们有数据流。考虑没有找到结果的场景。返回一个空模型并不理想,因为我们将返回一个带有 200 的空模型。我们希望返回更直观的结果。
如果我们将实现更改为以下内容,从管理器检查模型并返回 null,我们的响应为 204,这不会告诉使用者特定资源不存在:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00046.jpeg
使用IAction结果类型更灵活,并提供所需的结果:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00047.jpeg
我们可以从经理那里测试我们的模型,并注意我们的回报。有三种不同的回报。一个是模型的OK。第二个是NotFound;如果经理没有找到我们查询的 ID,我们可以返回未找到状态。这对消费者来说更直观,并且检查结果代码比解析数据便宜得多。
最后,我们假设我们收到的请求是错误的,并返回一个错误的请求。
需要注意的是,这只是一个示例和一种模式,说明了如何构造控制器;您可以在 switch 语句中添加更多事例。
您也可以将BadRequest更改为更智能一点,而不仅仅是返回BadRequest;这里的重点是从消费者的角度展示IActionResult的用途以及它是如何凝胶的。
现在,您可以在 Fiddler 中看到所需的结果:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00048.jpeg
这是在StoreManager类的Get方法中实现逻辑的方式:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00049.jpeg
我们通过 ID 向DataStore请求对象,如果结果为空,则返回Not Found。如果有一个对象从数据存储返回,那么我们使用映射器在数据库中的对象和 API 公开的对象之间进行映射。本质上,我们在dto和model之间映射。然后,我们将状态设置为OK并等待model结果。
映射
我们使用 AutoMapper 在模型和Dto之间进行映射,反之亦然。下图显示了AutoMapper如何融入我们的解决方案:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00100.jpeg
为 API 项目和将进行映射的项目添加对AutoMapper的 NuGet 引用。
在 API 项目中,我创建了一个新类来设置映射。此类继承自AutoMappers概要文件类。
这是最简单的设置:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00102.gif
它说你应该在dto作为源和model作为目标之间创建一个映射。然而,我们知道这并不是那么简单。结果是这样的:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00104.gif
对于dto,我们忽略StoreId和UId,对于model,我们忽略Id。然后,映射完成后,将UId从dto映射到Id从model映射。
在Startup.cs中通过以下方式完成管道内的设置:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00106.jpeg
为映射器配置创建成员变量。然后,在Startup构造函数中,我们设置了一个新的MapperConfiguration变量,并添加包含映射的概要文件。
我们快搞定了。在ConfigureService中,我们需要将其添加到服务中:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00108.jpeg
IMapper被创建为单例,因为完成的映射不会更改,并且它们不包含任何状态。
邮递
从控制器开始,实现 Post,如果对象被创建,或者如果它返回内部管理器发送给我们的内容,则返回 201。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00112.jpeg
我们没有特殊的路由,但是我们声明默认路由是清除的。用HttpPost装饰方法。与Get一样,我们返回IActionResult。从请求主体检索模型。如果您查看此方法中的代码,我们会将所有工作委托给经理。然后,我们得到一个结果;如果管理器发送给我们HttpStatusCode,我们知道对象已经创建,我们返回 201,创建时带有新创建资源的位置。任何其他结果被翻译回HttpStatusCodeResult。这是我对 Fiddler 的研究结果:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00114.jpeg
请求主体 JSON 如下所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00116.jpeg
服务器的响应如下所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00001.jpeg
经理的工作是从控制器获取模型并将其传递到此数据存储:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00002.jpeg
然而,它的责任比这多一点。它需要告诉管理器对象是否已创建,如果未创建,则需要指定问题所在。在数据存储可以插入对象之前,管理器会检查存储是否具有此对象。请注意,检查现有对象是否由管理器负责,而不是由数据存储负责。顾名思义,这是一家商店。如果对象存在,则管理器会将冲突返回给控制器。这是管理器中的 else 块:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00003.jpeg
我们利用AutoMapper在model和dto之间切换并返回到模型。您必须在AutoMapperProfileConfiguration中进行更改,才能使其正常工作:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00004.jpeg
放
我们使用Put更新商店或创建新商店。Put的签名与Post不同:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00005.jpeg
我们在控制器类的签名中有 ID 和模型。路由具有 ID,并且模型位于请求主体中。您会注意到Post没有 ID,我们要求店长更新我们的型号;如果我们从商店管理器中获得一个已创建的资源,那么我们将发布新资源的这个位置。任何其他状态,包括OK,返回为HttpStatusCodeResult。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00006.jpeg
我已经对我们的商店经理做了一些重构,就像人们通常做的那样。如果找不到提供给我们的 ID,那么我们将模型添加到数据存储中。在正常流程中,更新模型并返回 200。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00007.jpeg
我把这件事告诉了费德勒;注意动作Put和主体中的模型。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00008.jpeg
在此之前的步骤是使用Post创建模型,使用Put更新模型,然后在资源上调用 get 以检查是否已执行更新。
删去
Delete非常直截了当。我们将继续执行第 1 章、微服务和面向服务架构简介中设定的原则,围绕其余删除原则。当我们第一次删除一个表示时,我们可以返回 200,但是当我们发出相同的请求时,表示不再存在,所以我们应该返回 404。让我们看看这个代码:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00009.jpeg
要求商店经理删除具有特定 ID 的对象,然后我们返回从经理那里得到的任何东西。让我们看看经理:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00012.jpeg
这也很简单;根据我们从数据存储中得到的信息,我们返回 200 或未找到。
我将不显示 Fiddler 请求和对不同流的响应,因为我觉得这对于我们在本章中介绍的内容非常基本,直到现在。这只是删除请求:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00013.jpeg
请注意,我们的操作是Delete。
盖特尔
我们的GetAll操作看起来比Delete简单:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00014.jpeg
我们向我们的经理索要所有门店,并将其连同 200:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00015.jpeg
我们在 manager 中所做的就是,向数据存储请求所有 DTO,然后将它们映射到一个模型,并将它们作为IEnumerable返回。就在那之前,我们把状态设为 200。
路线是什么样的?它看起来类似于GetById,只是不需要设置 ID。
总结
在本章中,我们开发了一个完整的 CRUD 端点,并研究了 ASP.NETCore2.0 的一些新特性,如内置依赖项注入。
我们探索了HttpGet、HttpPost、HttpPut和HttpDelete动作,以及一些基本的路由。
我们建立了一个干净的模式来分解类的职责,并使其更容易扩展给定的功能。
对象是松散耦合的,这使得它们更容易测试;这是通过内置依赖项注入实现的。我们还使用 Fiddler 来演示我们的 API 是如何工作的。
在下一章中,我们将深入讨论路由机制、路由生成器、属性路由、约束等等。
五、实现路由
我们使用URL(统一资源定位器)访问 web 上的代码资源。例如,当您看到对www.dummysite.com/pages/profile.aspx的请求时,很容易推断profile.aspx实际存在于网站 dummysite.com 上的 pages 文件夹中。
请注意,在我们的示例中,URL 和物理文件有直接的关系——当 web 服务器接收到此文件的请求时,执行代码,并返回响应以在浏览器上显示。
当使用基于 MVC 的框架(如 ASP.NETCore)时,URL 会使用一种称为路由的方法映射到控制器类及其操作方法。
在本章中,我们将研究以下主题:
- 引入路由
- 路由中间件
- 路线生成器
- 基于约定和基于模板的路由
- 基于属性的路由
- 路线约束
- 链接生成
- 路由的最佳实践
引入路由
在第四章控制器、动作和模型中我们学习了很多关于控制器和动作的知识。任何 ASP.NET Web API 项目都将有一个或多个控制器,这些控制器具有许多基于 HTTP 谓词(如 GET、POST、PUT 和 DELETE)的操作方法。
当我们在第 3 章ASP.NET Core Web API 剖析中创建一个基本的 ASP.NET Core Web API 并运行该应用时,我们在浏览器中看到 URL 为http://localhost:5000/api/values——它显示了来自值控制器的 JSON 响应。
这里出现了如下几个问题:
- 项目如何知道应该加载特定的控制器和操作方法?
- 如果我在真实场景中有许多控制器和动作方法会怎么样?我们如何指向特定的控制器?
- 正确服务 HTTP 请求的机制是什么?
将传入 HTTP 请求映射到其相应控制器的操作方法的机制称为路由。它是 ASP.NET Core MVC/Web API 的关键组件之一。如果没有路由机制,ASP.NET(MVC/Web API)将无法运行应用和服务请求。路由解决了上述所有问题。
路由中间件
在 ASP.NET Core 世界中,每个 HTTP 请求和响应都必须通过各种中间件。Startup类的Configure方法配置处理请求的管道,并对请求采取适当的操作。
ASP.NET Core 提供路由中间件来执行将请求映射到相应控制器和操作方法的任务。让我们了解一下这个中间件。
创建一个空的 ASP.NET Core 项目,通过手动编辑*.csproj或 NuGet 将Microsoft.AspNetCore.All添加到其中。在依赖项中添加以下包详细信息,以便恢复并准备使用:
> " Microsoft.AspNetCore.All ": "2.0.0-preview2-final"
打开Startup类添加以下代码,查看路由中间件的运行情况:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Routing;
namespace BasicRoutes
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Adding Router Middleware
services.AddRouting();
}
public void Configure(IApplicationBuilder app,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//HTTP pipeline now handles routing
app.UseRouter(new RouteHandler(
context => context.Response.WriteAsync("Mastering Web API!!")
));
}
}
}
您可以按如下方式分解前面的代码:
Services.AddRouting()增加要使用的路由中间件。app.UseRouter处理 HTTP 请求的路由。需要RouteHandler来处理请求。这里我们只是用一个字符串来写响应。
对于传入的任何请求,管道都需要有人来处理。这是由RouteHandler完成的;每个路由都应该有一个处理程序。
运行应用时(按F5,浏览器显示字符串 Mastering Web API,如本屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00016.jpeg
Basic example of routing middleware
这是 ASP.NET Core 中最基本的路由示例。由于管道仅对其进行路由,因此使用任何 URL 访问应用都将给出相同的响应。
RouteBuilder 类
RouteBuilder类帮助我们构建定制路线,并在请求到达时处理它们。MVC 还使用这个类来构建控制器的默认路由机制 actions。
在上一个示例中,我们创建了一个服务于任何路由的基本路由。现在,我们将使用不同的 HTTP 谓词(如 GET 和 POST)创建自定义路由。
在Startup.cs的Configure方法中为自定义路由生成器复制以下代码:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
var routes = new RouteBuilder(app)
.MapGet("greeting", context => context.Response.WriteAsync("Good morning!!
Packt readers."))
.MapGet("review/{msg}", context => context.Response.WriteAsync(
$"This book is , {context.GetRouteValue("msg")}"))
.MapPost("packtpost", context => context.Response.WriteAsync(
"Glad you did Post !"))
.Build();
app.UseRouter(routes);
}
您可以按如下方式分解前面的代码:
- 实例化一个新的
RouteBuilder类。 - 我们使用
MapGet设置问候路径,以使用 HTTP 动词 GET 处理客户端请求。 - 我们使用
MapGet设置审核路径,用RouteData或RouteValue处理客户请求。请求处理程序委托读取消息。 - 使用
MapPost,我们得到处理客户端 POST 请求的packtpost路径。
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00017.jpeg
Route Builder using MapGet
地图路线
MapRoute是一种扩展方法,使用指定的名称和模板向IRouteBuilder添加路由。需要增加一个DefaultHandler进行进路办理。
下面的代码显示了如何定义要与MapRoute一起使用的defaultHandler:
var routeHandler = new RouteHandler(context =>
{
var data = context.GetRouteData().Values;
return context.Response.WriteAsync("Controller Name is " +
data["controller"].ToString());
});
var routes = new RouteBuilder(app, routeHandler)
.MapRoute("packt", "{controller}/{action}")
.Build();
app.UseRouter(routes);
ASP.NET Core Web API 和路由
到目前为止,我们还看到了路由的基础知识,没有 MVC 或 web API,而是以中间件路由、RouteBuilder 和 MapRoute 的形式出现。必须理解这些概念是如何协同工作的。
当我们创建一个 ASP.NET Core 应用作为 web API 时,需要了解一些与路由相关的功能。
在第 3 章ASP.NET Core Web API 剖析中,我们创建了一个简单的 Web API 项目;查看Startup类的Configure和ConfigureServices方法,只添加了 MVC 中间件和服务。没有提到路由中间件。
现在出现的问题是 WebAPI 项目如何完成所有需要的路由。答案在于在Configure方法中添加的 MVC 中间件app.UseMvc()。
UseMvc()是微软 ASP.NET Core 团队编写的 MVC 和 web API 项目的中间件。该中间件通过相同的代码库确认 MVC 和 WebAPI 的工作。
以下代码是 GitHub 上的 ASP.NET MVC 开源项目(的一部分 https://github.com/aspnet/Mvc :
public static IApplicationBuilder UseMvc(
this IApplicationBuilder app,
Action<IRouteBuilder> configureRoutes)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (configureRoutes == null)
{
throw new ArgumentNullException(nameof(configureRoutes));
}
if (app.ApplicationServices.GetService(typeof(MvcMarkerService))
== null)
{
throw new InvalidOperationException(
Resources.FormatUnableToFindServices(
nameof(IServiceCollection),
"AddMvc",
"ConfigureServices(...)"));
}
var middlewarePipelineBuilder =
app.ApplicationServices.GetRequiredService
<MiddlewareFilterBuilder>();
middlewarePipelineBuilder.ApplicationBuilder = app.New();
var routes = new RouteBuilder(app)
{
DefaultHandler =
app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
configureRoutes(routes);
routes.Routes.Insert(0,
AttributeRouting.CreateAttributeMegaRoute(
app.ApplicationServices));
return app.UseRouter(routes.Build());}
您可以按如下方式分解前面的代码:
UseMvc方法创建 RouteBuilder 的一个实例。RouteBuilder需要DefaultHandler处理路由;这是由MvcRouteHandler类提供的。- 类
MvcRouteHandler实现IRouter,完成 URL 模式匹配和生成 URL 的工作。 configureRoutes是配置路由的动作方式。在前面的示例中,我们完成了相同的任务。AttributeRouting作为IRouter集合的第一个条目添加。CreateAttributeMegaRoute方法扫描所有控制器动作并自动建立路由。这是UseMvc的重要行之一。- 最后,使用
UseRouter构建路由并添加到路由中间件。这与本章开头执行的任务类似。
UseMvc中的AttributeRouting.CreateAttributeMegaRoute在 ASP.NET Core MVC 和 web API 应用中默认提供属性路由。
基于约定的路由
在 ASP.NET Web API 的第一个版本中,路由机制是基于约定的。这种类型的路由具有一个或多个路由模板的参数化字符串定义。
ASP.NET Core 仍然支持这种类型的路由;下面的代码片段显示了如何实现这一点:
public void Configure(IApplicationBuilder app)
{
//Rest of code removed for brevity
app.UseMvc(routes =>
{
// route1
routes.MapRoute(
name: "packtroute1",
template: "api/{controller}/{id}"
);
// route2
routes.MapRoute(
name: "packtroute1",
template: "testpackt",
defaults: new { controller = "Books", action = "Index" }
);
});
}
基于约定的路由方式不受欢迎,原因如下:
- 它不支持 web API 世界中常见的某些类型的 URL 模式
- 具有子资源的资源很难创建。例如,
/products/1/orders/2/reviews - 当 web API 有许多控制器和操作时,它是不可伸缩的
基于模板的路由
在使用 web API 时,您可能会遇到多种 URI,如/product/12、/product/12/orders、/departments/、/books等等。
在 WebAPI 世界中,它们被称为路由——一个描述 URI 模板的字符串。例如,可以在此 URI 模式上形成一个示例路由:/products/{id}/orders。
这里有几点需要注意:
- URI 模板由文本和参数组成
- 在前面的示例中,产品和订单是文本
- 大括号{}中的任何内容都称为参数–
{id}就是这样一个例子 - 路径分隔符(
/必须是路由模板的一部分——URI 将/理解为路径分隔符 - 文本、路径分隔符和参数的组合应与 URI 模式匹配
使用 web API 时,文本将是控制器或方法。路由参数在使路由模板具有多用途方面起着重要作用。大括号中的参数可以发挥多用途作用,如下所示:
- 即使管线模板具有参数,也可以通过在模板中放置“?”使其成为可选参数。例如,
/books/chapters/{numb?}——这里,如果我们不提供numb,那么它将加载所有章节,如果我们提供 numb,那么将加载相关章节。 - 一个管线模板可以有多个管线参数。
- 路由参数可以使用
*作为前缀,以便绑定到 URI 的其余部分。这种参数称为catch-all参数。 - 可以为管线参数提供默认值。此默认值将在未为其提供 route 参数时生效。例如,
"{controller=Home}/{action=Index}"将在Home控制器中加载Index动作方法。 - 管线参数可以具有约束,以确保以正确的方式生成管线。例如,
/customers/{id:int}/services/。{id:int}参数表示id必须是整数,否则 web API 响应 404 响应类型。
以下是在Startup class的Configure方法中定义的几个示例路由模板:
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
// Route Template with default values and optional parameter
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
//Route Template with default value, parameter constrainst
routes.MapRoute(
name: "alternate_route",
template: "{controller}/{action}/{id:int}/{guid:string}",
defaults: new { controller = "Dashboard" });
// Route Template with no default values or parameters
routes.MapRoute(
name: "simple_route",
template: "{controller}/{action}");
});
}
基于属性的路由
在.NET 编程世界中,将各种元素(如类、方法和枚举)的声明信息添加到程序中的标记称为属性。
ASP.NET Web API 2 引入了基于属性的路由的概念,为 Web API 中的 URI 提供了更多控制。这有助于我们轻松构建具有资源层次结构的 URI。
在 ASP.NET Core 应用中,默认情况下提供基于属性的路由。在Startup类的Configure方法中,app.UseMvc()行表示在请求处理管道中包含 MVC 中间件。
在ASP.NET Core Web API 和路由一节中,我们解释了默认情况下如何实现基于属性的路由,这与 ASP.NET Web API 2 不同,后者必须在配置中显式启用。
AttributeRouting.CreateAttributeMegaRoute重复执行所有 MVC 控制器操作并自动构建路由的繁重工作。
当我们在第 3 章ASP.NET Core Web API 剖析中创建演示项目时,它是使用 ASP.NET Core 工具附带的 Web API 模板创建的。
让我们了解演示项目中ValuesController.cs的属性路由:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace MyFirstCoreAPI.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value is " + id;
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
ValuesController类用Route属性修饰为[Route("api/[controller]")]。
在运行应用时,当我们导航到http://localhost:5000/api/values/时,您可以看到结果,如以下屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00019.jpeg
Attribute Route in action
在控制器上定义属性路由;我们需要访问 URL,如前面的屏幕截图所示。/values为控制器名称,测试时根据HttpGet动词执行动作Get和Get(int id)。
RESTful 应用的属性路由
在语法上,属性路由定义如下:
[HttpMethod("Template URI", Name?, Order?)]
- 基于 HttpVerbs,ASP.NET Core 提供了这些用于属性路由的 HttpMethods–
HttpDeleteAttribute、HttpGetAttribute、HttpHeadAttribute、HttpOptionsAttribute、HttpPatchAttribute、HttpPostAttribute和HttpPutAttribute。 Template URI是描述路由的字符串。Name——属性路由的可选名称。它通常在操作似乎是重载方法时使用。- 命令有助于在控制器中执行重载 HTTP 方法时优先。顺序取决于文字、参数、约束以及顺序。这是一个可选参数。
考虑到这些语法,让我们创建一个新的 web API 控制器PacktController,添加操作方法,并为其添加属性路由:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace MyFirstCoreAPI.Controllers
{
[Route("api/[controller]")]
public class PacktController : Controller
{
// Get: api/packt/show
[HttpGet("Show")]
public string Show()
{
return "I am Packt Show !!";
}
// GET api/packt
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "Packt 1", "Packt 2" };
}
// GET: /api/packt/13
[HttpGet("{id:int}", Name = "GetPacktById", Order = 0)]
public string Get(int id)
{
return "Response from GetPacktById" + id;
}
// POST: /api/packt
[HttpPost]
public IActionResult Post()
{
return Content("Created Post !!");
}
// POST: /api/packt/packtpost
[HttpPost("packtpost")]
public IActionResult Post([FromBody]string chapterName)
{
return Content("You invoked packt post");
}
// PUT api/packt/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/packt/15
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
您可以按如下方式分解前面的代码:
PacktController类有以下七种动作方法:
Show():使用路由/api/packt/show上的HttpGet请求调用。Get():使用路由/api/packt上的HttpGet请求调用。Get(int id):使用路由/api/packt/13上的HttpGet请求调用。此处提供了路由名称。Post():使用路由/api/packt/上的HttpPost请求调用。Post([FromBody]string chapterName):使用路由/api/packt/packtpost上的HttpPost请求调用。Put():使用路由api/packt/12上的HttpPut请求调用。提供路由参数 ID,因为PUT对应更新功能,对现有记录进行更新。Delete():使用路由api/packt/12上的HttpDelete请求调用。要删除任何记录,我们需要传递其唯一标识;因此,需要传递 ID route 参数。
多路线
有时,我们可能会得到路由需求,例如应用于同一控制器或操作方法的不同路由。起初,这似乎非常令人惊讶,但在大型项目中,我们可能需要这种路由。
通过在控制器上放置多个路由属性,可以实现多个路由,如以下代码段所示:
[Route("Stocks")]
[Route("[controller]")]
public class PacktsController : Controller
{
[HttpGet("Check")]
[HttpGet("Available")]
public string GetStock()
{
return "Its multiple route";
}
}
运行应用以查看正在运行的多个路由。您可以通过访问浏览器上的端点/api/stocks/check or /api/packts/available来验证多条路由是否正常工作。
路由约束
ASP.NET Core 作为 MVC 或 web API 应用,支持属性和集中式路由机制。可以使用 Route 属性直接在控制器和操作上设置路由,也可以通过在一个位置创建和映射所有路由来设置路由。
我们已经看到,可以使用或不使用管线参数创建不同的管线模板。当路由模板中包含参数时,有助于构建优秀的路由模式。但是路线参数的存在也会引起问题;我们来看一个例子:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
var routeBuilder = new RouteBuilder(app);
routeBuilder.MapGet("employee/{id}", context =>
{
var idValue = context.GetRouteValue("id");
return context.Response.WriteAsync($"The number is - {idValue}");
});
var routes = routeBuilder.Build();
app.UseRouter(routes);
}
您可以按如下方式分解前面的代码:
- 提供模板为
employee/{id}的基本路由,其中{id}为路由参数 - routes 模板用于通过传递 ID 来获取员工列表
GetRouteValue读取{id}参数,并返回响应
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00020.jpeg
Route Parameters without constraints
运行应用以查看提供路由参数的不同方式。看起来一切都在运转——事实上,它运转得很好,但问题仍然存在。
这里,我们假设员工将其 ID 存储为整数,因为大多数组织使用整数作为员工 ID,例如 John 的员工 ID 为 23,Sarah 的员工 ID 为 45。当路由参数 ID 作为整数传递时,一切正常,但是如果{id}作为字符串传递,会怎么样,如上图所示?
应用确实接受参数,并且响应良好。但是,在处理数据库源的实际项目时,作为字符串提供的 ID 将破坏应用,并导致异常或错误。
一个明显的问题是,如果在路由参数中传递了错误的数据,我们如何限制对此类路由参数的请求处理。答案是路线限制。
如果参数未能满足应用于它们的约束条件,路由约束可以帮助我们限制请求处理。
可通过在参数上使用:添加参数约束。例如,"employee/{id:int}"
当带有路由参数约束的请求发送无效类型时,返回的 HTTP 响应为 404(未找到)。是的,由于路由限制,ASP.NET Core 将失败的 HTTP 请求响应为 404(未找到)。请求处理不会影响处理请求的控制器。
让我们对前面代码中的{id}参数应用整数约束,并运行应用,如下所示:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
var routeBuilder = new RouteBuilder(app);
// Id parameter should be Integer now
routeBuilder.MapGet("employee/{id:int}", context =>
{
var idValue = context.GetRouteValue("id");
return context.Response.WriteAsync($"The number is - {idValue}");
});
// Name parameter should be length 8
routeBuilder.MapGet("product/{name:length(8)}", context =>
{
var nameValue = context.GetRouteValue("id");
return context.Response.WriteAsync($"The Name is - {nameValue}");
});
var routes = routeBuilder.Build();
app.UseRouter(routes);
}
您可以按如下方式分解前面的代码:
- 路由模板
employee/{id:int}只取参数 ID 为整数 - 路由模板
product/{name:length(8)}参数仅取长度 8
运行应用以查看路由现在在请求处理中是否得到了规范。路线限制是第一道防线。
下图显示,当路由参数名称的长度为 8 时,它正确地返回了响应。但当名称长度不是 8 时,它返回 404 错误:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00021.jpeg
Route Constraints in action
管线约束也可以应用于属性管线。下面是一个例子:
using Microsoft.AspNetCore.Mvc;
namespace MyFirstCoreAPI.Controllers
{
[Route("api/[controller]")]
public class PacktController : Controller
{
// GET: /api/packt/13
[HttpGet("{id:int}")]
public string Get(int id)
{
return "Response from " + id;
}
}
}
路线限制的类型
ASP.NET Core 团队基于不同的数据类型创建了一系列广泛使用的路由约束场景。下表列出了不同的管线约束:
| 约束名称 | 用法 | 备注 |
| Int | {id:int} | 参数应为整数 |
| 布尔 | {isExists:bool} | 参数应为 TRUE 或 FALSE |
| 日期时间 | {eventdate:datetime} | 仅接受日期时间作为参数 |
| 十进制的 | {amount:decimal} | 仅接受十进制作为参数 |
| 双重的 | {weight:double} | 仅接受 double 作为参数 |
| 浮动 | {distance:float} | 仅将 float 作为参数匹配 |
| 指南 | {id:guid} | 仅接受 GUID 作为参数 |
| 长的 | {ticks:long} | 参数应为长类型 |
| 最小长度 | {username:minlength(8)} | 参数的最小长度应为 8 |
| 最大长度 | {filename:maxlength(5)} | 参数的最大长度应为 5 |
| 长度(最小值、最大值) | {name:length(4,16)} | 参数的最小长度可以是 4,最大长度可以是 16 |
| 最小值(值) | {age:min(18)} | 参数年龄的最小值应为 18 |
| 最大值 | {weight:max(90)} | 参数权重的最大值应为 90 |
| 范围(最小值、最大值) | {age:range(18,100)} | 年龄参数应介于 18 到 100 之间 |
| 阿尔法 | {name:alpha} | 仅允许字母作为参数 |
| 正则表达式(表达式) | {email:regex(/\S+@\S+\.\S+/} | 使用正则表达式匹配参数;电子邮件就是这样一个例子 |
| 要求的 | {productId:required} | 必须提供参数 |
我们可以根据需求将不同的路由约束组合到参数中。例如,如果tableno是一个整数,并且在 18 到 30 之间可用,那么我们可以定义如下路由:
[Route("api/[controller]")]
public class HotelController : Controller
{
[HttpGet("{tableno:int}/{tableno:range(18, 30)}")]
public string Get()
{
return "Table Range is 18 - 30";
}
}
编写自定义路由约束
到目前为止,我们使用了内置的路由约束;它们服务于大量用例,但是不同的业务需求可能导致编写定制约束的需要。使用Microsoft.AspNetCore.Routing命名空间中的IRouteConstraint接口编写自定义路由约束。
接口有Match方式,采用HttpContext、IRoute、routeKey、values、RouteDirection。当Match方法实现时,如果约束条件与参数值匹配,则返回TRUE,否则返回FALSE。
例如,让我们使用自定义路由约束的业务用例,该参数指定该参数应该包含域名为 OutT0},否则它应该以 404 的错误响应。
- 通过复制以下代码创建一个 C#类
DomainConstraint。在此代码中我们实现了IRouteConstraint接口和Match方法:
public class DomainConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext, IRouter route,
string routeKey, RouteValueDictionary values,
RouteDirection routeDirection)
{
var isMatch = false;
if (values["domName"].ToString().Contains("@packt.com"))
{
isMatch = true;
}
return isMatch;
}
}
- 打开
Startup.cs,在ConfigureServices方法中将该约束添加为RouteOption。 - 在
Startup.cs中,使用Configure方法中路由模板中的 DomainConstraint 域,如下所示:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Routing;
namespace BasicRoutes
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Adding Router Middleware
services.AddRouting();
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("domain",
typeof(DomainConstraint)));
}
public void Configure(IApplicationBuilder app,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var routeBuilder = new RouteBuilder(app);
// domName parameter should have @packt.com
routeBuilder.MapGet("api/employee/{domName:domain}",
context =>
{
var domName = context.GetRouteValue("domName");
return context.Response.WriteAsync(
$"Domain Name Constraint Passed - {domName}");
});
var routes = routeBuilder.Build();
app.UseRouter(routes);
}
}
}
运行前面的应用将显示如下响应。
由于 route 参数包含@packt.com域,根据自定义约束,这可以很好地工作,如此屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00022.jpeg
Domain Constraint returns response
现在让我们在不使用@packt.com的情况下传递参数;这将抛出如下的404 Not Found错误:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00117.gif
Domain Constraint returns 404 error
链接生成
路由机制提供了足够的链接到应用中的路由,但是,有时,生成指向特定路由的链接变得至关重要。我们可以使用链接生成概念实现自定义链接。
ASP.NET Core 提供了UrlHelper类,这是IUrlHelper接口的一个实现,用于为 ASP.NET Core 应用(MVC 或 web API)构建 URL。方法CreatedAtRoute和CreatedAtAction是链接生成的两种内置方法。让我们在一个例子中使用它们:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
namespace BasicRoute.Controllers
{
public class TodoController : Controller
{
// List containing Todo Items
List<TodoTasks> TodoList = new List<TodoTasks>();
// Gets Todo item details based on Id
[Route("api/todos/{id}", Name = "GetTodoById")]
public IActionResult GetTodo(int id)
{
var taskitem = TodoList.FirstOrDefault(x => x.Id == id);
if (taskitem == null)
{
return NotFound();
}
return Ok(taskitem);
}
// Adds or POST a todo item to list
[Route("api/todos")]
public IActionResult PostTodo([FromBody]TodoTasks todoItems)
{
TodoList.Add(todoItems);
// CreatedAtRoute generators link
return CreatedAtRoute("GetTodoById", new {
Controller = "Todo", id = todoItems.Id }, todoItems);
}
}
public class TodoTasks
{
public int Id { get; set; }
public string Name { get; set; }
}
}
您可以按如下方式分解前面的代码:
TodoTasks类是基本的 C#类,具有用于 Todo 任务的id和NameTodoController定义了GetTodo和PostTodo两种动作方式,具有适当的属性路由。CreatedAtRoute方法生成一个链接,并将其添加到生成链接的响应头中,以便客户端可以访问该链接。
可以编写一个类似的CreatedAtRoute,以生成更集中的链接。当我们使用 Postman 客户端运行应用时,位置头被显式添加,URL 指向带有id的GetTodo方法,如下面的屏幕截图所示:
https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/master-aspdn-web-api/img/00119.jpeg
Link generation using CreatedAtRoute method
路由最佳实践
NET Core 以中间件、基于约定的路由、直接或属性路由、约束等形式提供了一种轻量级、完全可配置的路由机制。正确使用这些路由功能对于应用的最佳性能是必要的。
下面总结一些需要考虑的最佳实践;随着使用各种用例构建更多的应用,这些实践将不断发展。根据您的应用的需要,这些列表可能适用于您的应用,也可能不适用于您的应用:
- 使用位于路由中间件之上的内置
UseMvc中间件实现路由策略。 - 传统的路由已经足够了,CRUD 风格的 web API 正在开发中。它减少了为各种控制器中的所有操作编写直接路由的开销。
- 尽量避免使用 catch-all 路由参数,即
{*empId}。在传统(集中式)路由中使用时,不需要的路由可能会与此“一网打尽”匹配,从而导致意外行为。 - 在使用 ASP.NET Core Web API 时,最好使用基于 HTTP 谓词的路由,即使用 HTTP 谓词(如 GET、POST、PUT 和 DELETE)的属性路由。客户端可以轻松地使用这些 web API。
- 应该避免在属性路由上排序,而是为不同的 API 方法使用路由名称。
- 多个路由可以指向相同的控制器/操作,但大规模使用此路由可能会导致将操作的实现与许多条件情况相结合。
- 管线参数约束是一项功能强大的功能,但不应将其用于模型验证。
- 应尽量减少使用传统路由和属性路由设计 web API。
总结
ASP.NET Core 路由功能强大且高度可配置。在本章中,您了解了路由中间件如何与 HTTP 管道一起工作。我们使用 RouteBuilder 创建路由表并在它们之间导航。您还了解了在内部实现路由中间件的UseMvc中间件。
路由可以是传统的,也可以是基于属性的,我们使用自定义约束讨论并实现了路由参数。链接生成功能可用于生成到特定路由的链接。您还学习了一些 web API 路由的最佳实践。
下一章将重点介绍 ASP.NET Core 的中间件概念——您将学习中间件的基础知识、编写一些自定义中间件以及 ASP.NET Core 应用中的过滤器。
1万+

被折叠的 条评论
为什么被折叠?



