当前位置:   article > 正文

C#10 和 .NET6 代码跨平台开发(七)_c#多平台运行

c#多平台运行

原文:zh.annas-archive.org/md5/B053DEF9CB8C4C14E67E73C1EC2319CF

译者:飞龙

协议:CC BY-NC-SA 4.0

第十三章:介绍 C#和.NET 的实际应用

本书的第三部分也是最后一部分是关于 C#和.NET 的实际应用。你将学习如何构建跨平台项目,如网站、服务以及移动和桌面应用。

微软将构建应用的平台称为应用模型工作负载

第一章第十八章第二十章中,你可以使用特定操作系统的 Visual Studio 或跨平台的 Visual Studio Code 和 JetBrains Rider 来构建所有应用。在第十九章使用.NET MAUI 构建移动和桌面应用中,尽管你可以使用 Visual Studio Code 来构建移动和桌面应用,但这并不容易。Windows 上的 Visual Studio 2022 对.NET MAUI 的支持比 Visual Studio Code 更好(目前)。

我建议你按顺序阅读本章及后续章节,因为后续章节会引用早期章节中的项目,并且你将积累足够的知识和技能来解决后续章节中更棘手的问题。

本章我们将涵盖以下主题:

  • 理解 C#和.NET 的应用模型

  • ASP.NET Core 中的新特性

  • 项目结构

  • 使用其他项目模板

  • 构建 Northwind 实体数据模型

理解 C#和.NET 的应用模型

由于本书是关于 C# 10 和.NET 6 的,我们将学习使用它们构建实际应用的应用模型,这些应用将在本书剩余章节中遇到。

了解更多:微软在其.NET 应用架构指南文档中提供了丰富的应用模型实施指导,你可以在以下链接阅读:www.microsoft.com/net/learn/architecture

使用 ASP.NET Core 构建网站

网站由多个网页组成,这些网页可以从文件系统静态加载或通过服务器端技术如 ASP.NET Core 动态生成。Web 浏览器使用唯一资源定位符URLs)进行GET请求,这些 URL 标识每个页面,并可以使用POSTPUTDELETE请求操作服务器上存储的数据。

在许多网站中,Web 浏览器被视为表示层,几乎所有的处理都在服务器端执行。客户端可能会使用一些 JavaScript 来实现某些表示层功能,如轮播。

ASP.NET Core 提供了多种构建网站的技术:

  • ASP.NET Core Razor PagesRazor 类库是动态生成简单网站 HTML 的方法。你将在第十四章使用 ASP.NET Core Razor Pages 构建网站中详细学习它们。

  • ASP.NET Core MVC模型-视图-控制器MVC)设计模式的一种实现,该模式在开发复杂网站时非常流行。你将在第十五章使用模型-视图-控制器模式构建网站中详细学习它。

  • Blazor 允许你使用 C#和.NET 构建用户界面组件,而非基于 JavaScript 的 UI 框架如 Angular、React 和 Vue。Blazor WebAssembly 在浏览器中运行你的代码,如同 JavaScript 框架一样。Blazor Server 在服务器上运行你的代码,并动态更新网页。你将在第十七章使用 Blazor 构建用户界面中详细了解 Blazor。Blazor 不仅适用于构建网站,还可用于创建混合移动和桌面应用。

使用内容管理系统构建网站

大多数网站内容繁多,如果每次内容变动都需要开发者介入,这将难以扩展。内容管理系统CMS)使开发者能够定义内容结构和模板,以保持一致性和良好设计,同时让非技术内容所有者轻松管理实际内容。他们可以创建新页面或内容块,并更新现有内容,确保访客看到的内容美观且维护工作量最小。

针对所有网络平台,有多种 CMS 可供选择,如 PHP 的 WordPress 或 Python 的 Django CMS。支持现代.NET 的 CMS 包括 Optimizely 内容云、Piranha CMS 和 Orchard Core。

使用 CMS 的关键优势在于它提供了一个友好的内容管理用户界面。内容所有者登录网站并自行管理内容。内容随后通过 ASP.NET Core MVC 控制器和视图渲染并返回给访问者,或通过称为无头 CMS的网络服务端点,将内容提供给作为移动或桌面应用、店内触点或使用 JavaScript 框架或 Blazor 构建的客户端的“头部”。

本书不涉及.NET CMS,因此我在 GitHub 仓库中提供了链接,供你进一步了解:

github.com/markjprice/cs10dotnet6/blob/main/book-links.md#net-content-management-systems

使用 SPA 框架构建网页应用

网页应用,又称单页应用SPAs),由单一网页构成,采用前端技术如 Blazor WebAssembly、Angular、React、Vue 或专有 JavaScript 库。这些应用在需要时向后台网络服务请求更多数据,并通过 XML 和 JSON 等通用序列化格式发布更新数据。典型例子包括谷歌的 Gmail、地图和文档等网页应用。

在网页应用中,客户端使用 JavaScript 框架或 Blazor WebAssembly 实现复杂的用户交互,但大部分重要处理和数据访问仍在服务器端进行,因为网络浏览器对本地系统资源的访问有限。

JavaScript 是弱类型语言,并非为复杂项目设计,因此当今大多数 JavaScript 库采用微软的 TypeScript,它为 JavaScript 添加了强类型特性,并设计了许多现代语言特性以应对复杂实现。

.NET SDK 提供了基于 JavaScript 和 TypeScript 的 SPA 项目模板,但本书不会花费时间学习如何构建基于 JavaScript 和 TypeScript 的 SPA,尽管这些通常与 ASP.NET Core 作为后端配合使用,因为本书专注于 C#,而非其他语言。

综上所述,C# 和 .NET 可用于服务器端和客户端构建网站,如 图 13.1 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 13.1:C# 和 .NET 用于构建服务器端和客户端网站

构建 Web 及其他服务

尽管我们不会学习基于 JavaScript 和 TypeScript 的 SPA,但我们将学习如何使用 ASP.NET Core Web API 构建 Web 服务,然后从我们的 ASP.NET Core 网站的服务器端代码调用该 Web 服务,之后再从 Blazor WebAssembly 组件以及跨平台移动和桌面应用调用该 Web 服务。

虽然没有正式定义,但服务有时会根据其复杂性进行描述:

  • 服务:客户端应用所需的所有功能集中在一个单一服务中。

  • 微服务:专注于较小功能集的多个服务。

  • Nanoservice:作为服务提供的单一功能。与全天候运行的服务和微服务不同,纳米服务通常处于非活动状态,直到被调用以减少资源和成本。

除了使用 HTTP 作为底层通信技术的 Web 服务以及 API 设计原则外,我们还将学习如何使用其他技术和设计理念构建服务,包括:

  • gRPC 用于构建高效且性能卓越的服务,支持几乎所有平台。

  • SignalR 用于构建组件间的实时通信。

  • OData 用于将 Entity Framework Core 和其他数据模型通过 Web API 进行封装。

  • GraphQL 允许客户端控制跨多个数据源检索哪些数据。

  • Azure Functions 用于在云中托管无服务器纳米服务。

构建移动和桌面应用

移动平台主要有两大阵营:苹果的 iOS 和谷歌的 Android,各自拥有自己的编程语言和平台 API。桌面平台也有两大主流:苹果的 macOS 和微软的 Windows,同样各自拥有自己的编程语言和平台 API,如下表所示:

  • iOS:Objective C 或 Swift 以及 UIkit。

  • Android:Java 或 Kotlin 以及 Android API。

  • macOS:Objective C 或 Swift 以及 AppKit 或 Catalyst。

  • Windows:C、C++ 或多种其他语言,以及 Win32 API 或 Windows App SDK。

由于本书关注的是使用 C# 和 .NET 进行现代跨平台开发,因此不包含使用 Windows FormsWindows Presentation Foundation (WPF) 或 Universal Windows Platform (UWP) 构建桌面应用的内容,因为它们仅限于 Windows 平台。

跨平台移动和桌面应用可以为 .NET 多平台应用用户界面 (MAUI) 平台构建一次,然后就能在多种移动和桌面平台上运行。

.NET MAUI 使得通过共享用户界面组件和业务逻辑来开发这些应用变得简单。它们可以针对与控制台应用、网站和 Web 服务相同的 .NET API。应用将在移动设备上的 Mono 运行时和桌面设备上的 CoreCLR 运行时执行。与常规 .NET CoreCLR 运行时相比,Mono 运行时对移动设备的优化更好。Blazor WebAssembly 也使用 Mono 运行时,因为它像移动应用一样,资源受限。

这些应用可以独立存在,但通常会调用服务以提供跨所有计算设备的体验,从服务器和笔记本电脑到手机和游戏系统。

未来对 .NET MAUI 的更新将支持现有的 MVVM 和 XAML 模式,以及类似 模型-视图-更新 (MVU) 的 C# 模式,这与 Apple 的 Swift UI 类似。

第六版的倒数第二章是 第十九章使用 .NET MAUI 构建跨平台移动和桌面应用,涵盖了使用 .NET MAUI 构建跨平台移动和桌面应用的内容。

.NET MAUI 的替代方案

在微软创建 .NET MAUI 之前,第三方已发起开源倡议,让 .NET 开发者能够使用 XAML 构建跨平台应用,名为 UnoAvalonia

理解 Uno Platform

正如 Uno 在其网站上所述,它是“首个也是唯一一个为 Windows、WebAssembly、iOS、macOS、Android 和 Linux 提供单一代码库应用的 UI 平台”。

开发者可以在原生移动、Web 和桌面平台上重用 99% 的业务逻辑和 UI 层。

Uno Platform 使用 Xamarin 原生平台而非 Xamarin.Forms。对于 WebAssembly,Uno 采用 Mono-WASM 运行时,与 Blazor WebAssembly 类似。在 Linux 上,Uno 利用 Skia 在画布上绘制用户界面。

理解 Avalonia

根据 .NET 基金会的网站所述,Avalonia “是一个跨平台的 XAML 基础 UI 框架,提供灵活的样式系统,并支持广泛的如 Windows、通过 Xorg 的 Linux、macOS 等操作系统。Avalonia 已准备好用于通用桌面应用开发。”

你可以将 Avalonia 视为 WPF 的精神继承者。熟悉 WPF、Silverlight 和 UWP 的开发者可以继续利用他们多年积累的知识和技能。

JetBrains 利用它来现代化其基于 WPF 的工具,并使其跨平台。

Avalonia 的 Visual Studio 扩展以及与 JetBrains Rider 的深度集成使得开发更加简便和高效。

ASP.NET Core 的新特性

过去几年,微软迅速扩展了 ASP.NET Core 的能力。您应注意哪些.NET 平台得到支持,如下表所示:

  • ASP.NET Core 1.0 至 2.2 版本可在.NET Core 或.NET Framework 上运行。

  • ASP.NET Core 3.0 及更高版本仅在.NET Core 3.0 及更高版本上运行。

ASP.NET Core 1.0

ASP.NET Core 1.0 于 2016 年 6 月发布,重点是实现一个适合构建现代跨平台 Web 应用和服务的最小 API,支持 Windows、macOS 和 Linux。

ASP.NET Core 1.1

ASP.NET Core 1.1 于 2016 年 11 月发布,主要关注错误修复和功能及性能的常规改进。

ASP.NET Core 2.0

ASP.NET Core 2.0 于 2017 年 8 月发布,重点增加了新特性,如 Razor Pages、将程序集捆绑到Microsoft.AspNetCore.All元包中、面向.NET Standard 2.0、提供新的认证模型以及性能改进。

ASP.NET Core 2.0 引入的最大新特性包括 ASP.NET Core Razor Pages,这在第十四章使用 ASP.NET Core Razor Pages 构建网站中有所介绍,以及 ASP.NET Core OData 支持,这在第十八章构建和消费专业化服务中有所介绍。

ASP.NET Core 2.1

ASP.NET Core 2.1 于 2018 年 5 月发布,是一个长期支持(LTS)版本,意味着它将支持至 2021 年 8 月 21 日(LTS 标识直到 2018 年 8 月版本 2.1.3 才正式分配给它)。

ASP.NET Core 2.2 专注于增加新特性,如SignalR用于实时通信,Razor 类库用于重用 Web 组件,ASP.NET Core Identity用于认证,以及对 HTTPS 和欧盟通用数据保护条例(GDPR)的更好支持,包括下表中列出的主题:

特性章节主题
Razor 类库14使用 Razor 类库
GDPR 支持15创建和探索 ASP.NET Core MVC 网站
身份验证 UI 库和脚手架15探索 ASP.NET Core MVC 网站
集成测试15测试 ASP.NET Core MVC 网站
[ApiController], ActionResult<T>16创建 ASP.NET Core Web API 项目
问题详情16实现 Web API 控制器
IHttpClientFactory16使用 HttpClientFactory 配置 HTTP 客户端
ASP.NET Core SignalR18使用 SignalR 实现实时通信

ASP.NET Core 2.2

ASP.NET Core 2.2 于 2018 年 12 月发布,重点改进 RESTful HTTP API 的构建,更新项目模板至 Bootstrap 4 和 Angular 6,优化 Azure 托管配置,以及性能改进,包括下表中列出的主题:

特性章节主题
Kestrel 中的 HTTP/214经典 ASP.NET 与现代 ASP.NET Core 的对比
进程内托管模型14创建 ASP.NET Core 项目
端点路由14理解端点路由
健康检查 API16实现健康检查 API
Open API 分析器16实现 Open API 分析器和约定

ASP.NET Core 3.0

ASP.NET Core 3.0 于 2019 年 9 月发布,专注于充分利用.NET Core 3.0 和.NET Standard 2.1,这意味着它不支持.NET Framework,并增加了有用的改进,包括下表列出的主题:

特性章节主题
Razor 类库中的静态资产14使用 Razor 类库
MVC 服务注册的新选项15理解 ASP.NET Core MVC 启动
ASP.NET Core gRPC18使用 ASP.NET Core gRPC 构建服务
Blazor Server17使用 Blazor Server 构建组件

ASP.NET Core 3.1

ASP.NET Core 3.1 于 2019 年 12 月发布,是一款 LTS 版本,意味着它将得到支持直至 2022 年 12 月 3 日。它专注于改进,如 Razor 组件的局部类支持和新的<component>标签助手。

Blazor WebAssembly 3.2

Blazor WebAssembly 3.2 于 2020 年 5 月发布。它是一个当前版本,意味着项目必须在.NET 5 发布后的三个月内升级到.NET 5 版本,即 2021 年 2 月 10 日前。微软最终实现了使用.NET 进行全栈 Web 开发的承诺,并且 Blazor Server 和 Blazor WebAssembly 都在第十七章使用 Blazor 构建用户界面中有所涉及。

ASP.NET Core 5.0

ASP.NET Core 5.0 于 2020 年 11 月发布,重点在于修复错误、使用缓存提高证书认证性能、Kestrel 中 HTTP/2 响应头的 HPACK 动态压缩、ASP.NET Core 程序集的可空性注解,以及减少容器镜像大小,包括下表列出的主题:

特性章节主题
允许匿名访问端点的扩展方法16保护 Web 服务
HttpRequestHttpResponse的 JSON 扩展方法16在控制器中获取 JSON 格式的客户信息

ASP.NET Core 6.0

ASP.NET Core 6.0 于 2021 年 11 月发布,专注于提高生产力的改进,如最小化实现基本网站和服务的代码、.NET 热重载,以及新的 Blazor 托管选项,如使用.NET MAUI 的混合应用,包括下表列出的主题:

特性章节主题
新的空 Web 项目模板14理解空 Web 模板
HTTP 日志记录中间件16启用 HTTP 日志记录
最小 API16实现最小 Web API
Blazor 错误边界17定义 Blazor 错误边界
Blazor WebAssembly AOT17启用 Blazor WebAssembly 预先编译
.NET 热重载17使用.NET 热重载修复代码
.NET MAUI Blazor 应用19在.NET MAUI 应用中托管 Blazor 组件

| 构建仅限 Windows 的桌面应用 |

构建仅限 Windows 的桌面应用的技术包括:

  • Windows Forms, 2002.

  • Windows Presentation Foundation (WPF),2006 年。

  • Windows Store 应用,2012 年。

  • 通用 Windows 平台 (UWP) 应用,2015 年。

  • Windows App SDK(曾用名 WinUI 3Project Reunion)应用,2021 年。

理解传统 Windows 应用平台

随着 1985 年微软 Windows 1.0 的发布,创建 Windows 应用的唯一方式是使用 C 语言并调用名为 kernel、user 和 GDI 的三个核心 DLL 中的函数。当 Windows 95 成为 32 位系统后,这些 DLL 被附加了 32 后缀,并被称为 Win32 API

1991 年,微软推出了 Visual Basic,为开发者提供了一种通过工具箱中的控件进行拖放操作的可视化方式来构建 Windows 应用程序的用户界面。它极受欢迎,Visual Basic 运行时至今仍是 Windows 10 的一部分。

随着 2002 年 C# 和 .NET Framework 的首个版本发布,微软提供了名为 Windows Forms 的技术来构建 Windows 桌面应用。当时,Web 开发的对应技术名为 Web Forms,因此名称相辅相成。代码可以用 Visual Basic 或 C# 语言编写。Windows Forms 拥有类似的拖放式可视化设计器,尽管它生成的是 C# 或 Visual Basic 代码来定义用户界面,这可能对人类来说难以直接理解和编辑。

2006 年,微软发布了一种更强大的技术,名为 Windows Presentation Foundation (WPF),作为 .NET Framework 3.0 的关键组件,与 Windows Communication Foundation (WCF) 和 Windows Workflow (WF) 并列。

尽管可以通过仅编写 C# 语句来创建 WPF 应用,但它也可以使用 可扩展应用程序标记语言 (XAML) 来指定用户界面,这既便于人类理解,也便于代码处理。Windows 版的 Visual Studio 部分基于 WPF 构建。

2012 年,微软发布了 Windows 8,其内置的 Windows Store 应用运行在一个受保护的沙箱环境中。

2015 年,微软发布了 Windows 10,并引入了名为 通用 Windows 平台 (UWP) 的更新版 Windows Store 应用概念。UWP 应用可以使用 C++ 和 DirectX UI、JavaScript 和 HTML 或 C# 结合现代 .NET 的定制分支来构建,该分支虽非跨平台,但能完全访问底层的 WinRT API。

UWP 应用仅能在 Windows 10 平台上运行,不支持早期版本的 Windows,但可在 Xbox 及配备运动控制器的 Windows Mixed Reality 头显上运行。

许多 Windows 开发者因 UWP 应用对底层系统的访问受限而拒绝使用 Windows Store。微软最近推出了 Project ReunionWinUI 3,两者协同工作,使 Windows 开发者能够将现代 Windows 开发的某些优势引入现有的 WPF 应用,并使其享有与 UWP 应用相同的益处和系统集成。这一举措现称为 Windows App SDK

理解现代.NET 对遗留 Windows 平台的支持

.NET SDK 在 Linux 和 macOS 上的磁盘占用约为 330 MB。.NET SDK 在 Windows 上的磁盘占用约为 440 MB。这是因为它包含了 Windows 桌面运行时,该运行时允许遗留的 Windows 应用程序平台 Windows Forms 和 WPF 在现代.NET 上运行。

许多使用 Windows Forms 和 WPF 构建的企业应用程序需要维护或增强新功能,但直到最近它们还停留在.NET Framework 上,这是一个遗留平台。借助现代.NET 及其 Windows 桌面包,这些应用程序现在可以充分利用.NET 的现代功能。

项目结构

你应该如何组织你的项目?到目前为止,我们构建了小型独立的控制台应用程序来演示语言或库功能。在本书的其余部分,我们将使用不同的技术构建多个项目,这些技术协同工作以提供单一解决方案。

在大型复杂的解决方案中,要在所有代码中导航可能会很困难。因此,组织项目的主要原因是使其更容易找到组件。为解决方案或工作区提供一个反映应用程序或解决方案的整体名称是很好的。

我们将为一家名为Northwind的虚构公司构建多个项目。我们将把解决方案或工作区命名为PracticalApps,并使用Northwind作为所有项目名称的前缀。

有许多方法来组织和命名项目和解决方案,例如使用文件夹层次结构以及命名约定。如果你在一个团队中工作,请确保你知道你的团队是如何做的。

在解决方案或工作区中组织项目

在解决方案或工作区中为项目制定命名约定是很好的做法,这样任何开发人员都能立即了解每个项目的作用。常见的选择是使用项目类型,例如类库、控制台应用程序、网站等,如下表所示:

名称描述
Northwind.Common一个类库项目,用于跨多个项目使用的通用类型,如接口、枚举、类、记录和结构。
Northwind.Common.EntityModels一个类库项目,用于通用的 EF Core 实体模型。实体模型通常在服务器和客户端侧都被使用,因此最好将特定数据库提供程序的依赖关系分开。
Northwind.Common.DataContext一个类库项目,用于依赖特定数据库提供程序的 EF Core 数据库上下文。
Northwind.Web一个 ASP.NET Core 项目,用于一个简单的网站,该网站混合使用静态 HTML 文件和动态 Razor 页面。
Northwind.Razor.Component一个类库项目,用于在多个项目中使用的 Razor 页面。
Northwind.Mvc一个 ASP.NET Core 项目,用于使用 MVC 模式的复杂网站,可以更容易地进行单元测试。
Northwind.WebApi一个用于 HTTP API 服务的 ASP.NET Core 项目。与网站集成的好选择,因为它们可以使用任何 JavaScript 库或 Blazor 与服务交互。
Northwind.OData一个实现 OData 标准以允许客户端控制查询的 HTTP API 服务的 ASP.NET Core 项目。
Northwind.GraphQL一个实现 GraphQL 标准以允许客户端控制查询的 HTTP API 服务的 ASP.NET Core 项目。
Northwind.gRPC一个用于 gRPC 服务的 ASP.NET Core 项目。与使用任何语言和平台构建的应用程序集成的好选择,因为 gRPC 支持广泛且高效且性能优越。
Northwind.SignalR一个用于实时通信的 ASP.NET Core 项目。
Northwind.AzureFuncs一个用于在 Azure Functions 中托管的无服务器纳米服务的 ASP.NET Core 项目。
Northwind.BlazorServer一个 ASP.NET Core Blazor 服务器项目。
Northwind.BlazorWasm.Client一个 ASP.NET Core Blazor WebAssembly 客户端项目。
Northwind.BlazorWasm.Server一个 ASP.NET Core Blazor WebAssembly 服务器端项目。
Northwind.Maui一个用于跨平台桌面/移动应用的.NET MAUI 项目。
Northwind.MauiBlazor一个用于托管 Blazor 组件并具有与操作系统原生集成的.NET MAUI 项目。

使用其他项目模板

安装.NET SDK 时,包含许多项目模板:

  1. 在命令提示符或终端中,输入以下命令:

    dotnet new --list 
    
    • 1
  2. 您将看到当前安装的模板列表,如果您在 Windows 上运行,还包括 Windows 桌面开发模板,如图13.2所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 13.2:dotnet 项目模板列表

  3. 注意与 Web 相关的项目模板,包括使用 Blazor、Angular 和 React 创建 SPA 的模板。但另一个常见的 JavaScript SPA 库缺失了:Vue。

安装额外的模板包

开发者可以安装许多额外的模板包:

  1. 启动浏览器并导航至dotnetnew.azurewebsites.net/

  2. 在文本框中输入vue,并注意 Vue.js 可用模板列表,其中包括微软发布的一个模板,如图13.3所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 13.3:微软提供的 Vue.js 项目模板

  3. 点击微软提供的 ASP.NET Core with Vue.js,并注意安装和使用此模板的说明,如下列命令所示:

    dotnet new --install "Microsoft.AspNetCore.SpaTemplates"
    dotnet new vue 
    
    • 1
    • 2
  4. 点击查看此包中的其他模板,并注意除了 Vue.js 的项目模板外,它还有 Aurelia 和 Knockout.js 的项目模板。

为 Northwind 数据库构建实体数据模型

实际应用通常需要与关系数据库或其他数据存储进行交互。在本章中,我们将为存储在 SQL Server 或 SQLite 中的 Northwind 数据库定义实体数据模型。它将被用于我们后续章节创建的大多数应用中。

Northwind4SQLServer.sqlNorthwind4SQLite.sql脚本文件有所不同。SQL Server 脚本创建了 13 个表以及相关的视图和存储过程。SQLite 脚本是一个简化版本,仅创建 10 个表,因为 SQLite 不支持那么多特性。本书的主要项目仅需要这 10 个表,因此你可以使用任一数据库完成本书中的所有任务。

安装 SQL Server 和 SQLite 的指南可在第十章使用 Entity Framework Core 处理数据中找到。该章节还包含了安装dotnet-ef工具的说明,该工具用于从现有数据库生成实体模型。

最佳实践:应为实体数据模型创建单独的类库项目。这使得在后端 Web 服务器和前端桌面、移动及 Blazor WebAssembly 客户端之间共享数据更为便捷。

为实体模型创建使用 SQLite 的类库

现在,你将在类库中定义实体数据模型,以便它们能在包括客户端应用模型在内的其他类型项目中复用。如果你未使用 SQL Server,则需为 SQLite 创建此类库。若使用 SQL Server,则可同时为 SQLite 和 SQL Server 创建类库,并根据需要切换使用。

我们将使用 EF Core 命令行工具自动生成一些实体模型:

  1. 使用你偏好的代码编辑器创建一个名为PracticalApps的新解决方案/工作区。

  2. 添加一个类库项目,如下列表所述:

    1. 项目模板:类库 / classlib

    2. 工作区/解决方案文件和文件夹:PracticalApps

    3. 项目文件和文件夹:Northwind.Common.EntityModels.Sqlite

  3. Northwind.Common.EntityModels.Sqlite项目中,添加 SQLite 数据库提供程序和 EF Core 设计时支持的包引用,如下所示:

    <ItemGroup>
      <PackageReference
        Include="Microsoft.EntityFrameworkCore.Sqlite" 
        Version="6.0.0" />
      <PackageReference 
        Include="Microsoft.EntityFrameworkCore.Design" 
        Version="6.0.0">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>  
    </ItemGroup> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  4. 删除Class1.cs文件。

  5. 构建项目。

  6. 通过将Northwind4SQLite.sql文件复制到PracticalApps文件夹,为 SQLite 创建Northwind.db文件,然后在命令提示符或终端中输入以下命令:

    sqlite3 Northwind.db -init Northwind4SQLite.sql 
    
    • 1
  7. 请耐心等待,因为此命令可能需要一段时间来创建数据库结构,如下所示:

    -- Loading resources from Northwind4SQLite.sql 
    SQLite version 3.35.5 2021-04-19 14:49:49
    Enter ".help" for usage hints.
    sqlite> 
    
    • 1
    • 2
    • 3
    • 4
  8. 在 Windows 上按 Ctrl + C 或在 macOS 上按 Cmd + D 以退出 SQLite 命令模式。

  9. 打开命令提示符或终端,定位到Northwind.Common.EntityModels.Sqlite文件夹。

  10. 在命令行中,为所有表生成实体类模型,如下所示:

    dotnet ef dbcontext scaffold "Filename=../Northwind.db" Microsoft.EntityFrameworkCore.Sqlite --namespace Packt.Shared --data-annotations 
    
    • 1

    注意以下事项:

    • 执行的命令:dbcontext scaffold

    • 连接字符串:"Filename=../Northwind.db"

    • 数据库提供程序:Microsoft.EntityFrameworkCore.Sqlite

    • 命名空间:--namespace Packt.Shared

    • 同时使用数据注解和 Fluent API:--data-annotations

  11. 注意构建消息和警告,如下面的输出所示:

    Build started...
    Build succeeded.
    To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148\. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. 
    
    • 1
    • 2
    • 3

改进类到表的映射

dotnet-ef命令行工具为 SQL Server 和 SQLite 生成不同的代码,因为它们支持不同级别的功能。

例如,SQL Server 文本列可以有字符数量的限制。SQLite 不支持这一点。因此,dotnet-ef将生成验证属性,以确保string属性在 SQL Server 上限制为指定数量的字符,而在 SQLite 上则不限制,如下面的代码所示:

// SQLite database provider-generated code
[Column(TypeName = "nvarchar (15)")] 
public string CategoryName { get; set; } = null!;
// SQL Server database provider-generated code 
[StringLength(15)]
public string CategoryName { get; set; } = null!; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

两种数据库提供程序都不会将非可空string属性标记为必填:

// no runtime validation of non-nullable property
public string CategoryName { get; set; } = null!;
// nullable property
public string? Description { get; set; }
// decorate with attribute to perform runtime validation
[Required]
public string CategoryName { get; set; } = null!; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们将对 SQLite 的实体模型映射和验证规则进行一些小改进:

  1. 打开Customer.cs文件,并添加一个正则表达式以验证其主键值,仅允许使用大写西文字符,如下面的代码中突出显示所示:

    [Key]
    [Column(TypeName = "nchar (5)")]
    **[****RegularExpression(****"[A-Z]{5}"****)****]**
    public string CustomerId { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
  2. 激活代码编辑器的查找和替换功能(在 Visual Studio 2022 中,导航至编辑 | 查找和替换 | 快速替换),切换使用正则表达式,然后在搜索框中输入一个正则表达式,如下面的表达式所示:

    \[Column\(TypeName = "(nchar|nvarchar) \((.*)\)"\)\] 
    
    • 1
  3. 在替换框中,输入一个替换用的正则表达式,如下面的表达式所示:

    $&\n    [StringLength($2)] 
    
    • 1

    在新行字符\n之后,我添加了四个空格字符以在我的系统上正确缩进,该系统每级缩进使用两个空格字符。您可以根据需要插入任意数量的空格。

  4. 设置查找和替换以搜索当前项目中的文件。

  5. 执行搜索和替换以替换所有内容,如图13.4所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 13.4:在 Visual Studio 2022 中使用正则表达式搜索并替换所有匹配项

  6. 将任何日期/时间属性,例如在Employee.cs中,改为使用可空DateTime而非字节数组,如下面的代码所示:

    // before
    [Column(TypeName = "datetime")] 
    public byte[] BirthDate { get; set; }
    // after
    [Column(TypeName = "datetime")]
    public DateTime? BirthDate { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用代码编辑器的查找功能搜索"datetime"以找到所有需要更改的属性。

  7. 将任何money属性,例如在Order.cs中,改为使用可空decimal而非字节数组,如下面的代码所示:

    // before
    [Column(TypeName =  "money")] 
    public byte[] Freight { get; set; }
    // after
    [Column(TypeName = "money")]
    public decimal? Freight { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用代码编辑器的查找功能搜索"money"以找到所有需要更改的属性。

  8. 将任何bit属性,例如在Product.cs中,改为使用bool而非字节数组,如下面的代码所示:

    // before
    [Column(TypeName = "bit")]
    public byte[] Discontinued { get; set; } = null!;
    // after
    [Column(TypeName = "bit")]
    public bool Discontinued { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用代码编辑器的查找功能搜索"bit"以找到所有需要更改的属性。

  9. Category.cs中,将CategoryId属性设为int类型,如以下代码中突出显示所示:

    [Key]
    public **int** CategoryId { get; set; } 
    
    • 1
    • 2
  10. Category.cs中,将CategoryName属性设为必填项,如以下代码中突出显示所示:

    **[****Required****]**
    [Column(TypeName = "nvarchar (15)")]
    [StringLength(15)]
    public string CategoryName { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
  11. Customer.cs中,将CompanyName属性设为必填项,如以下代码中突出显示所示:

    **[****Required****]**
    [Column(TypeName = "nvarchar (40)")]
    [StringLength(40)]
    public string CompanyName { get; set; } 
    
    • 1
    • 2
    • 3
    • 4
  12. Employee.cs中,将EmployeeId属性改为int而非long

  13. Employee.cs中,使FirstNameLastName属性成为必填项。

  14. Employee.cs中,将ReportsTo属性改为int?而非long?

  15. EmployeeTerritory.cs中,将EmployeeId属性改为int而非long

  16. EmployeeTerritory.cs中,使TerritoryId属性成为必填项。

  17. Order.cs中,将OrderId属性改为int而非long

  18. Order.cs中,用正则表达式装饰CustomerId属性,以强制五个大写字符。

  19. Order.cs中,将EmployeeId属性改为int?而非long?

  20. Order.cs中,将ShipVia属性改为int?而非long?

  21. OrderDetail.cs中,将OrderId属性改为int而非long

  22. OrderDetail.cs中,将ProductId属性改为int而非long

  23. OrderDetail.cs中,将Quantity属性改为short而非long

  24. Product.cs中,将ProductId属性改为int而非long

  25. Product.cs中,使ProductName属性成为必填项。

  26. Product.cs中,将SupplierIdCategoryId属性改为int?而非long?

  27. Product.cs中,将UnitsInStockUnitsOnOrderReorderLevel属性改为short?而非long?

  28. Shipper.cs中,将ShipperId属性改为int而非long

  29. Shipper.cs中,使CompanyName属性成为必填项。

  30. Supplier.cs中,将SupplierId属性改为int而非long

  31. Supplier.cs中,使CompanyName属性成为必填项。

  32. Territory.cs中,将RegionId属性改为int而非long

  33. Territory.cs中,使TerritoryIdTerritoryDescription属性成为必填项。

既然我们有了实体类的类库,我们就可以为数据库上下文创建一个类库。

为 Northwind 数据库上下文创建一个类库

你现在将定义一个数据库上下文类库:

  1. 向解决方案/工作区添加一个类库项目,如以下列表所定义:

    1. 项目模板:类库 / classlib

    2. 工作区/解决方案文件和文件夹:PracticalApps

    3. 项目文件和文件夹:Northwind.Common.DataContext.Sqlite

  2. 在 Visual Studio 中,将解决方案的启动项目设置为当前选择。

  3. 在 Visual Studio Code 中,选择Northwind.Common.DataContext.Sqlite作为活动的 OmniSharp 项目。

  4. Northwind.Common.DataContext.Sqlite项目中,添加对Northwind.Common.EntityModels.Sqlite项目的项目引用,并添加对 EF Core SQLite 数据提供程序的包引用,如下所示:

    <ItemGroup>
      <PackageReference 
        Include="Microsoft.EntityFrameworkCore.SQLite" 
        Version="6.0.0" />
    </ItemGroup>
    <ItemGroup>
      <ProjectReference Include=
        "..\Northwind.Common.EntityModels.Sqlite\Northwind.Common
    .EntityModels.Sqlite.csproj" />
    </ItemGroup> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    项目引用的路径在你的项目文件中不应有换行。

  5. Northwind.Common.DataContext.Sqlite项目中,删除Class1.cs类文件。

  6. 构建Northwind.Common.DataContext.Sqlite项目。

  7. NorthwindContext.cs 文件从 Northwind.Common.EntityModels.Sqlite 项目/文件夹移动到 Northwind.Common.DataContext.Sqlite 项目/文件夹。

    在 Visual Studio 解决方案资源管理器中,如果你在项目间拖放文件,它将被复制。如果你在拖放时按住 Shift 键,文件将被移动。在 Visual Studio Code 资源管理器中,如果你在项目间拖放文件,它将被移动。如果你在拖放时按住 Ctrl 键,文件将被复制。

  8. NorthwindContext.cs 中,在 OnConfiguring 方法中,移除关于连接字符串的编译器 #warning

    良好实践:我们将在需要与 Northwind 数据库交互的任何项目(如网站)中覆盖默认数据库连接字符串,因此从 DbContext 派生的类必须有一个带有 DbContextOptions 参数的构造函数才能实现这一点,如下列代码所示:

    public NorthwindContext(DbContextOptions<NorthwindContext> options)
      : base(options)
    {
    } 
    
    • 1
    • 2
    • 3
    • 4
  9. OnModelCreating 方法中,移除所有调用 ValueGeneratedNever 方法的 Fluent API 语句,以配置主键属性如 SupplierId 永不自动生成值,或调用 HasDefaultValueSql 方法,如下列代码所示:

    modelBuilder.Entity<Supplier>(entity =>
    {
      entity.Property(e => e.SupplierId).ValueGeneratedNever();
    }); 
    
    • 1
    • 2
    • 3
    • 4

    如果我们不移除上述配置语句,那么当我们添加新供应商时,SupplierId 值将始终为 0,我们只能添加一个具有该值的供应商,之后所有其他尝试都会抛出异常。

  10. 对于 Product 实体,告诉 SQLite UnitPrice 可以从 decimal 转换为 doubleOnModelCreating 方法现在应该简化很多,如下列代码所示:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity<OrderDetail>(entity =>
      {
        entity.HasKey(e => new { e.OrderId, e.ProductId });
        entity.HasOne(d => d.Order)
          .WithMany(p => p.OrderDetails)
          .HasForeignKey(d => d.OrderId)
          .OnDelete(DeleteBehavior.ClientSetNull);
        entity.HasOne(d => d.Product)
          .WithMany(p => p.OrderDetails)
          .HasForeignKey(d => d.ProductId)
          .OnDelete(DeleteBehavior.ClientSetNull);
      });
      modelBuilder.Entity<Product>()
        .Property(product => product.UnitPrice)
        .HasConversion<double>();
      OnModelCreatingPartial(modelBuilder);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  11. 添加一个名为 NorthwindContextExtensions.cs 的类,并修改其内容以定义一个扩展方法,该方法将 Northwind 数据库上下文添加到依赖服务集合中,如下列代码所示:

    using Microsoft.EntityFrameworkCore; // UseSqlite
    using Microsoft.Extensions.DependencyInjection; // IServiceCollection
    namespace Packt.Shared;
    public static class NorthwindContextExtensions
    {
      /// <summary>
      /// Adds NorthwindContext to the specified IServiceCollection. Uses the Sqlite database provider.
      /// </summary>
      /// <param name="services"></param>
      /// <param name="relativePath">Set to override the default of ".."</param>
      /// <returns>An IServiceCollection that can be used to add more services.</returns>
      public static IServiceCollection AddNorthwindContext(
        this IServiceCollection services, string relativePath = "..")
      {
        string databasePath = Path.Combine(relativePath, "Northwind.db");
        services.AddDbContext<NorthwindContext>(options =>
          options.UseSqlite($"Data Source={databasePath}")
        );
        return services;
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  12. 构建两个类库并修复任何编译器错误。

使用 SQL Server 创建实体模型的类库

要使用 SQL Server,如果您已经在 第十章使用 Entity Framework Core 处理数据 中设置了 Northwind 数据库,则无需执行任何操作。但现在您将使用 dotnet-ef 工具创建实体模型:

  1. 使用您偏好的代码编辑器创建一个名为 PracticalApps 的新解决方案/工作区。

  2. 添加一个类库项目,如以下列表所定义:

    1. 项目模板:类库 / classlib

    2. 工作区/解决方案文件和文件夹:PracticalApps

    3. 项目文件和文件夹:Northwind.Common.EntityModels.SqlServer

  3. Northwind.Common.EntityModels.SqlServer 项目中,添加 SQL Server 数据库提供程序和 EF Core 设计时支持的包引用,如下列标记所示:

    <ItemGroup>
      <PackageReference
        Include="Microsoft.EntityFrameworkCore.SqlServer" 
        Version="6.0.0" />
      <PackageReference 
        Include="Microsoft.EntityFrameworkCore.Design" 
        Version="6.0.0">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>  
    </ItemGroup> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  4. 删除 Class1.cs 文件。

  5. 构建项目。

  6. Northwind.Common.EntityModels.SqlServer 文件夹打开命令提示符或终端。

  7. 在命令行中,为所有表生成实体类模型,如下列命令所示:

    dotnet ef dbcontext scaffold "Data Source=.;Initial Catalog=Northwind;Integrated Security=true;" Microsoft.EntityFrameworkCore.SqlServer --namespace Packt.Shared --data-annotations 
    
    • 1

    注意以下事项:

    • 执行的命令:dbcontext scaffold

    • 连接字符串:"Data Source=.;Initial Catalog=Northwind;Integrated Security=true;"

    • 数据库提供程序:Microsoft.EntityFrameworkCore.SqlServer

    • 命名空间:--namespace Packt.Shared

    • 同时使用数据注解和 Fluent API:--data-annotations

  8. Customer.cs 中,添加一个正则表达式以验证其主键值,仅允许大写的西文字符,如下列高亮代码所示:

    [Key]
    [StringLength(5)]
    **[****RegularExpression(****"[A-Z]{5}"****)****]** 
    public string CustomerId { get; set; } = null!; 
    
    • 1
    • 2
    • 3
    • 4
  9. Customer.cs 中,使 CustomerIdCompanyName 属性成为必需。

  10. 向解决方案/工作区中添加一个类库项目,如以下列表所定义:

    1. 项目模板:类库 / classlib

    2. 工作区/解决方案文件和文件夹:PracticalApps

    3. 项目文件和文件夹:Northwind.Common.DataContext.SqlServer

  11. 在 Visual Studio Code 中,选择 Northwind.Common.DataContext.SqlServer 作为活动的 OmniSharp 项目。

  12. Northwind.Common.DataContext.SqlServer 项目中,添加对 Northwind.Common.EntityModels.SqlServer 项目的项目引用,并添加对 EF Core SQL Server 数据提供程序的包引用,如下列标记所示:

    <ItemGroup>
      <PackageReference 
        Include="Microsoft.EntityFrameworkCore.SqlServer" 
        Version="6.0.0" />
    </ItemGroup>
    <ItemGroup>
      <ProjectReference Include=
        "..\Northwind.Common.EntityModels.SqlServer\Northwind.Common
    .EntityModels.SqlServer.csproj" />
    </ItemGroup> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  13. Northwind.Common.DataContext.SqlServer 项目中,删除 Class1.cs 类文件。

  14. 构建 Northwind.Common.DataContext.SqlServer 项目。

  15. NorthwindContext.cs 文件从 Northwind.Common.EntityModels.SqlServer 项目/文件夹移动到 Northwind.Common.DataContext.SqlServer 项目/文件夹。

  16. NorthwindContext.cs 中,移除关于连接字符串的编译器警告。

  17. 添加一个名为 NorthwindContextExtensions.cs 的类,并修改其内容以定义一个扩展方法,该方法将 Northwind 数据库上下文添加到依赖服务集合中,如下列代码所示:

    using Microsoft.EntityFrameworkCore; // UseSqlServer
    using Microsoft.Extensions.DependencyInjection; // IServiceCollection
    namespace Packt.Shared;
    public static class NorthwindContextExtensions
    {
      /// <summary>
      /// Adds NorthwindContext to the specified IServiceCollection. Uses the SqlServer database provider.
      /// </summary>
      /// <param name="services"></param>
      /// <param name="connectionString">Set to override the default.</param>
      /// <returns>An IServiceCollection that can be used to add more services.</returns>
      public static IServiceCollection AddNorthwindContext(
        this IServiceCollection services, string connectionString = 
          "Data Source=.;Initial Catalog=Northwind;"
          + "Integrated Security=true;MultipleActiveResultsets=true;")
      {
        services.AddDbContext<NorthwindContext>(options =>
          options.UseSqlServer(connectionString));
        return services;
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  18. 构建这两个类库并修复任何编译器错误。

最佳实践:我们为 AddNorthwindContext 方法提供了可选参数,以便我们可以覆盖硬编码的 SQLite 数据库文件路径或 SQL Server 数据库连接字符串。这将使我们拥有更多灵活性,例如,从配置文件加载这些值。

实践与探索

通过深入研究探索本章主题。

练习 13.1 – 测试你的知识

  1. .NET 6 是跨平台的。Windows Forms 和 WPF 应用可以在 .NET 6 上运行。那么这些应用是否能在 macOS 和 Linux 上运行呢?

  2. Windows Forms 应用如何定义其用户界面,以及为什么这可能是一个潜在问题?

  3. WPF 或 UWP 应用如何定义其用户界面,以及为什么这对开发者有益?

练习 13.2 – 探索主题

使用以下页面上的链接来了解更多关于本章涵盖主题的详细信息:

github.com/markjprice/cs10dotnet6/blob/main/book-links.md#chapter-13---introducing-practical-applications-of-c-and-net

总结

本章中,你已了解到一些可用于使用 C#和.NET 构建实用应用的应用模型和工作负载。

你已创建了两到四个类库,用于定义与 Northwind 数据库交互的实体数据模型,可使用 SQLite、SQL Server 或两者兼用。

在接下来的六章中,你将学习如何构建以下内容的详细信息:

  • 使用静态 HTML 页面和动态 Razor 页面的简单网站。

  • 采用模型-视图-控制器(MVC)设计模式的复杂网站。

  • 可被任何能发起 HTTP 请求的平台调用的 Web 服务,以及调用这些 Web 服务的客户端网站。

  • 可托管在 Web 服务器、浏览器或混合 Web-原生移动和桌面应用中的 Blazor 用户界面组件。

  • 使用 gRPC 实现远程过程调用的服务。

  • 使用 SignalR 实现实时通信的服务。

  • 提供简便灵活访问 EF Core 模型的服务。

  • Azure Functions 中托管的无服务器微服务。

  • 使用.NET MAUI 构建跨平台的原生移动和桌面应用。

第十四章:使用 ASP.NET Core Razor Pages 构建网站

本章是关于使用 Microsoft ASP.NET Core 在服务器端构建具有现代 HTTP 架构的网站。您将学习使用 ASP.NET Core 2.0 引入的 ASP.NET Core Razor Pages 功能以及 ASP.NET Core 2.1 引入的 Razor 类库功能构建简单的网站。

本章将涵盖以下主题:

  • 理解 Web 开发

  • 理解 ASP.NET Core

  • 探索 ASP.NET Core Razor Pages

  • 使用 Entity Framework Core 与 ASP.NET Core

  • 使用 Razor 类库

  • 配置服务和 HTTP 请求管道

理解 Web 开发

开发 Web 意味着使用超文本传输协议HTTP)进行开发,因此我们将从回顾这一重要的基础技术开始。

理解 HTTP

为了与 Web 服务器通信,客户端,也称为用户代理,使用 HTTP 通过网络进行调用。因此,HTTP 是 Web 的技术基础。所以,当我们谈论网站和 Web 服务时,我们的意思是它们使用 HTTP 在客户端(通常是 Web 浏览器)和服务器之间进行通信。

客户端对资源(如页面)发出 HTTP 请求,该资源由统一资源定位符URL)唯一标识,服务器发送回 HTTP 响应,如图 14.1所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 14.1:HTTP 请求和响应

您可以使用 Google Chrome 和其他浏览器记录请求和响应。

最佳实践:Google Chrome 在比任何其他浏览器更多的操作系统上可用,并且它具有强大的内置开发工具,因此它是测试网站的首选浏览器。始终使用 Chrome 以及至少另外两种浏览器(例如,macOS 和 iPhone 上的 Firefox 和 Safari)测试您的 Web 应用程序。Microsoft Edge 在 2019 年从使用 Microsoft 自己的渲染引擎切换到使用 Chromium,因此测试它的重要性较低。如果使用 Microsoft 的 Internet Explorer,则通常主要用于组织内部网。

理解 URL 的组成部分

URL 由几个部分组成:

  • 方案http(明文)或https(加密)。

  • :对于生产网站或服务,顶级域TLD)可能是example.com。您可能有子域,例如wwwjobsextranet。在开发过程中,您通常为所有网站和服务使用localhost

  • 端口号:对于生产网站或服务,http80https443。这些端口号通常从方案中推断出来。在开发过程中,通常使用其他端口号,例如50005001等,以区分使用共享域localhost的所有网站和服务。

  • 路径:到资源的相对路径,例如,/customers/germany

  • 查询字符串:传递参数值的一种方式,例如,?country=Germany&searchtext=shoes

  • 片段:通过其id引用网页上的元素,例如,#toc

为本书中的项目分配端口号

本书中,我们将为所有网站和服务使用域名localhost,因此当多个项目需要同时执行时,我们将使用端口号来区分项目,如下表所示:

项目描述端口号
Northwind.WebASP.NET Core Razor Pages 网站5000 HTTP, 5001 HTTPS
Northwind.MvcASP.NET Core MVC 网站5000 HTTP, 5001 HTTPS
Northwind.WebApiASP.NET Core Web API 服务5002 HTTPS, 5008 HTTP
Minimal.WebApiASP.NET Core Web API(最小化)5003 HTTPS
Northwind.ODataASP.NET Core OData 服务5004 HTTPS
Northwind.GraphQLASP.NET Core GraphQL 服务5005 HTTPS
Northwind.gRPCASP.NET Core gRPC 服务5006 HTTPS
Northwind.AzureFuncsAzure Functions 纳米服务7071 HTTP

使用 Google Chrome 发起 HTTP 请求

让我们探索如何使用 Google Chrome 发起 HTTP 请求:

  1. 启动 Google Chrome。

  2. 导航至更多工具 | 开发者工具

  3. 点击网络标签,Chrome 应立即开始记录浏览器与任何 Web 服务器之间的网络流量(注意红色圆圈),如图 14.2 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.2:Chrome 开发者工具记录网络流量

  4. 在 Chrome 地址栏中,输入微软学习 ASP.NET 的网站地址,如下所示:

    dotnet.microsoft.com/learn/aspnet

  5. 在开发者工具中,在记录的请求列表中滚动到顶部,点击第一个条目,即类型文档的行,如图 14.3 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.3:开发者工具中记录的请求

  6. 在右侧,点击头部标签,您将看到有关请求头部响应头部的详细信息,如图 14.4 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.4:请求和响应头部

    注意以下方面:

    • 请求方法GET。其他可能在此处看到的 HTTP 方法包括POSTPUTDELETEHEADPATCH

    • 状态码200 OK。这意味着服务器找到了浏览器请求的资源,并将其作为响应的主体返回。其他可能看到的GET请求响应状态码包括301 永久移动400 错误请求401 未授权404 未找到

    • 请求头部由浏览器发送给 Web 服务器,包括:

      • accept,列出了浏览器接受的格式。在这种情况下,浏览器表示它理解 HTML、XHTML、XML 和一些图像格式,但它将接受所有其他文件(*/*)。默认权重,也称为质量值,是1.0。XML 的质量值指定为0.9,因此其优先级低于 HTML 或 XHTML。所有其他文件类型的质量值为0.8,因此优先级最低。

      • accept-encoding,列出了浏览器理解的压缩算法,在这种情况下,包括 GZIP、DEFLATE 和 Brotli。

      • accept-language,列出了浏览器希望内容使用的语言。在这种情况下,首先是美国英语,其默认质量值为1.0,其次是任何具有明确指定质量值0.9的英语方言,然后是任何具有明确指定质量值0.8的瑞典语方言。

    • 响应头content-encoding告诉我服务器已使用 GZIP 算法压缩 HTML 网页响应,因为它知道客户端可以解压缩该格式。(这在图 14.4中不可见,因为没有足够的空间展开响应头部分。)

  7. 关闭 Chrome。

理解客户端网页开发技术

在构建网站时,开发者需要了解的不仅仅是 C#和.NET。在客户端(即在网页浏览器中),您将使用以下技术的组合:

  • HTML5:用于网页的内容和结构。

  • CSS3:用于网页上元素的样式。

  • JavaScript:用于编写网页所需的任何业务逻辑,例如验证表单输入或调用网页服务以获取网页所需的其他数据。

尽管 HTML5、CSS3 和 JavaScript 是前端网页开发的基本组成部分,但还有许多其他技术可以使前端网页开发更高效,包括全球最受欢迎的前端开源工具包 Bootstrap,以及用于样式的 CSS 预处理器如 SASS 和 LESS,用于编写更健壮代码的 Microsoft TypeScript 语言,以及 JavaScript 库如 jQuery、Angular、React 和 Vue。所有这些更高级别的技术最终都会翻译或编译到基础的三个核心技术,因此它们在所有现代浏览器中都能工作。

作为构建和部署过程的一部分,您可能会使用诸如 Node.js;客户端包管理器 npm 和 Yarn;以及 webpack,这是一个流行的模块捆绑器,用于编译、转换和捆绑网站源文件的工具。

理解 ASP.NET Core

Microsoft ASP.NET Core 是微软多年来用于构建网站和服务的一系列技术的一部分:

  • Active Server PagesASP)于 1996 年发布,是微软首次尝试为动态服务器端执行网站代码提供的平台。ASP 文件包含 HTML 和服务器端执行的 VBScript 代码的混合体。

  • ASP.NET Web Forms 于 2002 年随.NET Framework 一同发布,旨在让非 Web 开发者(如熟悉 Visual Basic 的开发者)通过拖放可视组件和编写 Visual Basic 或 C#的事件驱动代码快速创建网站。对于新的.NET Framework Web 项目,应避免使用 Web Forms,转而采用 ASP.NET MVC。

  • Windows Communication FoundationWCF)于 2006 年发布,使开发者能够构建 SOAP 和 REST 服务。SOAP 功能强大但复杂,除非需要高级特性(如分布式事务和复杂的消息拓扑),否则应避免使用。

  • ASP.NET MVC 于 2009 年发布,旨在清晰地将 Web 开发者的关注点分离为模型(临时存储数据)、视图(以各种格式在 UI 中呈现数据)和控制器(获取模型并将其传递给视图)。这种分离提高了代码复用性和单元测试能力。

  • ASP.NET Web API 于 2012 年发布,使开发者能够创建比 SOAP 服务更简单、更可扩展的 HTTP 服务(即 REST 服务)。

  • ASP.NET SignalR 于 2013 年发布,通过抽象底层技术(如 WebSockets 和长轮询)实现网站的实时通信。这使得网站能够提供如实时聊天或对时间敏感数据(如股票价格)的更新等功能,即使在不支持 WebSockets 等底层技术的多种浏览器上也能运行。

  • ASP.NET Core 于 2016 年发布,它将.NET Framework 的现代实现(如 MVC、Web API 和 SignalR)与新技术(如 Razor Pages、gRPC 和 Blazor)相结合,全部运行在现代.NET 上,因此支持跨平台执行。ASP.NET Core 提供了多种项目模板,帮助你快速上手其支持的技术。

最佳实践:选择 ASP.NET Core 开发网站和服务,因为它包含了现代且跨平台的 Web 相关技术。

ASP.NET Core 2.0 至 2.2 版本既可运行于.NET Framework 4.6.1 及以上版本(仅限 Windows),也可运行于.NET Core 2.0 及以上版本(跨平台)。ASP.NET Core 3.0 仅支持.NET Core 3.0。ASP.NET Core 6.0 仅支持.NET 6.0。

经典 ASP.NET 与现代 ASP.NET Core 的对比

迄今为止,ASP.NET 一直构建在.NET Framework 中的一个大型程序集System.Web.dll之上,并与微软的 Windows 专用 Web 服务器Internet Information ServicesIIS)紧密耦合。多年来,该程序集积累了许多功能,其中许多功能不适用于现代的跨平台开发。

ASP.NET Core 是对 ASP.NET 的重大重新设计。它去除了对System.Web.dll程序集和 IIS 的依赖,并由模块化轻量级包组成,就像现代.NET 的其他部分一样。虽然 ASP.NET Core 仍然支持使用 IIS 作为 Web 服务器,但还有更好的选择。

你可以在 Windows、macOS 和 Linux 上跨平台开发和运行 ASP.NET Core 应用程序。微软甚至创建了一个跨平台的、高性能的 Web 服务器,名为Kestrel,整个技术栈都是开源的。

ASP.NET Core 2.2 及以上版本的项目默认采用新的进程内托管模型。这使得在 Microsoft IIS 中托管时性能提升了 400%,但微软仍建议使用 Kestrel 以获得更佳性能。

创建一个空的 ASP.NET Core 项目

我们将创建一个 ASP.NET Core 项目,该项目将显示 Northwind 数据库中的供应商列表。

dotnet工具提供了许多项目模板,这些模板为你做了很多工作,但很难知道在特定情况下哪个最适合,因此我们将从空网站项目模板开始,然后逐步添加功能,以便你可以理解所有组件:

  1. 使用你偏好的代码编辑器添加一个新项目,如下表所定义:

    1. 项目模板:ASP.NET Core Empty / web

    2. 语言:C#

    3. 工作区/解决方案文件和文件夹:PracticalApps

    4. 项目文件和文件夹:Northwind.Web

    5. 对于 Visual Studio 2022,保持所有其他选项为其默认设置,例如,选中为 HTTPS 配置,未选中启用 Docker

  2. 在 Visual Studio Code 中,选择Northwind.Web作为活动的 OmniSharp 项目。

  3. 构建Northwind.Web项目。

  4. 打开Northwind.Web.csproj文件,并注意到该项目类似于类库,只是其 SDK 为Microsoft.NET.Sdk.Web,如下所示高亮显示:

    <Project Sdk="**Microsoft.NET.Sdk.Web**">
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
      </PropertyGroup>
    </Project> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  5. 如果你使用的是 Visual Studio 2022,在解决方案资源管理器中,切换显示所有文件

  6. 展开obj文件夹,展开Debug文件夹,展开net6.0文件夹,选择Northwind.Web.GlobalUsings.g.cs文件,并注意到隐式导入的命名空间包括所有控制台应用或类库的命名空间,以及一些 ASP.NET Core 的命名空间,如Microsoft.AspNetCore.Builder,如下所示:

    // <autogenerated />
    global using global::Microsoft.AspNetCore.Builder;
    global using global::Microsoft.AspNetCore.Hosting;
    global using global::Microsoft.AspNetCore.Http;
    global using global::Microsoft.AspNetCore.Routing;
    global using global::Microsoft.Extensions.Configuration;
    global using global::Microsoft.Extensions.DependencyInjection;
    global using global::Microsoft.Extensions.Hosting;
    global using global::Microsoft.Extensions.Logging;
    global using global::System;
    global using global::System.Collections.Generic;
    global using global::System.IO;
    global using global::System.Linq;
    global using global::System.Net.Http;
    global using global::System.Net.Http.Json;
    global using global::System.Threading;
    global using global::System.Threading.Tasks; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  7. 折叠obj文件夹。

  8. 打开Program.cs,并注意以下内容:

    • ASP.NET Core 项目类似于顶级控制台应用程序,其入口点是一个隐藏的Main方法,该方法通过名为args的参数传递。

    • 它调用WebApplication.CreateBuilder,该方法使用 Web 主机的默认设置创建一个网站主机,然后构建该主机。

    • 该网站将对所有 HTTP GET请求做出响应,返回纯文本:Hello World!

    • 调用Run方法是一个阻塞调用,因此隐藏的Main方法直到 Web 服务器停止运行才返回,如下所示:

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    app.MapGet("/", () => "Hello World!");
    app.Run(); 
    
    • 1
    • 2
    • 3
    • 4
  9. 在文件底部,添加一条语句,在调用 Run 方法后(即 Web 服务器停止后)向控制台写入一条消息,如下所示高亮显示:

    app.Run();
    **Console.WriteLine(****"This executes after the web server has stopped!"****);** 
    
    • 1
    • 2

测试和保护网站

现在我们将测试 ASP.NET Core 空网站项目的功能,并通过从 HTTP 切换到 HTTPS 来启用浏览器和 Web 服务器之间所有流量的加密,以保护隐私。HTTPS 是 HTTP 的安全加密版本。

  1. 对于 Visual Studio:

    1. 在工具栏中,确保选中Northwind.Web 而不是 IIS ExpressWSL,并将Web 浏览器(Microsoft Edge)切换到Google Chrome,如图 14.5 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

      图 14.5:在 Visual Studio 中选择带有 Kestrel Web 服务器的 Northwind.Web 配置文件

    2. 导航至调试 | 开始执行(不调试)…

    3. 首次启动安全网站时,系统会提示你的项目已配置为使用 SSL,为了避免浏览器警告,你可以选择信任 ASP.NET Core 生成的自签名证书。点击

    4. 当看到安全警告对话框时,再次点击

  2. 对于 Visual Studio Code,在终端中输入 dotnet run 命令。

  3. 在 Visual Studio 的命令提示窗口或 Visual Studio Code 的终端中,注意 Kestrel Web 服务器已开始在随机端口上监听 HTTP 和 HTTPS,你可以按 Ctrl + C 关闭 Kestrel Web 服务器,并且托管环境为 Development,如下所示:

    info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:5001 
    info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000 
    info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down. 
    info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development 
    info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Code\PracticalApps\Northwind.Web 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Visual Studio 还会自动启动你选择的浏览器。如果你使用的是 Visual Studio Code,则需要手动启动 Chrome。

  4. 保持 Web 服务器运行。

  5. 在 Chrome 中,打开开发者工具,然后点击网络标签。

  6. 输入地址 http://localhost:5000/,或分配给 HTTP 的任何端口号,并注意响应是来自跨平台 Kestrel Web 服务器的 Hello World! 纯文本,如图 14.6 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.6:来自 http://localhost:5000/ 的纯文本响应

    Chrome 也会自动请求一个 favicon.ico 文件以显示在浏览器标签页中,但该文件缺失,因此显示为 404 Not Found 错误。

  7. 输入地址 https://localhost:5001/,或分配给 HTTPS 的任何端口号,并注意如果你没有使用 Visual Studio 或在被提示信任 SSL 证书时点击了,则响应将是一个隐私错误,如图 14.7 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.7:显示 SSL 加密未使用证书启用的隐私错误

    如果你未配置浏览器可信任的证书来加密和解密 HTTPS 流量,则会看到此错误(如果你未看到此错误,则是因为你已经配置了证书)。

    在生产环境中,你可能会希望向 Verisign 等公司支付费用以获取 SSL 证书,因为他们提供责任保护和技术支持。

    Linux 开发者注意:如果你使用的 Linux 发行版无法创建自签名证书,或者你不介意每 90 天重新申请新证书,那么你可以通过以下链接获取免费证书:letsencrypt.org

    在开发过程中,你可以指示操作系统信任 ASP.NET Core 提供的临时开发证书。

  8. 在命令行或终端中,按 Ctrl + C 关闭 Web 服务器,并注意写入的消息,如下所示突出显示:

    info: Microsoft.Hosting.Lifetime[0]
          Application is shutting down...
    **This executes after the web server has stopped!**
    C:\Code\PracticalApps\Northwind.Web\bin\Debug\net6.0\Northwind.Web.exe (process 19888) exited with code 0. 
    
    • 1
    • 2
    • 3
    • 4
  9. 如果你需要信任本地自签名 SSL 证书,那么在命令行或终端中输入dotnet dev-certs https --trust命令,并注意消息提示,请求信任 HTTPS 开发证书。你可能需要输入密码,并且可能已经存在有效的 HTTPS 证书。

加强安全并自动重定向至安全连接

启用更严格的安全措施并自动将 HTTP 请求重定向到 HTTPS 是一种良好实践。

良好实践HTTP 严格传输安全HSTS)是一种应始终启用的可选安全增强措施。如果网站指定它且浏览器支持它,那么它将强制所有通信通过 HTTPS 进行,并阻止访问者使用不受信任或无效的证书。

现在让我们进行操作:

  1. Program.cs中,添加一个if语句,在非开发环境下启用 HSTS,如下代码所示:

    if (!app.Environment.IsDevelopment())
    {
      app.UseHsts();
    } 
    
    • 1
    • 2
    • 3
    • 4
  2. 在调用app.MapGet之前添加一条语句,将 HTTP 请求重定向到 HTTPS,如下代码所示:

    app.UseHttpsRedirection(); 
    
    • 1
  3. 启动Northwind.Web网站项目。

  4. 如果 Chrome 仍在运行,请关闭并重新启动它。

  5. 在 Chrome 中,显示开发者工具,并点击网络标签。

  6. 输入地址http://localhost:5000/,或分配给 HTTP 的任何端口号,并注意服务器如何响应307 临时重定向到端口5001,以及证书现在如何有效且受信任,如图14.8所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.8:现在连接已通过有效证书和 307 重定向得到安全保障

  7. 关闭 Chrome。

  8. 关闭 Web 服务器。

良好实践:记得在完成网站测试后关闭 Kestrel Web 服务器。

控制托管环境

在 ASP.NET Core 早期版本中,项目模板设定了一项规则,即在开发模式下,任何未处理的异常都会在浏览器窗口中显示,以便开发者查看异常详情,如下代码所示:

if (app.Environment.IsDevelopment())
{
  app.UseDeveloperExceptionPage();
} 
  • 1
  • 2
  • 3
  • 4

ASP.NET Core 6 及更高版本中,此代码默认会自动执行,因此不会包含在项目模板中。

ASP.NET Core 如何知道我们何时处于开发模式,使得IsDevelopment方法返回true?让我们来探究一下。

ASP.NET Core 可以从环境变量中读取以确定使用哪个托管环境,例如DOTNET_ENVIRONMENTASPNETCORE_ENVIRONMENT

您可以在本地开发期间覆盖这些设置:

  1. Northwind.Web文件夹中,展开名为Properties的文件夹,打开名为launchSettings.json的文件,并注意名为Northwind.Web的配置文件,该配置文件将托管环境设置为Development,如下所示:

    {
      "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
          "applicationUrl": "http://localhost:56111",
          "sslPort": 44329
        }
      },
      "profiles": {
    **"Northwind.Web"****: {**
    **"commandName"****:** **"Project"****,**
    **"dotnetRunMessages"****:** **"true"****,**
    **"launchBrowser"****:** **true****,**
    **"applicationUrl"****:** **"https://localhost:5001;http://localhost:5000"****,** 
    **"environmentVariables"****: {**
    **"ASPNETCORE_ENVIRONMENT"****:** **"Development"**
     **}**
     **},**
        "IIS Express": {
          "commandName": "IISExpress",
          "launchBrowser": true, 
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  2. 将随机分配的 HTTP 端口号更改为5000,HTTPS 端口号更改为5001

  3. 将环境更改为Production。可选地,将launchBrowser更改为false以防止 Visual Studio 自动启动浏览器。

  4. 启动网站并注意托管环境为Production,如下所示:

    info: Microsoft.Hosting.Lifetime[0] 
      Hosting environment: Production 
    
    • 1
    • 2
  5. 关闭 Web 服务器。

  6. launchSettings.json中,将环境更改回Development

launchSettings.json文件还具有使用随机端口号使用 IIS 作为 Web 服务器的配置。在本书中,我们将仅使用 Kestrel 作为 Web 服务器,因为它跨平台。

服务和管道配置的分离

将所有代码初始化一个简单的 Web 项目放在Program.cs中可能是一个好主意,特别是对于 Web 服务,因此我们将在第十六章构建和消费 Web 服务中再次看到这种风格。

然而,对于任何比最基本的 Web 项目更复杂的情况,您可能更愿意将配置分离到一个单独的Startup类中,该类包含两个方法:

  • ConfigureServices(IServiceCollection services):向依赖注入容器添加依赖服务,例如 Razor Pages 支持、跨源资源共享CORS)支持,或用于处理 Northwind 数据库的数据库上下文。

  • Configure(IApplicationBuilder app, IWebHostEnvironment env):通过调用app参数上的各种Use方法来设置 HTTP 管道,请求和响应通过该管道流动。按照应处理功能的顺序构建管道!

图 14.9:Startup 类 ConfigureServices 和 Configure 方法图

这两个方法将由运行时自动调用。

现在让我们创建一个Startup类:

  1. Northwind.Web项目添加一个名为Startup.cs的新类文件。

  2. 修改Startup.cs,如下所示:

    namespace Northwind.Web;
    public class Startup
    {
      public void ConfigureServices(IServiceCollection services)
      {
      }
      public void Configure(
        IApplicationBuilder app, IWebHostEnvironment env)
      {
        if (!env.IsDevelopment())
        {
          app.UseHsts();
        }
        app.UseRouting(); // start endpoint routing
        app.UseHttpsRedirection();
        app.UseEndpoints(endpoints =>
        {
          endpoints.MapGet("/", () => "Hello World!");
        });
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    请注意以下代码内容:

    • ConfigureServices方法目前为空,因为我们尚未需要添加任何依赖服务。

    • 通过Configure方法设置 HTTP 请求管道并启用端点路由功能。它配置了一个路由端点,该端点等待根路径/的每个 HTTP GET请求,并通过返回纯文本"Hello World!"来响应这些请求。我们将在本章末尾学习路由端点及其好处。

    现在我们必须指定在应用程序入口点使用Startup类。

  3. 修改Program.cs,如下所示:

    using Northwind.Web; // Startup
    Host.CreateDefaultBuilder(args)
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      }).Build().Run();
    Console.WriteLine("This executes after the web server has stopped!"); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  4. 启动网站并注意其行为与之前相同。

  5. 关闭 Web 服务器。

在本书中创建的所有其他网站和服务项目中,我们将使用.NET 6 项目模板创建的单个Program.cs文件。如果你喜欢使用Startup.cs的方式,那么你将在本章中看到如何使用它。

使网站能够提供静态内容

一个仅返回单一纯文本消息的网站并不十分有用!

至少,它应该返回静态 HTML 页面、网页将用于样式的 CSS 以及任何其他静态资源,如图像和视频。

按照惯例,这些文件应存储在名为wwwroot的目录中,以将它们与网站项目中动态执行的部分分开。

创建静态文件文件夹和网页

你现在将为静态网站资源创建一个文件夹,并创建一个使用 Bootstrap 进行样式设置的基本索引页面:

  1. Northwind.Web项目/文件夹中,创建一个名为wwwroot的文件夹。

  2. wwwroot文件夹添加一个新的 HTML 页面文件,命名为index.html

  3. 修改其内容,链接到 CDN 托管的 Bootstrap 以进行样式设置,并采用现代良好实践,例如设置视口,如下所示标记:

    <!doctype html>
    <html lang="en">
    <head>
      <!-- Required meta tags -->
      <meta charset="utf-8" />
      <meta name="viewport" content=
        "width=device-width, initial-scale=1 " />
      <!-- Bootstrap CSS -->
      <link href=
    "https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
      <title>Welcome ASP.NET Core!</title>
    </head>
    <body>
      <div class="container">
        <div class="jumbotron">
          <h1 class="display-3">Welcome to Northwind B2B</h1>
          <p class="lead">We supply products to our customers.</p>
          <hr />
          <h2>This is a static HTML page.</h2>
          <p>Our customers include restaurants, hotels, and cruise lines.</p>
          <p>
            <a class="btn btn-primary" 
              href="https://www.asp.net/">Learn more</a>
          </p>
        </div>
      </div>
    </body>
    </html> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

良好实践:为了获取最新的<link>元素用于 Bootstrap,请从以下链接的文档中复制并粘贴它:getbootstrap.com/docs/5.0/getting-started/introduction/#starter-template

启用静态和默认文件

如果你现在启动网站并在地址栏中输入http://localhost:5000/index.html,网站将返回一个404 Not Found错误,表示未找到网页。为了使网站能够返回诸如index.html之类的静态文件,我们必须明确配置该功能。

即使我们启用了静态文件,如果你启动网站并在地址栏中输入http://localhost:5000/,网站将返回一个404 Not Found错误,因为 Web 服务器不知道如果没有请求特定文件,默认应该返回什么。

你现在将启用静态文件,明确配置默认文件,并更改注册的 URL 路径,该路径返回纯文本Hello World!响应:

  1. Startup.cs中,在Configure方法中,在启用 HTTPS 重定向后添加语句以启用静态文件和默认文件,并修改将GET请求映射到返回Hello World!纯文本响应的语句,使其仅响应 URL 路径/hello,如下所示突出显示的代码:

    app.UseHttpsRedirection();
    **app.UseDefaultFiles();** **// index.html, default.html, and so on**
    **app.UseStaticFiles();**
    app.UseEndpoints(endpoints =>
    {
      endpoints.MapGet(**"/hello"**, () => "Hello World!");
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用UseDefaultFiles必须在调用UseStaticFiles之前,否则它不会工作!你将在本章末了解更多关于中间件和端点路由的顺序。

  2. 启动网站。

  3. 启动Chrome并显示开发者工具

  4. 在 Chrome 中,输入http://localhost:5000/,并注意您被重定向到端口5001上的 HTTPS 地址,并且index.html文件现在通过该安全连接返回,因为它是该网站可能的默认文件之一。

  5. 开发者工具中,注意对 Bootstrap 样式表的请求。

  6. 在 Chrome 中,输入http://localhost:5000/hello,并注意它返回与之前一样的纯文本Hello World!

  7. 关闭 Chrome 并关闭 Web 服务器。

如果所有网页都是静态的,即它们仅由网页编辑器手动更改,那么我们的网站编程工作就完成了。但几乎所有网站都需要动态内容,这意味着在运行时通过执行代码生成的网页。

最简单的方法是使用 ASP.NET Core 的一个名为Razor 页面的功能。

探索 ASP.NET Core Razor Pages

ASP.NET Core Razor Pages 允许开发人员轻松地将 C#代码语句与 HTML 标记混合,以使生成的网页动态化。这就是为什么它们使用.cshtml文件扩展名。

按照惯例,ASP.NET Core 会在名为Pages的文件夹中查找 Razor 页面。

启用 Razor 页面

现在,您将把静态 HTML 页面转换为动态 Razor 页面,并添加并启用 Razor 页面服务:

  1. Northwind.Web项目文件夹中,创建一个名为Pages的文件夹。

  2. index.html文件复制到Pages文件夹中。

  3. 对于Pages文件夹中的文件,将文件扩展名从.html更改为.cshtml

  4. 删除表示这是静态 HTML 页面的<h2>元素。

  5. Startup.cs中的ConfigureServices方法中,添加一个语句以将 ASP.NET Core Razor Pages 及其相关服务(如模型绑定、授权、防伪、视图和标签助手)添加到构建器中,如下所示:

    services.AddRazorPages(); 
    
    • 1
  6. Startup.cs中的Configure方法中,在配置使用端点的部分,添加一个语句以调用MapRazorPages方法,如下所示:

    app.UseEndpoints(endpoints =>
    {
     **endpoints.MapRazorPages();**
      endpoints.MapGet("/hello",  () => "Hello World!");
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

向 Razor 页面添加代码

在网页的 HTML 标记中,Razor 语法由@符号表示。Razor 页面可以描述如下:

  • 它们需要在文件顶部使用@page指令。

  • 它们可以有可选的@functions部分,定义以下任何内容:

    • 用于存储数据值的属性,类似于类定义。该类的实例会自动实例化名为Model,其属性可以在特殊方法中设置,并且可以在 HTML 中获取属性值。

    • 当进行 HTTP 请求(如GETPOSTDELETE)时,执行名为OnGetOnPostOnDelete等方法。

现在让我们将静态 HTML 页面转换为 Razor 页面:

  1. Pages文件夹中,打开index.cshtml

  2. 在文件顶部添加@page语句。

  3. @page语句之后,添加一个@functions语句块。

  4. 定义一个属性以存储当前日期的名称作为string值。

  5. 定义一个设置DayName的方法,该方法在对该页面进行 HTTP GET请求时执行,如下面的代码所示:

    @page
    @functions
    {
      public string? DayName { get; set; }
      public void OnGet()
      {
        Model.DayName = DateTime.Now.ToString("dddd");
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  6. 在第二个 HTML 段落中输出日期名称,如下面的标记中突出显示的那样:

    <p>**It's @Model.DayName!** Our customers include restaurants, hotels, and cruise lines.</p> 
    
    • 1
  7. 启动网站。

  8. 在 Chrome 中输入https://localhost:5001/,并注意当前日期名称输出在页面上,如图 14.10所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.10:欢迎来到 Northwind 页面显示当前日期

  9. 在 Chrome 中输入https://localhost:5001/index.html,它与静态文件名完全匹配,并注意它像以前一样返回静态 HTML 页面。

  10. 在 Chrome 中输入https://localhost:5001/hello,它与返回纯文本的端点路由完全匹配,并注意它像以前一样返回Hello World!

  11. 关闭 Chrome 并停止 Web 服务器。

使用 Razor Pages 的共享布局

大多数网站都有多个页面。如果每个页面都必须包含当前index.cshtml中的所有样板标记,那将变得难以管理。因此,ASP.NET Core 提供了一个名为布局的功能。

要使用布局,我们必须创建一个 Razor 文件来定义所有 Razor Pages(和所有 MVC 视图)的默认布局,并将其存储在Shared文件夹中,以便通过约定轻松找到。此文件的名称可以是任何名称,因为我们将会指定它,但_Layout.cshtml是良好的实践。

我们还必须创建一个特殊命名的文件来设置所有 Razor Pages(和所有 MVC 视图)的默认布局文件。此文件必须命名为_ViewStart.cshtml

让我们看看布局的实际应用:

  1. Pages文件夹中,添加一个名为_ViewStart.cshtml的文件。(Visual Studio 项模板名为Razor 视图开始。)

  2. 修改其内容,如下面的标记所示:

    @{
      Layout = "_Layout";
    } 
    
    • 1
    • 2
    • 3
  3. Pages文件夹中,创建一个名为Shared的文件夹。

  4. Shared文件夹中,创建一个名为_Layout.cshtml的文件。(Visual Studio 项模板名为Razor 布局。)

  5. 修改_Layout.cshtml的内容(它与index.cshtml类似,因此你可以从那里复制粘贴 HTML 标记),如下面的标记所示:

    <!doctype html>
    <html lang="en">
    <head>
      <!-- Required meta tags -->
      <meta charset="utf-8" />
      <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no" />
      <!-- Bootstrap CSS -->
      <link href=
    "https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
      <title>@ViewData["Title"]</title>
    </head>
    <body>
      <div class="container">
        @RenderBody()
        <hr />
        <footer>
          <p>Copyright &copy; 2021 - @ViewData["Title"]</p>
        </footer>
      </div>
      <!-- JavaScript to enable features like carousel -->
      <script src="img/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
      @RenderSection("Scripts", required: false)
    </body>
    </html> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在审查前面的标记时,请注意以下几点:

    • <title>通过从名为ViewData的字典中使用服务器端代码动态设置。这是一种在 ASP.NET Core 网站的不同部分之间传递数据的简单方法。在这种情况下,数据将在 Razor 页面类文件中设置,然后在共享布局中输出。

    • @RenderBody()标记了请求视图的插入点。

    • 每个页面底部都会出现一条水平线和页脚。

    • 布局底部有一个脚本,用于实现 Bootstrap 的一些酷炫功能,我们稍后可以使用,例如图像轮播。

    • 在 Bootstrap 的<script>元素之后,我们定义了一个名为Scripts的部分,以便 Razor 页面可以有选择地注入它所需的额外脚本。

  6. 修改 index.cshtml 以删除所有 HTML 标记,除了 <div class="jumbotron"> 及其内容,并保留你之前添加的 @functions 块中的 C# 代码。

  7. OnGet 方法添加一条语句,将页面标题存储在 ViewData 字典中,并修改按钮以导航到供应商页面(我们将在下一节创建),如下所示的高亮标记:

    @page 
    @functions
    {
      public string? DayName { get; set; }
      public void OnGet()
      {
     **ViewData[****"Title"****] =** **"Northwind B2B"****;**
        Model.DayName = DateTime.Now.ToString("dddd");
      }
    }
    <div class="jumbotron">
      <h1 class="display-3">Welcome to Northwind B2B</h1>
      <p class="lead">We supply products to our customers.</p>
      <hr />
      <p>It's @Model.DayName! Our customers include restaurants, hotels, and cruise lines.</p>
      <p>
    **<****a****class****=****"btn btn-primary"****href****=****"suppliers"****>**
     **Learn more about our suppliers****</****a****>**
      </p>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  8. 启动网站,在 Chrome 中访问它,并注意到它与之前有相似的行为,尽管点击供应商按钮会给出 404 未找到 错误,因为我们尚未创建该页面。

使用 Razor Pages 的代码后置文件

有时,将 HTML 标记与数据和可执行代码分离会更好,因此 Razor Pages 允许你通过将 C# 代码放入 代码后置 类文件中来实现这一点。它们与 .cshtml 文件同名,但以 .cshtml.cs 结尾。

你现在将创建一个显示供应商列表的页面。在本例中,我们专注于学习代码后置文件。在下一个主题中,我们将从数据库加载供应商列表,但现在,我们将使用硬编码的字符串数组来模拟这一过程:

  1. Pages 文件夹中,添加两个名为 Suppliers.cshtmlSuppliers.cshtml.cs 的新文件。(Visual Studio 项模板名为 Razor 页面 - 空,它会创建这两个文件。)

  2. 向名为 Suppliers.cshtml.cs 的代码后置文件添加如下所示的语句:

    using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
    namespace Northwind.Web.Pages;
    public class SuppliersModel : PageModel
    {
      public IEnumerable<string>? Suppliers { get; set; }
      public void OnGet()
      {
        ViewData["Title"] = "Northwind B2B - Suppliers";
        Suppliers = new[]
        {
          "Alpha Co", "Beta Limited", "Gamma Corp"
        };
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在审查前面的标记时,请注意以下几点:

    • SuppliersModel 继承自 PageModel,因此它拥有诸如 ViewData 字典等成员,用于共享数据。你可以右键点击 PageModel 并选择 转到定义 来查看它还有许多其他有用的特性,例如当前请求的整个 HttpContext

    • SuppliersModel 定义了一个用于存储名为 Suppliersstring 值集合的属性。

    • 当对该 Razor 页面发起 HTTP GET 请求时,Suppliers 属性会用来自字符串数组的一些示例供应商名称进行填充。稍后,我们将从 Northwind 数据库填充此属性。

  3. 修改 Suppliers.cshtml 的内容,如下所示的标记:

    @page
    @model Northwind.Web.Pages.SuppliersModel
    <div class="row">
      <h1 class="display-2">Suppliers</h1>
      <table class="table">
        <thead class="thead-inverse">
          <tr><th>Company Name</th></tr>
        </thead>
        <tbody>
        @if (Model.Suppliers is not null)
        {
          @foreach(string name in Model.Suppliers)
          {
            <tr><td>@name</td></tr>
          }
        }
        </tbody>
      </table>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在审查前面的标记时,请注意以下几点:

    • 此 Razor 页面的模型类型设置为 SuppliersModel

    • 该页面输出一个带有 Bootstrap 样式的 HTML 表格。

    • 如果 ModelSuppliers 属性不为 null,则表格中的数据行是通过循环遍历该属性生成的。

  4. 启动网站并在 Chrome 中访问它。

  5. 点击按钮以了解更多关于供应商的信息,并注意供应商表格,如图 14.11 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.11:从字符串数组加载的供应商表格

使用 ASP.NET Core 的 Entity Framework Core

Entity Framework Core 是一种自然而然的方式,将真实数据引入网站。在第十三章C# 和 .NET 的实际应用介绍中,你创建了两对类库:一对用于实体模型,另一对用于 Northwind 数据库上下文,适用于 SQL Server 或 SQLite,或两者兼有。现在你将在网站项目中使用它们。

将 Entity Framework Core 配置为服务

诸如 Entity Framework Core 数据库上下文之类的功能,对于 ASP.NET Core 是必需的,必须在网站启动时注册为服务。GitHub 仓库解决方案和下面的代码使用 SQLite,但如果你更喜欢,也可以轻松使用 SQL Server。

让我们看看如何操作:

  1. Northwind.Web 项目中,添加一个项目引用到 Northwind.Common.DataContext 项目,适用于 SQLite 或 SQL Server,如下列标记所示:

    <!-- change Sqlite to SqlServer if you prefer -->
    <ItemGroup>
      <ProjectReference Include="..\Northwind.Common.DataContext.Sqlite\
    Northwind.Common.DataContext.Sqlite.csproj" />
    </ItemGroup> 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    项目引用必须全部在一行上,不能有换行。

  2. 构建 Northwind.Web 项目。

  3. Startup.cs 中,导入命名空间以处理你的实体模型类型,如下列代码所示:

    using Packt.Shared; // AddNorthwindContext extension method 
    
    • 1
  4. ConfigureServices 方法中添加一条语句,以注册 Northwind 数据库上下文类,如下列代码所示:

    services.AddNorthwindContext(); 
    
    • 1
  5. Northwind.Web 项目中,在 Pages 文件夹下,打开 Suppliers.cshtml.cs,并导入我们的数据库上下文命名空间,如下列代码所示:

    using Packt.Shared; // NorthwindContext 
    
    • 1
  6. SuppliersModel 类中,添加一个私有字段来存储 Northwind 数据库上下文,以及一个构造函数来设置它,如下列代码所示:

    private NorthwindContext db;
    public SuppliersModel(NorthwindContext injectedContext)
    {
      db = injectedContext;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
  7. Suppliers 属性更改为包含 Supplier 对象而不是 string 值。

  8. OnGet 方法中,修改语句以根据数据库上下文的 Suppliers 属性设置 Suppliers 属性,按国家和公司名称排序,如下列突出显示的代码所示:

    public void OnGet()
    {
      ViewData["Title"] = "Northwind B2B - Suppliers";
      Suppliers = **db.Suppliers**
     **.OrderBy(c => c.Country).ThenBy(c => c.CompanyName)**;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  9. 修改 Suppliers.cshtml 的内容,以导入 Packt.Shared 命名空间,并为每个供应商渲染多个列,如下列突出显示的标记所示:

    @page
    **@using Packt.Shared**
    @model Northwind.Web.Pages.SuppliersModel
    <div class="row">
      <h1 class="display-2">Suppliers</h1>
      <table class="table">
        <thead class="thead-inverse">
          <tr>
            <th>Company Name</th>
    **<****th****>****Country****</****th****>**
    **<****th****>****Phone****</****th****>**
          </tr>
        </thead>
        <tbody>
        @if (Model.Suppliers is not null)
        {
     **@foreach(Supplier s in Model.Suppliers)**
          {
            <tr>
    **<****td****>****@s.CompanyName****</****td****>**
    **<****td****>****@s.Country****</****td****>**
    **<****td****>****@s.Phone****</****td****>**
            </tr>
          }
        }
        </tbody>
      </table>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  10. 启动网站。

  11. 在 Chrome 中,输入 https://localhost:5001/

  12. 点击 了解更多关于我们的供应商,并注意供应商表现在从数据库加载,如图 14.12 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 14.12:从 Northwind 数据库加载的供应商表

使用 Razor Pages 操作数据

现在你将添加功能以插入新供应商。

启用模型以插入实体

首先,你将修改供应商模型,使其在访问者提交表单以插入新供应商时响应 HTTP POST 请求:

  1. Northwind.Web 项目中,在 Pages 文件夹下,打开 Suppliers.cshtml.cs 并导入以下命名空间:

    using Microsoft.AspNetCore.Mvc; // [BindProperty], IActionResult 
    
    • 1
  2. SuppliersModel 类中,添加一个属性来存储单个供应商,以及一个名为 OnPost 的方法,如果模型有效,该方法会将供应商添加到 Northwind 数据库的 Suppliers 表中,如下列代码所示:

    [BindProperty]
    public Supplier? Supplier { get; set; }
    public IActionResult OnPost()
    {
      if ((Supplier is not null) && ModelState.IsValid)
      {
        db.Suppliers.Add(Supplier);
        db.SaveChanges();
        return RedirectToPage("/suppliers");
      }
      else
      {
        return Page(); // return to original page
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

在回顾前面的代码时,请注意以下几点:

  • 我们添加了一个名为Supplier的属性,该属性装饰有[BindProperty]特性,以便我们可以轻松地将网页上的 HTML 元素连接到Supplier类中的属性。

  • 我们添加了一个响应 HTTP POST请求的方法。它检查所有属性值是否符合Supplier类实体模型上的验证规则(如[Required][StringLength]),然后将供应商添加到现有表中,并保存对数据库上下文的更改。这将生成一个 SQL 语句以执行数据库中的插入操作。然后它重定向到Suppliers页面,以便访客看到新添加的供应商。

定义一个用于插入新供应商的表单

接下来,你将修改 Razor 页面以定义一个表单,访客可以填写并提交以插入新供应商:

  1. Suppliers.cshtml中,在@model声明后添加标签助手,以便我们可以在该 Razor 页面上使用诸如asp-for之类的标签助手,如下面的标记所示:

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
    
    • 1
  2. 在文件底部,添加一个用于插入新供应商的表单,并使用asp-for标签助手将Supplier类的CompanyNameCountryPhone属性绑定到输入框,如下面的标记所示:

    <div class="row">
      <p>Enter details for a new supplier:</p>
      <form method="POST">
        <div><input asp-for="Supplier.CompanyName" 
                    placeholder="Company Name" /></div>
        <div><input asp-for="Supplier.Country" 
                    placeholder="Country" /></div>
        <div><input asp-for="Supplier.Phone" 
                    placeholder="Phone" /></div>
        <input type="submit" />
      </form>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在审查前面的标记时,请注意以下几点:

    • 带有POST方法的<form>元素是常规 HTML,因此其中的<input type="submit" />元素将使用该表单内其他元素的值向当前页面发出 HTTP POST请求。

    • 带有名为asp-for的标签助手的<input>元素能够将数据绑定到 Razor 页面背后的模型。

  3. 启动网站。

  4. 点击了解更多关于我们的供应商,滚动到页面底部,输入Bob's BurgersUSA(603) 555-4567,然后点击提交

  5. 注意,你会看到一个更新的供应商表,其中新增了供应商。

  6. 关闭 Chrome 并关闭 Web 服务器。

向 Razor 页面注入依赖服务

如果你有一个没有代码后置文件的.cshtml Razor 页面,那么你可以使用@inject指令而不是构造函数参数注入来注入依赖服务,然后直接在标记中间使用 Razor 语法引用注入的数据库上下文。

让我们创建一个简单的示例:

  1. Pages文件夹中,添加一个名为Orders.cshtml的新文件。(Visual Studio 的项目模板名为Razor Page - Empty,它会创建两个文件。删除.cs文件。)

  2. Orders.cshtml中,编写代码以输出 Northwind 数据库中的订单数量,如下面的标记所示:

    @page
    @using Packt.Shared
    @inject NorthwindContext db
    @{
      string title = "Orders";
      ViewData["Title"] = $"Northwind B2B - {title}";
    }
    <div class="row">
      <h1 class="display-2">@title</h1>
      <p>
        There are @db.Orders.Count() orders in the Northwind database.
      </p>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  3. 启动网站。

  4. 导航到/orders并注意你看到 Northwind 数据库中有 830 个订单。

  5. 关闭 Chrome 并关闭 Web 服务器。

使用 Razor 类库

与 Razor 页面相关的所有内容都可以编译成类库,以便在多个项目中更容易重用。使用 ASP.NET Core 3.0 及更高版本,这可以包括静态文件,如 HTML、CSS、JavaScript 库和媒体资产,如图像文件。网站可以使用类库中定义的 Razor 页面的视图,也可以覆盖它。

创建 Razor 类库

让我们创建一个新的 Razor 类库:

使用你喜欢的代码编辑器添加新项目,如下表所示:

  1. 项目模板:Razor 类库 / razorclasslib

  2. 复选框/开关:支持页面和视图 / -s

  3. 工作区/解决方案文件和文件夹:PracticalApps

  4. 项目文件和文件夹:Northwind.Razor.Employees

-s--support-pages-and-views开关的简写,该开关使类库能够使用 Razor 页面和.cshtml文件视图。

为 Visual Studio Code 禁用紧凑文件夹

在我们实现 Razor 类库之前,我想解释一下 Visual Studio Code 的一个功能,该功能在出版后添加,曾让一些读者感到困惑。

紧凑文件夹功能意味着,如果层次结构中的中间文件夹不包含文件,则类似/Areas/MyFeature/Pages/的嵌套文件夹会以紧凑形式显示,如图14.13所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 14.13:启用或禁用紧凑文件夹

如果你想禁用 Visual Studio Code 的紧凑文件夹功能,请完成以下步骤:

  1. 在 Windows 上,导航至文件 | 首选项 | 设置。在 macOS 上,导航至代码 | 首选项 | 设置

  2. 搜索设置框中,输入compact

  3. 清除资源管理器:紧凑文件夹复选框,如图14.14所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.14:为 Visual Studio Code 禁用紧凑文件夹

  4. 关闭设置标签页。

使用 EF Core 实现员工功能

现在我们可以添加对实体模型的引用,以便在 Razor 类库中显示员工信息:

  1. Northwind.Razor.Employees项目中,添加对Northwind.Common.DataContext项目的项目引用,选择 SQLite 或 SQL Server,并注意 SDK 为Microsoft.NET.Sdk.Razor,如下所示高亮显示:

    <Project Sdk="**Microsoft.NET.Sdk.Razor**">
      <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
      </PropertyGroup>
      <ItemGroup>
        <FrameworkReference Include="Microsoft.AspNetCore.App" />
      </ItemGroup>
     **<!-- change Sqlite to SqlServer** **if** **you prefer -->**
     **<ItemGroup>**
     **<ProjectReference Include=****"..\Northwind.Common.DataContext.Sqlite**
    **\Northwind.Common.DataContext.Sqlite.csproj"** **/>**
     **</ItemGroup>**
    </Project> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    项目引用必须全部在一行上,不能有换行。此外,不要混合使用我们的 SQLite 和 SQL Server 项目,否则你会看到编译器错误。如果你在Northwind.Web项目中使用了 SQL Server,那么在Northwind.Razor.Employees项目中也必须使用 SQL Server。

  2. 构建Northwind.Razor.Employees项目。

  3. Areas文件夹中,右键点击MyFeature文件夹,选择重命名,输入新名称PacktFeatures,然后按 Enter 键。

  4. PacktFeatures文件夹中,在Pages子文件夹中,添加一个名为_ViewStart.cshtml的新文件。(Visual Studio 项模板名为Razor 视图开始。或者直接从Northwind.Web项目复制。)

  5. 修改其内容,通知此类库任何 Razor 页面应查找与Northwind.Web项目中使用的名称相同的布局,如下所示:

    @{
      Layout = "_Layout";
    } 
    
    • 1
    • 2
    • 3

    我们无需在此项目中创建_Layout.cshtml文件。它将使用其宿主项目中的那个,例如Northwind.Web项目中的那个。

  6. Pages子文件夹中,将Page1.cshtml重命名为Employees.cshtml,并将Page1.cshtml.cs重命名为Employees.cshtml.cs

  7. 修改Employees.cshtml.cs以定义一个页面模型,该模型包含从 Northwind 数据库加载的Employee实体实例数组,如下所示:

    using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
    using Packt.Shared; // Employee, NorthwindContext
    namespace PacktFeatures.Pages;
    public class EmployeesPageModel : PageModel
    {
      private NorthwindContext db;
      public EmployeesPageModel(NorthwindContext injectedContext)
      {
        db = injectedContext;
      }
      public Employee[] Employees { get; set; } = null!;
      public void OnGet()
      {
        ViewData["Title"] = "Northwind B2B - Employees";
        Employees = db.Employees.OrderBy(e => e.LastName)
          .ThenBy(e => e.FirstName).ToArray();
      }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  8. 修改Employees.cshtml,如下所示:

    @page
    @using Packt.Shared
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
    @model PacktFeatures.Pages.EmployeesPageModel
    <div class="row">
      <h1 class="display-2">Employees</h1>
    </div>
    <div class="row">
    @foreach(Employee employee in Model.Employees)
    {
      <div class="col-sm-3">
        <partial name="_Employee" model="employee" />
      </div>
    }
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

在审查前面的标记时,请注意以下几点:

  • 我们导入Packt.Shared命名空间,以便可以使用其中的类,例如Employee

  • 我们添加对标签助手的支持,以便可以使用<partial>元素。

  • 我们声明此 Razor 页面的@model类型,以使用你刚定义的页面模型类。

  • 我们遍历模型中的Employees,使用部分视图输出每个员工。

实现一个部分视图以显示单个员工

<partial>标签助手是在 ASP.NET Core 2.1 中引入的。部分视图类似于 Razor 页面的一个片段。接下来几步中,你将创建一个以渲染单个员工:

  1. Northwind.Razor.Employees项目中,在Pages文件夹中创建一个Shared文件夹。

  2. Shared文件夹中,创建一个名为_Employee.cshtml的文件。(Visual Studio 项模板名为Razor 视图 - 空。)

  3. 修改_Employee.cshtml,如下所示:

    @model Packt.Shared.Employee
    <div class="card border-dark mb-3" style="max-width: 18rem;">
      <div class="card-header">@Model?.LastName, @Model?.FirstName</div>
      <div class="card-body text-dark">
        <h5 class="card-title">@Model?.Country</h5>
        <p class="card-text">@Model?.Notes</p>
      </div>
    </div> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

在审查前面的标记时,请注意以下几点:

  • 按照惯例,部分视图的名称以一个下划线开头。

  • 如果你将部分视图放在Shared文件夹中,那么它可以被自动找到。

  • 此部分视图的模型类型是单个Employee实体。

  • 我们使用 Bootstrap 卡片样式输出每个员工的信息。

使用和测试 Razor 类库

你现在将在网站项目中引用并使用 Razor 类库:

  1. Northwind.Web项目中,添加对Northwind.Razor.Employees项目的一个项目引用,如下所示:

    <ProjectReference Include=
      "..\Northwind.Razor.Employees\Northwind.Razor.Employees.csproj" /> 
    
    • 1
    • 2
  2. 修改Pages\index.cshtml,在供应商页面链接后添加一个段落,其中包含指向 Packt 功能员工页面的链接,如下所示:

    <p>
      <a class="btn btn-primary" href="packtfeatures/employees">
        Contact our employees
      </a>
    </p> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
  3. 启动网站,使用 Chrome 访问网站,并点击联系我们的员工按钮以查看员工卡片,如图 14.15 所示:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 14.15:来自 Razor 类库功能的员工列表

配置服务和 HTTP 请求管道

既然我们已经构建了一个网站,我们可以回到 Startup 配置,并更详细地审查服务和 HTTP 请求管道是如何工作的。

理解 Endpoint routing

在早期版本的 ASP.NET Core 中,路由系统和可扩展的中间件系统并不总是能轻松协同工作;例如,如果你想在中间件和 MVC 中实现 CORS 等策略。微软投资改进路由,引入了名为 Endpoint routing 的系统,该系统随 ASP.NET Core 2.2 一起推出。

最佳实践:Endpoint routing 取代了 ASP.NET Core 2.1 及更早版本中使用的基于 IRouter 的路由。微软建议尽可能将所有旧的 ASP.NET Core 项目迁移到 Endpoint routing。

Endpoint routing 旨在实现需要路由的框架(如 Razor Pages、MVC 或 Web API)与需要了解路由如何影响它们的中间件(如本地化、授权、CORS 等)之间的更好互操作性。

Endpoint routing 之所以得名,是因为它将路由表表示为可由路由系统高效遍历的编译端点树。其中最大的改进之一是路由和动作方法选择的性能。

如果兼容性设置为 2.2 或更高版本,则默认情况下,ASP.NET Core 2.2 或更高版本会启用 Endpoint routing。使用 MapRoute 方法或属性注册的传统路由会映射到新系统。

新的路由系统包括一个链接生成服务,该服务作为依赖服务注册,不需要 HttpContext

配置 Endpoint routing

Endpoint routing 需要一对调用 UseRoutingUseEndpoints 方法:

  • UseRouting 标记了做出路由决策的管道位置。

  • UseEndpoints 标记了选定端点执行的管道位置。

在这些方法之间运行的中间件(如本地化)可以看到选定的端点,并在必要时切换到不同的端点。

Endpoint routing 使用自 2010 年以来在 ASP.NET MVC 中使用的相同路由模板语法,以及 2013 年随 ASP.NET MVC 5 引入的 [Route] 属性。迁移通常只需要更改 Startup 配置。

MVC 控制器、Razor Pages 和 SignalR 等框架过去通过调用 UseMvc 或类似方法启用,但现在它们都在 UseEndpoints 方法调用内部添加,因为它们都集成到了同一个路由系统中,与中间件一起。

在我们的项目中审查 Endpoint routing 配置

查看 Startup.cs 类文件,如下所示:

using Packt.Shared; // AddNorthwindContext extension method
namespace Northwind.Web;
public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddRazorPages();
    services.AddNorthwindContext();
  }
  public void Configure(
    IApplicationBuilder app, IWebHostEnvironment env)
  {
    if (!env.IsDevelopment())
    {
      app.UseHsts();
    }
    app.UseRouting();
    app.UseHttpsRedirection();
    app.UseDefaultFiles(); // index.html, default.html, and so on
    app.UseStaticFiles();
    app.UseEndpoints(endpoints =>
    {
      endpoints.MapRazorPages();
      endpoints.MapGet("/hello", () => "Hello World!");
    });
  }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

Startup 类有两个方法,主机自动调用这两个方法来配置网站。

ConfigureServices 方法注册了可以在需要它们提供的功能时使用依赖注入检索的服务。我们的代码注册了两个服务:Razor Pages 和 EF Core 数据库上下文。

在 ConfigureServices 方法中注册服务

注册依赖服务的常用方法,包括结合其他注册服务方法调用的服务,如下表所示:

方法注册的服务
AddMvcCore路由请求和调用控制器所需的最小服务集。大多数网站需要比这更多的配置。
AddAuthorization认证和授权服务。
AddDataAnnotationsMVC 数据注解服务。
AddCacheTagHelperMVC 缓存标签助手服务。
AddRazorPages包含 Razor 视图引擎的 Razor Pages 服务。常用于简单网站项目。它调用以下附加方法:AddMvcCore``AddAuthorization``AddDataAnnotations``AddCacheTagHelper
AddApiExplorerWeb API 探索服务。
AddCors增强安全性的 CORS 支持。
AddFormatterMappingsURL 格式与其对应的媒体类型之间的映射。
AddControllers控制器服务,但不包括视图或页面服务。常用于 ASP.NET Core Web API 项目。它调用以下附加方法:AddMvcCore``AddAuthorization``AddDataAnnotations``AddCacheTagHelper``AddApiExplorer``AddCors``AddFormatterMappings
AddViews支持 .cshtml 视图,包括默认约定。
AddRazorViewEngine支持 Razor 视图引擎,包括处理 @ 符号。
AddControllersWithViews控制器、视图和页面服务。常用于 ASP.NET Core MVC 网站项目。它调用以下附加方法:AddMvcCore``AddAuthorization``AddDataAnnotations``AddCacheTagHelper``AddApiExplorer``AddCors``AddFormatterMappings``AddViews``AddRazorViewEngine
AddMvc类似于 AddControllersWithViews,但仅应为向后兼容而使用。
AddDbContext<T>您的 DbContext 类型及其可选的 DbContextOptions<TContext>
AddNorthwindContext我们创建的一个自定义扩展方法,以便更容易地根据项目引用注册 NorthwindContext 类,无论是 SQLite 还是 SQL Server。

在接下来的几章中,当与 MVC 和 Web API 服务一起工作时,您将看到更多使用这些扩展方法注册服务的示例。

在 Configure 方法中设置 HTTP 请求管道

Configure 方法配置 HTTP 请求管道,该管道由一系列连接的委托组成,这些委托可以执行处理,然后决定是自行返回响应,还是将处理传递给管道中的下一个委托。返回的响应也可以被操纵。

请记住,委托定义了一个方法签名,委托实现可以插入其中。HTTP 请求管道的委托很简单,如下所示:

public delegate Task RequestDelegate(HttpContext context); 
  • 1

您可以看到输入参数是一个HttpContext。这提供了访问处理传入 HTTP 请求所需的一切,包括 URL 路径、查询字符串参数、Cookie 和用户代理。

这些委托通常被称为中间件,因为它们位于浏览器客户端和网站或服务之间。

中间件委托通过以下方法之一或调用它们本身的自定义方法进行配置:

  • Run:添加一个中间件委托,该委托通过立即返回响应而不是调用下一个中间件委托来终止管道。

  • Map:添加一个中间件委托,当存在匹配的请求时,通常基于 URL 路径(如/hello)在管道中创建分支。

  • Use:添加一个中间件委托,该委托构成管道的一部分,因此它可以决定是否要将请求传递给管道中的下一个委托,并且可以在下一个委托之前和之后修改请求和响应。

为了方便,有许多扩展方法使得构建管道更加容易,例如UseMiddleware<T>,其中T是一个具有以下特性的类:

  1. 一个带有RequestDelegate参数的构造函数,该参数将传递给下一个管道组件

  2. 一个带有HttpContext参数并返回TaskInvoke方法

总结关键中间件扩展方法

我们的代码中使用的关键中间件扩展方法包括以下内容:

  • UseDeveloperExceptionPage:捕获管道中的同步和异步System.Exception实例,并生成 HTML 错误响应。

  • UseHsts:添加使用 HSTS 的中间件,该中间件添加了Strict-Transport-Security头。

  • UseRouting:添加中间件,该中间件定义管道中的一个点,在此点进行路由决策,并且必须与调用UseEndpoints结合使用,在此处执行处理。这意味着对于我们的代码,任何匹配//index/suppliers的 URL 路径都将映射到 Razor 页面,而匹配/hello的 URL 路径将映射到匿名委托。任何其他 URL 路径都将传递给下一个委托进行匹配,例如静态文件。这就是为什么,尽管看起来 Razor 页面和/hello的映射发生在静态文件之后,但实际上它们具有优先权,因为调用UseRouting发生在UseStaticFiles之前。

  • UseHttpsRedirection:添加中间件,用于将 HTTP 请求重定向到 HTTPS,因此在我们的代码中,对http://localhost:5000的请求将被修改为https://localhost:5001

  • UseDefaultFiles:添加中间件,该中间件在当前路径上启用默认文件映射,因此在我们的代码中,它会识别诸如index.html之类的文件。

  • UseStaticFiles:添加中间件,该中间件在wwwroot中查找静态文件以返回 HTTP 响应。

  • UseEndpoints:添加中间件以执行,根据管道中早先做出的决策生成响应。如以下子列表所示,添加了两个端点:

    • MapRazorPages:添加中间件,将 URL 路径(如/suppliers)映射到/Pages文件夹中的 Razor Page 文件suppliers.cshtml,并将结果作为 HTTP 响应返回。

    • MapGet:添加中间件,将 URL 路径(如/hello)映射到内联委托,该委托直接将纯文本写入 HTTP 响应。

可视化 HTTP 管道

HTTP 请求和响应管道可以被可视化为一系列请求委托的序列,一个接一个地调用,如下面的简化图所示,其中省略了一些中间件委托,如UseHsts

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 14.16:HTTP 请求和响应管道

如前所述,UseRoutingUseEndpoints方法必须一起使用。虽然定义映射路由(如/hello)的代码写在UseEndpoints中,但决定传入的 HTTP 请求 URL 路径是否匹配以及因此执行哪个端点的决策是在管道中的UseRouting点做出的。

实现匿名内联委托作为中间件

委托可以指定为内联匿名方法。我们将注册一个在端点路由决策之后插入到管道中的委托。

它将输出选择了哪个端点,以及处理特定路由/bonjour。如果匹配到该路由,它将以纯文本形式响应,不会进一步调用管道中的任何内容:

  1. Startup.cs中静态导入Console,如下所示:

    using static System.Console; 
    
    • 1
  2. 在调用UseRouting之后和调用UseHttpsRedirection之前添加语句,使用匿名方法作为中间件委托,如下所示:

    app.Use(async (HttpContext context, Func<Task> next) =>
    {
      RouteEndpoint? rep = context.GetEndpoint() as RouteEndpoint;
      if (rep is not null)
      {
        WriteLine($"Endpoint name: {rep.DisplayName}");
        WriteLine($"Endpoint route pattern: {rep.RoutePattern.RawText}");
      }
      if (context.Request.Path == "/bonjour")
      {
        // in the case of a match on URL path, this becomes a terminating
        // delegate that returns so does not call the next delegate
        await context.Response.WriteAsync("Bonjour Monde!");
        return;
      }
      // we could modify the request before calling the next delegate
      await next();
      // we could modify the response after calling the next delegate
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  3. 启动网站。

  4. 在 Chrome 中访问https://localhost:5001/,查看控制台输出,注意到匹配到了一个端点路由/,它被处理为/index,并且执行了Index.cshtml Razor Page 来返回响应,如下所示:

    Endpoint name: /index 
    Endpoint route pattern: 
    
    • 1
    • 2
  5. 访问https://localhost:5001/suppliers,注意到匹配到了一个端点路由/Suppliers,并且执行了Suppliers.cshtml Razor Page 来返回响应,如下所示:

    Endpoint name: /Suppliers 
    Endpoint route pattern: Suppliers 
    
    • 1
    • 2
  6. 访问https://localhost:5001/index,注意到匹配到了一个端点路由/index,并且执行了Index.cshtml Razor Page 来返回响应,如下所示:

    Endpoint name: /index 
    Endpoint route pattern: index 
    
    • 1
    • 2
  7. 访问https://localhost:5001/index.html,注意到控制台没有输出,因为没有匹配到端点路由,但匹配到了一个静态文件,因此作为响应返回。

  8. 访问https://localhost:5001/bonjour,注意到控制台没有输出,因为没有匹配到端点路由。相反,我们的委托匹配到了/bonjour,直接写入响应流,并返回,没有进一步处理。

  9. 关闭 Chrome 并关闭 Web 服务器。

实践与探索

通过回答一些问题来测试你的知识和理解,进行一些实践练习,并通过深入研究来探索本章的主题。

练习 14.1 – 测试你的知识

回答以下问题:

  1. 列出六个可以在 HTTP 请求中指定的方法名称。

  2. 列出六个状态码及其描述,这些状态码可以在 HTTP 响应中返回。

  3. ASP.NET Core 中,Startup类的作用是什么?

  4. 缩写 HSTS 代表什么,它有什么作用?

  5. 如何为网站启用静态 HTML 页面?

  6. 如何在 HTML 中间混合 C#代码以创建动态页面?

  7. 如何定义 Razor Pages 的共享布局?

  8. 如何在 Razor Page 中分离标记与代码背后的代码?

  9. 如何配置 Entity Framework Core 数据上下文以用于 ASP.NET Core 网站?

  10. 如何在 ASP.NET Core 2.2 或更高版本中重用 Razor Pages?

练习 14.2 – 实践构建数据驱动的网页

Northwind.Web网站添加一个 Razor Page,使用户能够查看按国家分组的客户列表。当用户点击客户记录时,他们将看到一个显示该客户完整联系信息以及其订单列表的页面。

练习 14.3 – 实践为控制台应用构建网页

将前几章的一些控制台应用重新实现为 Razor Pages,例如,从第四章编写、调试和测试函数,提供一个 Web 用户界面来输出乘法表、计算税款、生成阶乘和斐波那契序列。

练习 14.4 – 探索主题

使用以下页面上的链接,深入了解本章涵盖的主题:

github.com/markjprice/cs10dotnet6/blob/main/book-links.md#chapter-14---building-websites-using-aspnet-core-razor-pages

总结

本章中,你学习了使用 HTTP 进行 Web 开发的基础知识,如何构建一个返回静态文件的简单网站,以及如何使用 ASP.NET Core Razor Pages 结合 Entity Framework Core 来创建从数据库信息动态生成的网页。

我们回顾了 HTTP 请求和响应管道,辅助扩展方法的作用,以及如何添加自己的中间件,影响处理过程。

下一章,你将学习如何使用 ASP.NET Core MVC 构建更复杂的网站,该框架将构建网站的技术关注点分离为模型、视图和控制器,使其更易于管理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/889497
推荐阅读
相关标签
  

闽ICP备14008679号