C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (2)
使网站能够提供静态内容
一个只返回单一纯文本消息的网站并不是很有用!
至少,它应该返回静态 HTML 页面、网页将用于样式的 CSS 以及任何其他静态资源,例如图像和视频。
根据惯例,这些文件应存储在名为 wwwroot 的目录中,以将它们与您网站项目的动态执行部分分开。
创建静态文件和网页的文件夹
您现在将为您的静态网站资源创建一个文件夹,并创建一个使用 Bootstrap 进行样式设计的基本索引页面:
- In the Northwind.Web project/folder, create a folder named wwwroot. Note that Visual Studio recognizes it as a special type of folder by giving it a globe icon, .
- 在 wwwroot 文件夹中,添加一个名为 index.xhtml 的新文件。(在 Visual Studio 中,项目项模板名为 HTML 页面。)
- 在 index.xhtml 中,修改其标记以链接到 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, shrink-to-fit=no" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" 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>
- 良好实践:请访问以下链接查看最新版本:https://getbootstrap.com/docs/versions/。点击最新版本以进入其“开始使用 Bootstrap”页面。向下滚动页面到第 2 步,找到最新的 <link> 元素(以及在本章后面的 <script> 元素),您可以复制并粘贴。
Bootstrap 是全球最受欢迎的响应式、移动优先网站构建框架。您可以在以下链接阅读一个仅在线的部分,介绍 Bootstrap 的一些重要功能:https://github.com/markjprice/cs13net9/blob/main/docs/ch13-bootstrap.md。
- 在 wwwroot 文件夹中,添加一个名为 site.css 的文件并修改其内容,如下所示:
h1 {
color: darkcyan;
font-style: italic;
}
- 在 wwwroot 文件夹中,添加一个名为 categories.jpeg 的文件。您可以从以下链接下载它:https://github.com/markjprice/cs13net9/blob/main/code/images/Categories/categories.jpeg。
- 在 wwwroot 文件夹中,添加一个名为 about.xhtml 的文件并修改其内容,如下所示:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="site.css" rel="stylesheet" />
<title>About Northwind Web</title>
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1 class="display-3">About Northwind Web</h1>
<p class="lead">We supply products to our customers.</p>
<img src="categories.jpeg" />
</div>
</div>
</body>
</html>
启用静态和默认文件
如果您现在启动网站并在地址框中输入 http://localhost:5130/index.xhtml 或 https://localhost:5131/index.xhtml ,网站将返回一个 404 Not Found 错误,表示未找到网页。为了使网站能够返回静态文件,例如 index.xhtml ,我们必须明确配置该功能。
即使我们启用了存储在 wwwroot 中的静态文件,如果您启动网站并在地址框中输入 http://localhost:5130/ 或 https://localhost:5131/ ,网站仍然会返回 404 Not Found 错误,因为如果没有请求命名文件,web 服务器不知道默认返回什么。
您现在将启用静态文件,显式配置默认文件,如 index.xhtml ,并更改注册的返回纯文本响应的 URL 路径:
- 在 Program.cs 中,在启用 HTTPS 重定向后添加语句以启用静态文件和默认文件。同时,修改将 GET 请求映射为返回包含环境名称的纯文本响应的语句,使其仅响应 URL 路径 /env ,如以下代码中突出显示的内容所示:
app.UseDefaultFiles(); // index.xhtml, default.xhtml, and so on.
app.MapStaticAssets(); // .NET 9 or later.
// app.UseStaticFiles(); // .NET 8 or earlier.
app.MapGet("/env", () =>
#34;Environment is {app.Environment.EnvironmentName}");
- ASP.NET Core 9 引入了 MapStaticAssets 方法,该方法自动压缩静态文件,从而减少带宽需求。对于 ASP.NET Core 8 及更早版本,您必须改为调用 UseStaticFiles 方法。您可以在以下链接中了解更多信息: https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0#optimizing-static-web-asset-delivery。
警告!对 UseDefaultFiles 的调用必须在对 MapStaticAssets 或 UseStaticFiles 的调用之前,否则将无法正常工作!您将在本章末尾链接的在线部分中了解有关中间件和端点路由的顺序的更多信息。
- 启动网站。
- 启动 Chrome 并显示开发者工具。
- 在 Chrome 中输入 http://localhost:5130/ 。请注意,您被重定向到端口 5131 上的 HTTPS 地址,并且 index.xhtml 文件现在通过该安全连接返回,因为它是该网站的可能默认文件之一,并且是 wwwroot 文件夹中找到的第一个匹配项。
警告!如果您仍然看到环境名称为纯文本,请确保您将相对路径 /env 映射到该端点,而不仅仅是 / ,否则这将覆盖对 index.xhtml 文件的默认请求。
- 在 Chrome 中,输入 http://localhost:5131/about.xhtml ,注意网页被返回,并且它请求 site.css 文件,该文件应用了额外的样式。
- 在开发者工具中,注意对 Bootstrap 样式表的请求。
- 在 Chrome 中,输入 http://localhost:5130/env ,注意它返回的环境名称与之前的纯文本相同。
- 关闭 Chrome 并关闭网络服务器。
理解 MapStaticAssets
在.NET 9 中引入的 MapStaticAssets 通过整合构建和发布时的过程来收集应用程序中所有静态资源的数据。然后,运行时库使用这些数据高效地将这些文件提供给浏览器。
虽然 MapStaticAssets 通常可以直接替代 UseStaticFiles ,但它特别针对在构建和发布时已知的应用程序资产进行了优化。对于从其他位置提供的资产,例如磁盘或嵌入资源,仍应使用 UseStaticFiles 。
MapStaticAssets 相较于 UseStaticFiles 提供以下好处:
- 为应用中的所有资产构建时间压缩:gzip 在开发期间和 gzip + brotli 在发布期间。所有资产都经过压缩,目的是将资产的大小减少到最小。
- 每个资源的 ETag 是内容的 SHA-256 哈希的 Base64 编码字符串。这确保浏览器仅在文件内容发生变化时重新下载该文件。
作为一个例子,表 13.1 显示了使用 Fluent UI Blazor 组件库的原始和压缩大小,总共从 478 KB 未压缩到 84 KB 压缩:
文件 | 原始文本:Original 翻译文本:原始 | 压缩 | % 减少 |
fluent.js | 384 | 73 | 80.99% |
fluent.css | 94 | 11 | 88.30% |
总计 | 478 | 84 | 82.43% |
表 13.1:MapStaticAssets 如何压缩 Fluent UI Blazor 组件
如果所有网页都是静态的(也就是说,它们仅由网页编辑手动更改),那么我们的网站编程工作就完成了。但几乎所有网站都需要动态内容,这意味着通过执行代码在运行时生成的网页。
最简单的方法是使用 ASP.NET Core 的一个功能,名为 Blazor staticSSR。但在此之前,让我们了解一下为什么您可能会在开发者工具等工具中看到您不期望的额外请求。
理解开发过程中的浏览器请求
在开发者工具中,我们可以看到浏览器发出的所有请求。有些请求是你所期望的,例如:
- localhost : 这是网站项目中主页的请求。对于我们当前的项目,地址将是 http://localhost:5130/ 或 https://localhost:5131/ 。
- bootstrap.min.css : 这是对 Bootstrap 样式的请求。我们在主页上添加了对此的引用,因此浏览器随后发出了对样式表的请求。
某些请求仅在开发期间发出,并由您使用的代码编辑器确定。如果您在开发者工具中看到它们,通常可以忽略它们。例如:
- browserLink 和 aspnetcore-browser-refresh.js : 这些是 Visual Studio 发出的请求,用于将浏览器连接到 Visual Studio 进行调试和热重载。例如, https://localhost:5131/_vs/browserLink 和 https://localhost:5131/_framework/aspnetcore-browser-refresh.js 。
- negotiate?requestUrl , connect?transport , abort?Transport , 等等:这些是用于将 Visual Studio 与浏览器连接的附加请求。
- Northwind.Web/ : 这是一个与 SignalR 相关的安全 WebSockets 请求,用于将 Visual Studio 连接到浏览器: wss://localhost:44396/Northwind.Web/ .
现在您已经了解了如何设置一个基本的网站,并支持静态文件,如 HTML 网页和 CSS,让我们通过添加对动态生成的静态网页的支持来使其更有趣(当网页到达浏览器客户端时,它是静态的)。
ASP.NET Core 有多种技术用于动态生成静态网页,包括 Razor Pages 和模型视图控制器(MVC)Razor 视图。动态网页的最现代技术是 Blazor 静态 SSR 页面。但让我们首先回顾一下 Blazor 及其创建原因。
Blazor 的历史
Blazor 让您使用 C# 而不是 JavaScript 构建交互式网页 UI 组件。Blazor 在所有现代浏览器上都受支持。
JavaScript 和朋友们
传统上,任何需要在网页浏览器中执行的代码必须使用 JavaScript 编程语言或一种更高级的技术编写,该技术会转译(转换或编译)为 JavaScript。这是因为所有浏览器已经支持 JavaScript 超过二十年,因此它是实现客户端业务逻辑的最低共同分母。
JavaScript 确实存在一些问题。然而,尽管它与 C#和 Java 等 C 风格语言有表面上的相似之处,但一旦深入探讨,它实际上是非常不同的。它是一种动态类型的伪函数式语言,使用原型而不是类继承来实现对象重用。它可能看起来像人类,但当它被揭示为斯克鲁尔时,你会感到惊讶。
如果我们在浏览器中能够使用与服务器相同的语言和库,那就太好了。
即使是 Blazor 也无法完全取代 JavaScript。例如,浏览器的某些部分仅可通过 JavaScript 访问。Blazor 提供了一种互操作服务,以便您的 C# 代码可以调用 JavaScript 代码,反之亦然。您将在第 14 章《使用 Blazor 构建交互式 Web 组件》的仅在线 JavaScript 互操作部分看到这一点。
Silverlight – 使用插件的 C# 和 .NET
微软之前曾尝试通过一种名为 Silverlight 的技术来实现这一目标。当 Silverlight 2 于 2008 年发布时,C# 和 .NET 开发人员可以利用他们的技能构建库和可视组件,这些组件通过 Silverlight 插件在网页浏览器中执行。
到 2011 年和 Silverlight 5,苹果在 iPhone 上的成功以及史蒂夫·乔布斯对像 Flash 这样的浏览器插件的厌恶,最终导致微软放弃 Silverlight,因为像 Flash 一样,Silverlight 在 iPhone 和 iPad 上被禁止。
WebAssembly – Blazor 的目标
另一个网页浏览器的发展使微软有机会再次尝试。2017 年,WebAssembly 共识完成,所有主要浏览器现在都支持它:Chromium(Chrome、Edge、Opera 和 Brave)、Firefox 和 WebKit(Safari)。
WebAssembly(Wasm)是一种用于虚拟机的二进制指令格式,提供了一种在网络上以接近本地速度运行用多种语言编写的代码的方法。Wasm 被设计为高层语言(如 C#)编译的可移植目标。
.NET 7 及之前版本中的 Blazor 托管模型
Blazor 是一个单一的编程或应用模型。对于 .NET 7 及更早版本,开发者必须为每个项目选择一个托管模型:
- Blazor Server 项目运行在服务器端,因此 C# 代码可以完全访问业务逻辑可能需要的所有资源,而无需提供身份验证凭据。它使用 SignalR 将 UI 更新传递给客户端。服务器必须与每个客户端保持一个实时的 SignalR 连接,并跟踪每个客户端的当前状态。这意味着如果需要支持大量客户端,Blazor Server 的扩展性较差。它于 2019 年 9 月作为 ASP.NET Core 3 的一部分首次发布。
- Blazor Wasm 项目在客户端运行,因此 C# 代码只能访问浏览器中的资源。在访问服务器上的资源之前,它必须进行 HTTP 调用(这可能需要身份验证)。它于 2020 年 5 月作为 ASP.NET Core 3.1 的扩展首次发布,并被标记为 3.2,因为它是当前版本,因此不在 ASP.NET Core 3.1 的长期支持范围内。Blazor Wasm 3.2 版本使用了 Mono 运行时和 Mono 库。 .NET 5 及更高版本使用 Mono 运行时和 .NET 库。
- 一个 .NET MAUI Blazor 应用程序,也称为 Blazor Hybrid 项目,通过使用本地互操作通道将其网页 UI 渲染到网页视图控件,并托管在 .NET MAUI 应用程序中。它在概念上类似于 Electron 应用程序。
.NET 8 中 Blazor 托管模型的统一
随着 .NET 8 的发布,Blazor 团队创建了一个统一的托管模型,其中每个单独的组件都可以设置为使用不同的渲染模型执行:
- SSR:在服务器端执行代码,类似于 Razor Pages 和 MVC。然后将完整的响应发送到浏览器,以便向访问者显示,直到浏览器发出新的 HTTP 请求,服务器和客户端之间没有进一步的交互。就浏览器而言,网页是静态的,就像任何其他 HTML 文件一样。
- 流式渲染:在服务器端执行代码。HTML 标记可以返回并在浏览器中显示,并且在连接仍然打开时,任何异步操作可以继续执行。当所有异步操作完成后,最终的标记由服务器发送以更新页面内容。这改善了访客的体验,因为他们在等待其余内容时可以看到一些内容,比如“加载中…”的消息。
- 交互式服务器渲染:在实时交互过程中在服务器端执行代码,这意味着代码可以完全且轻松地访问服务器端资源,如数据库。这可以简化功能的实现。交互请求使用 SignalR 进行,这比完整请求更高效。浏览器和服务器之间需要保持一个永久连接,这限制了可扩展性。这是一个适合内部网网站的好选择,因为客户端数量有限且网络带宽高。
- 交互式 Wasm 渲染:在客户端执行代码,这意味着代码只能访问浏览器内的资源。这可能会使实现变得复杂,因为每当需要新数据时必须回调服务器。对于潜在有大量客户端且其中一些连接带宽较低的公共网站来说,这是一个不错的选择。
- 交互式自动渲染:首先在服务器上渲染以实现更快的初始显示,后台下载 Wasm 组件,然后切换到 Wasm 以进行后续交互。
这个统一模型意味着,通过精心规划,开发者可以一次编写 Blazor 组件,然后选择在 Web 服务器端、Web 客户端,或动态切换运行。这提供了最佳的解决方案。
理解 Blazor 组件
重要的是要理解,Blazor 用于创建 UI 组件。组件定义了如何渲染 UI 和响应用户事件,并且可以组合、嵌套,并编译成 Razor 类库以便打包和分发。
例如,为了在电商网站上提供产品的星级评分 UI,您可以创建一个名为 Rating.razor 的组件,如下所示的标记:
<div>
@for (int i = 0; i < Maximum; i++)
{
if (i < Value)
{
<span class="oi oi-star-filled" />
}
else
{
<span class="oi oi-star-empty" />
}
}
</div>
@code {
[Parameter]
public byte Maximum { get; set; }
[Parameter]
public byte Value { get; set; }
}
您可以在网页上使用该组件,如以下标记所示:
<h1>Review</h1>
<Rating id="rating" Maximum="5" Value="3" />
<textarea id="comment" />
创建组件实例的标记看起来像一个 HTML 标签,其中标签的名称是组件类型。组件可以通过元素嵌入到网页中,例如 <Rating Value="5" /> ,或者可以像映射的端点一样进行路由。
代码可以存储在一个名为 Rating.razor.cs 的单独代码隐藏文件中,而不是一个包含标记和 @code 块的单一文件。此文件中的类必须是 partial 并且与组件同名。
内置了许多 Blazor 组件,包括用于在网页的 <head> 部分设置像 <title> 这样的元素的组件,还有许多第三方会出售用于常见目的的组件。
Blazor 和 Razor 有什么区别?
您可能会想知道为什么 Blazor 组件使用 .razor 作为其文件扩展名。Razor 是一种模板标记语法,允许混合 HTML 和 C#。支持 Razor 语法的旧技术使用 .cshtml 文件扩展名来表示 C# 和 HTML 的混合。
Razor 语法用于:
- ASP.NET Core MVC 视图和使用 .cshtml 文件扩展名的部分视图。业务逻辑被分离到一个控制器类中,该类将视图视为一个模板,以将视图模型推送到该模板,然后将其输出到网页。
- 使用 .cshtml 文件扩展名的 Razor 页面。业务逻辑可以嵌入或分离到使用 .cshtml.cs 文件扩展名的文件中。输出是一个网页。
- 使用 .razor 文件扩展名的 Blazor 组件。输出作为网页的一部分进行渲染,尽管可以使用布局来包装组件,使其作为网页输出,并且可以使用 @page 指令来分配一个路由,定义检索组件作为页面的 URL 路径。
现在您了解了 Blazor 的背景,让我们来看一些更实际的内容:如何将 Blazor 支持添加到现有的 ASP.NET Core 项目中。
探索 Blazor 静态 SSR
ASP.NET Core Blazor 静态 SSR 允许开发者轻松地将 C# 代码语句与 HTML 标记混合,以使生成的网页动态。
启用 Blazor 静态 SSR
以下是您必须完成的任务摘要,以在现有的 ASP.NET Core 项目中启用 Blazor 及其静态 SSR 功能:
- 创建一个 Components 文件夹以包含您的 Blazor 组件。
- 创建一个 Components\Pages 文件夹以包含您的 Blazor 页面组件。
- 在 Components 文件夹中,创建三个 .razor 文件:_Imports.razor : 此文件导入所有 .razor 文件的命名空间,以便您无需在每个 .razor 文件的顶部导入它们。至少,您需要导入 Blazor 路由和您本地项目 Blazor 组件的命名空间。App.razor : 此文件包含将包含您所有 Blazor 组件的网页的 HTML。它还需要在网页的 <body> 中某处引用您的 Blazor Routes 组件。Routes.razor : 此文件定义了一个 <Router> 组件,该组件扫描当前程序集以查找页面组件及其注册的路由。
- 在 Components\Pages 中,创建一个 Index.razor 文件,这是一个 Blazor 页面组件,将作为 Blazor 路由器默认显示的主页。文件顶部需要一个指令来定义根路径的路由: @page "/" 。
- 在 Program.cs 中,您必须调用 AddRazorComponents() 来将 Blazor 或 Razor 组件 ( *.razor ) 文件注册到 ASP.NET Core 的依赖服务集合中,然后调用 MapRazorComponents<App>() 来映射所有找到的 Blazor 路由的端点。您还必须调用 UseAntiforgery() ,因为 Blazor 组件会自动检查防伪令牌;因此,HTTP 管道必须启用中间件以支持它们。
您现在将添加并启用 Blazor 静态 SSR 服务,然后将静态 HTML 页面复制并更改为 Blazor 静态 SSR 文件:
- 在 Northwind.Web 项目文件夹中,创建一个名为 Components 的文件夹。
- 在 Components 文件夹中,创建一个名为 Pages 的文件夹。
- 在 Components 文件夹中,创建一个名为 _Imports.razor 的文件。
- 在 _Imports.razor 中,添加语句以导入 Blazor 组件路由的命名空间,以及您的 Northwind.Web 项目及其组件,如下所示的标记:
@using Microsoft.AspNetCore.Components.Routing
@using Northwind.Web
@using Northwind.Web.Components
- 在 Components 文件夹中,创建一个名为 Routes.razor 的文件。
- 在 Routes.razor 中,添加语句以定义一个路由器,该路由器扫描当前程序集以查找已注册路由的 Blazor 页面组件,如下标记所示:
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" />
</Found>
</Router>
- 将 index.xhtml 文件复制到 Components\Pages 文件夹中。(在 Visual Studio 或 Rider 中,按住 Ctrl 键同时拖放。)
- 对于 Components\Pages 文件夹中的文件(不是原始文件),将文件扩展名从 index.xhtml 重命名为 Index.razor 。确保“I”是大写的。(Blazor 组件必须以大写字母开头,否则会出现编译错误!)
- 在 Components 文件夹中,创建一个名为 App.razor 的文件。
- 在 App.razor 中,从 Index.razor 中剪切并粘贴根标记,包括头部和主体元素,然后添加元素以使用 <HeadOutlet /> 插入页面标题,并为您的 <Routes> 组件,如以下标记所示:
<!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.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<HeadOutlet />
</head>
<body>
<Routes />
</body>
</html>
在 Index.razor 中,将 @page 指令添加到文件顶部,并将其路由设置为 / ,添加一个 <PageTitle> 组件以设置网页 <title> ,删除表示这是一个静态 HTML 页面的 <h2> 元素,并注意结果,如以下标记所示:
@page "/"
<PageTitle>Welcome to Northwind B2B</PageTitle>
<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 />
<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>
警告!如果您已为 Visual Studio 安装了 ReSharper,或者使用 Rider,那么它们可能会在您的 Razor 页面、Razor 视图和 Blazor 组件中发出“无法解析符号”的警告。这并不总是意味着存在实际问题。如果文件可以编译,则可以忽略它们的错误。有时这些工具会感到困惑,毫无必要地让开发者担忧。
- 在 Program.cs 中,在创建 builder 的语句后,添加一条语句以添加 ASP.NET Core Blazor 组件及其相关服务,并可选择性地定义一个 #region ,如下代码所示:
#region Configure the web server host and services.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents();
var app = builder.Build();
#endregion
在 Program.cs 中,在调用方法以使用 HTTPS 重定向后,添加一条语句以使用反伪造中间件,如以下代码中突出显示的内容所示:
app.UseHttpsRedirection();
app.UseAntiforgery();
在 Program.cs 中,在文件顶部添加一条语句以导入您的项目组件,如以下代码所示:
using Northwind.Web.Components; // To use App.
在 Program.cs 中,在映射 HTTP GET 请求到路径 /env 的语句之前,添加一个调用 MapRazorComponents 方法的语句,如以下代码中突出显示的那样:
app.MapRazorComponents<App>();
app.MapGet("/env", () =>
#34;Environment is {app.Environment.EnvironmentName}");
- 使用 https 启动配置开始网站项目。
- 在 Chrome 中,输入 https://localhost:5131/ ,注意到显示这是一个静态 HTML 页面的元素消失了。如果它仍然存在,那么您可能需要清空浏览器缓存。查看开发者工具,点击并按住重新加载此页面按钮,然后选择清空缓存并强制重新加载,如图 13.4 所示: