ASP.NET Core 实战-7.使用 Razor 视图呈现 HTML
Razor 页面中涉及的术语很容易混淆——PageModel、页面处理程序、Razor 视图——尤其是当一些术语描述具体功能,而其他术语描述模式和概念时。 我们在前面的章节中详细讨论了所有这些术语,但重要的是要让它们在你的脑海中直截了当:
-
Razor Pages - Razor Pages 通常是指基于页面的范例,它使用 Razor 视图结合路由、模型绑定和 HTML 生成。
-
Razor 页面 - 单个 Razor 页面代表单个页面或“端点”。 它通常由两个文件组成:一个包含 Razor 视图的 .cshtml 文件和一个包含页面的 PageModel 的 .cshtml.cs 文件。
-
PageModel - Razor 页面的 PageModel 是大部分操作发生的地方。 它是您为页面定义绑定模型的地方,该模型从传入请求中提取数据。 它也是您定义页面的页面处理程序的地方。
-
页面处理程序——每个 Razor 页面通常处理一个路由,但它可以处理多个 HTTP 动词,例如 GET 和 POST。 每个页面处理程序通常处理一个 HTTP 动词。
-
Razor 视图 - Razor 视图(也称为 Razor 模板)用于生成 HTML。 它们通常用于 Razor 页面的最后阶段,以生成 HTML 响应以发送回用户。
在 ASP.NET Core 中,通常使用 Razor 标记语法(有时被描述为模板语言)创建视图,该语法使用 HTML 和 C# 的混合来生成最终的 HTML。 本章介绍 Razor 的一些功能以及如何使用它为您的应用程序构建视图模板。 一般来说,用户将与您的应用程序进行两种交互:他们将读取您的应用程序显示的数据,并将数据或命令发送回它。 Razor 语言包含许多结构,可以轻松构建这两种类型的应用程序。
显示数据时,您可以使用 Razor 语言轻松地将静态 HTML 与 PageModel 中的值结合起来。 Razor 可以使用 C# 作为控制机制,因此添加条件元素和循环很简单——这是您单独使用 HTML 无法实现的。
将数据发送到 Web 应用程序的常规方法是使用 HTML 表单。实际上,您构建的每个动态应用程序都将使用表单; 有些应用程序几乎只是表格! ASP.NET Core 和 Razor 模板语言包括许多标记帮助程序,可以轻松生成 HTML 表单。
在本章中,我们将主要关注使用 Razor 显示数据和生成 HTML,而不是创建表单。 您将看到如何将值从 PageModel 呈现到 HTML,以及如何使用 C# 来控制生成的输出。 最后,您将学习如何将视图的公共元素提取到称为布局和部分视图的子视图中,以及如何组合它们以创建最终的 HTML 页面。
视图:渲染用户界面
正如您从前面有关 MVC 设计模式的章节中了解到的,Razor Page 的页面处理程序的工作是选择返回给客户端的内容。 例如,如果您正在开发一个待办事项列表应用程序,想象一个查看特定待办事项的请求,如图 7.1 所示。
图 7.1 使用 ASP.NET Core Razor Pages 处理对待办事项列表项的请求。页面处理程序构建视图所需的数据并将其公开为 PageModel 上的属性。 视图仅根据提供的数据生成 HTML; 它不需要知道这些数据来自哪里。 |
---|
一个典型的请求遵循图 7.1 所示的步骤:
-
中间件管道接收请求,路由中间件确定要调用的端点——在本例中,是 ToDo 文件夹中的 View Razor Page。
-
模型绑定器(Razor Pages 框架的一部分)使用请求为页面构建绑定模型,正如您在上一章中看到的那样。 绑定模型在 Razor 页面上设置为属性,或者在执行处理程序时作为参数传递给页面处理程序方法。 页面处理程序检查您是否为待办事项传递了有效的 id,从而使请求有效。
-
假设一切正常,页面处理程序调用构成应用程序模型的各种服务。 这可能会从数据库或文件系统加载有关待办事项的详细信息,然后将它们返回给处理程序。 作为此过程的一部分,应用程序模型或页面处理程序本身会生成要传递给视图的值,并将它们设置为 Razor Page PageModel 上的属性。
页面处理程序执行后,PageModel 应该包含呈现视图所需的所有数据。 在此示例中,它包含有关待办事项本身的详细信息,但它也可能包含其他数据:您还剩下多少待办事项、您今天是否有任何待办事项安排、您的用户名等等——任何控制 如何为请求生成最终 UI。
-
Razor 视图模板使用 PageModel 生成最终响应并通过中间件管道将其返回给用户。
在整个 MVC 讨论中,一个共同的主线是 MVC 带来的关注点分离,当涉及到你的观点时,它也没有什么不同。 在您的应用程序模型或控制器操作中直接生成 HTML 很容易,但您可以将该责任委托给单个组件,即视图。
但更重要的是,您还将使用 PageModel 上的属性将构建视图所需的数据与构建视图的过程分开。 这些属性应该包含视图生成最终输出所需的所有动态数据。
视图不应该调用 PageModel 上的方法——视图通常应该只访问已经收集并作为属性公开的数据。
正如您在第 3 章中所看到的,Razor 页面处理程序指示应该通过返回 PageResult(或返回 void)来呈现 Razor 视图。Razor 页面基础结构执行与给定 Razor 页面关联的 Razor 视图以生成最终响应。 在 Razor 模板中使用 C# 意味着您可以动态生成发送到浏览器的最终 HTML。 例如,这允许您在页面中显示当前用户的姓名、隐藏当前用户无权访问的链接,或者为列表中的每个项目呈现一个按钮。
想象一下,您的老板要求您向应用程序添加一个页面,该页面显示应用程序的用户列表。 您还应该能够从页面查看用户,或创建一个新用户,如图 7.2 所示。
图 7.2 在 Razor 中使用 C# 可以轻松生成在运行时变化的动态 HTML。 在此示例中,在 Razor 视图中使用 foreach 循环可显着减少 HTML 中的重复内容,否则您必须编写这些内容。 |
---|
使用 Razor 模板,生成这种动态内容很简单。 例如,清单 7.1 显示了一个可用于生成图 7.2 中的界面的模板。 它将标准 HTML 与 C# 语句相结合,并使用 Tag Helpers 生成表单元素。
清单 7.1 列出用户的 Razor 模板和添加新用户的表单
@page
@model IndexViewModel
<div class="row">
<div class="col-md-6"> //普通 HTML 原封不动地发送到浏览器。
<form method="post">
<div class="form-group">
<label asp-for="NewUser"></label>
<input class="form-control" asp-for="NewUser" /> //标记助手附加到 HTML 元素以创建表单。
<span asp-validation-for="NewUser"></span>
</div>
<div class="form-group">
<button type="submit"
class="btn btn-success">Add</button>
</div>
</form>
</div>
</div>
//值可以从 C# 对象写入 HTML。
<h4>Number of users: @Model.ExistingUsers.Count</h4>
<div class="row">
<div class="col-md-6">
<ul class="list-group">
//可以在 Razor 中使用诸如 for 循环之类的 C# 构造。
@foreach (var user in Model.ExistingUsers)
{
<li class="list-group-item d-flex justify-content-between">
<span>@user</span>
//Tag Helpers 也可以在表单之外使用,以帮助其他 HTML 生成。
<a class="btn btn-info"
asp-page="ViewUser"
asp-route-userName="@user">View</a>
</li>
}
</ul>
</div>
</div>
此示例演示了各种 Razor 功能。 有混合的 HTML 未经修改地写入响应输出,还有各种 C# 构造用于动态生成 HTML。 此外,您还可以看到几个 Tag Helpers。这些看起来像以 asp- 开头的普通 HTML 属性,但它们是 Razor 语言的一部分。 他们可以自定义他们附加的 HTML 元素,改变它的呈现方式。 它们使构建 HTML 表单比其他方式简单得多。 如果这个模板目前有点压倒性,请不要担心;
Razor Pages 在您构建应用程序时进行编译。 在幕后,它们只是您应用程序中的另一个 C# 类。 还可以启用 Razor 页面的运行时编译。 这允许您在应用程序运行时修改 Razor 页面,而无需显式停止和重建。 这在本地开发时很方便,但最好在部署到生产环境时避免。
创建 Razor 视图
使用 ASP.NET Core,每当您需要向用户显示 HTML 响应时,都应该使用视图来生成它。 尽管可以从页面处理程序中直接生成一个字符串,该字符串将在浏览器中呈现为 HTML,但这种方法不遵守 MVC 关注点分离,很快就会让你大吃一惊。
相反,通过依赖 Razor 视图来生成响应,您可以访问各种功能以及提供帮助的编辑器工具。 本节简要介绍 Razor 视图、可以使用它们执行的操作以及可以将数据传递给它们的各种方式。
Razor 视图和代码隐藏
,您已经看到 Razor Pages 通常包含两个文件:
- .cshtml 文件,通常称为 Razor 视图。
- .cshtml.cs 文件,通常称为代码隐藏,其中包含 PageModel。
Razor 视图包含 @page 指令,这使它成为 Razor 页面,正如您在第 3 章中看到的那样。没有此指令,Razor 页面框架将不会将请求路由到页面,并且在大多数情况下该文件将被忽略。
定义 指令是 Razor 文件中的一条语句,用于更改模板的解析或编译方式。 另一个常见的指令是 @using newNamespace 指令,它使 newNamespace 命名空间中的对象可用。
代码隐藏的 .cshtml.cs 文件包含关联 Razor 页面的 PageModel。它包含响应请求的页面处理程序,并且是 Razor 页面通常与应用程序的其他部分交互的地方。
尽管 .cshtml 和 .cshtml.cs 文件共享相同的名称,例如 ToDoItem.cshtml 和 ToDoItem.cshtml.cs,但将它们链接在一起的并不是文件名。 但如果不是通过文件名,Razor Pages 框架如何知道哪个 PageModel 与给定的 Razor Page 视图文件相关联?
在每个 Razor 页面的顶部,就在 @page 指令之后,是一个带有类型的 @model 指令,指示哪个 PageModel 与 Razor 视图相关联。 例如,以下指令指示 ToDoItemModel 是与 Razor 页面关联的 PageModel:
@page
@model ToDoItemModel
正如我们在第 4 章中介绍的那样,一旦请求被路由到 Razor Page,框架就会查找 @model 指令来决定使用哪个 PageModel。 根据所选的 PageModel,它会绑定到 PageModel 中标有 [BindProperty] 属性的任何属性并执行适当的页面处理程序(基于请求的 HTTP 动词)。
从技术上讲,PageModel 和@model 指令是可选的。 如果您不指定 PageModel,框架将执行默认页面处理程序,如您在第 5 章中所见。也可以将 .cshtml 和 .cshtml.cs 文件组合成一个 .cshtml 文件。 在实践中,这些方法都不是很常见,即使对于简单的页面也是如此,但如果你遇到它,这是需要注意的。
除了 @page 和 @model 指令之外,Razor 视图文件还包含执行以生成 HTML 响应的 Razor 模板。
介绍 Razor 模板
Razor 视图模板包含相互穿插的 HTML 和 C# 代码的混合体。 HTML 标记可让您轻松准确地描述应发送到浏览器的内容,而 C# 代码可用于动态更改呈现的内容。 例如,以下清单显示了 Razor 呈现字符串列表的示例,表示待办事项。
清单 7.2 用于呈现字符串列表的 Razor 模板
@page
@{
//可以在模板中执行任意 C#。 变量保持在整个页面的范围内。
var tasks = new List<string> { "Buy milk", "Buy eggs", "Buy bread" };
}
//标准 HTML 标记将原样呈现到输出。
<h1>Tasks to complete</h1>
<ul>
//混合 C# 和 HTML 允许您在运行时动态创建 HTML。
@for(var i=0; i< tasks.Count; i++)
{
var task = tasks[i];
<li>@i - @task</li>
}
</ul>
此模板中的纯 HTML 部分位于尖括号中。 Razor 引擎将这个 HTML 直接复制到输出中,保持不变,就像您正在编写一个普通的 HTML 文件一样。
除了 HTML,您还可以在其中看到许多 C# 语句。 例如,能够使用 for 循环而不是显式写出每个
清单 7.3 通过渲染 Razor 模板生成的 HTML 输出
<h1>Tasks to complete</h1> //Razor 模板中的 HTML 直接写入输出。
<ul>
<li>0 - Buy milk</li>
<li>1 - Buy eggs</li>
<li>2 - Buy bread</li> //<li> 元素是根据数据动态生成的。
</ul> //Razor 模板中的 HTML 直接写入输出。
如您所见,Razor 模板在渲染后的最终输出是简单的 HTML。 没有什么复杂的东西,只是可以发送到浏览器并呈现的直接 HTML 标记。 图 7.3 显示了浏览器如何呈现它。
图 7.3 Razor 模板可用于在运行时从 C# 对象动态生成 HTML。 在这种情况下,for 循环用于创建重复的 HTML |
---|
在这个例子中,为了简单起见,我对列表值进行了硬编码——没有提供动态数据。 这在简单的 Razor 页面上通常是这种情况,就像您在主页上可能拥有的那样——您需要显示一个几乎静态的页面。 对于您的应用程序的其余部分,您需要显示某种类型的数据将更为常见,通常作为 PageModel 上的属性公开。
将数据传递给视图
在 ASP.NET Core 中,您可以通过多种方式将数据从 Razor 页面中的页面处理程序传递到其视图。 哪种方法最好取决于您尝试传递的数据,但通常您应该按以下顺序使用这些机制:
PageModel
属性——您通常应该公开任何需要在PageModel
上显示为属性的数据。 任何特定于关联 Razor 视图的数据都应该以这种方式公开。PageModel
对象在渲染时在视图中可用,您很快就会看到。ViewData
——这是一个带有字符串键的对象字典,可用于将任意数据从页面处理程序传递到视图。 此外,它还允许您将数据传递给_layout
文件,正如您将在第 7.4 节中看到的那样。 这是使用ViewData
而不是在PageModel
上设置属性的主要原因。HttpContext
- 从技术上讲,HttpContext
对象在页面处理程序和 Razor 视图中都可用,因此您可以使用它在它们之间传输数据。 但是不要——你不需要使用其他可用的方法。@inject services
——你可以使用依赖注入来使服务在你的视图中可用,尽管这通常应该非常谨慎地使用。
毫无疑问,将数据从页面处理程序传递到视图的最佳方法是使用 PageModel
上的属性。 属性本身没有什么特别之处; 你可以在那里存储任何东西来保存你需要的数据。
许多框架都有用于绑定 UI 组件的数据上下文的概念。
PageModel
是一个类似的概念,因为它包含要在 UI 中显示的值,但绑定只是单向的;PageModel
为 UI 提供值,一旦 UI 被构建并作为响应发送,PageModel 就会被销毁。
正如我在第 7.2.1 节中所述,Razor 视图顶部的 @model 指令描述了与给定 Razor 页面关联的 PageModel
类型。 与 Razor 页面关联的 PageModel
包含一个或多个页面处理程序,并将数据公开为属性以在 Razor 视图中使用。
清单 7.4 将数据公开为 PageModel 上的属性
public class ToDoItemModel : PageModel //PageModel 在执行时被传递给 Razor 视图。
{
public List<string> Tasks { get; set; }
public string Title { get; set; } //可以从 Razor 视图访问公共属性。
//构建所需的数据:这通常会调用服务或数据库来加载数据。
public void OnGet(int id)
{
Title = "Tasks for today";
Tasks = new List<string>
{
"Get fuel",
"Check oil",
"Check tyre pressure"
};
}
}
您可以使用 Model 属性从 Razor 视图访问 PageModel 实例本身。例如,要在 Razor 视图中显示 ToDoItemModel 的 Title 属性,您可以使用 <h1>@Model.Title</h1>
。 这将呈现 ToDoItemModel.Title
属性中提供的字符串,生成 Html <h1>Tasks for today</h1>
。
请注意,
@model
指令应该在视图的顶部,就在@page
指令之后,并且它有一个小写的 m。Model
属性可以在视图中的任何位置访问,并且有一个大写的 M。
在绝大多数情况下,在 PageModel
上使用公共属性是可行的方法; 它是在页面处理程序和视图之间传递数据的标准机制。 但在某些情况下,PageModel
上的属性可能不是最合适的。 当您想要在视图布局之间传递数据时,通常会出现这种情况。
一个常见的例子是页面的标题。 您需要为应用程序中的每个页面提供一个标题,因此您可以创建一个具有 Title
属性的基类,并使每个 PageModel
都从它继承。 但这非常麻烦,因此针对这种情况的常用方法是使用 ViewData
集合来传递数据。
事实上,标准 Razor 页面模板默认使用这种方法,通过在视图本身中设置 ViewData
字典的值:
@{
ViewData["Title"] = "Home Page";
}
<h2>@ViewData["Title"].</h2>
此模板将 ViewData
字典中“Title”键的值设置为“Home Page”,然后获取要在模板中呈现的键。 这种设置和立即获取可能看起来是多余的,但由于 ViewData
字典在整个请求中共享,它使页面的标题在布局中可用,稍后您将看到。 渲染时,前面的模板将产生以下输出:
<h2>Home Page.</h2>
您还可以通过两种不同的方式从页面处理程序中设置 ViewData
字典中的值,如下面的清单所示。
清单 7.5 使用属性设置
ViewData
值
public class IndexModel: PageModel
{
[ViewData] //用 [ViewData] 属性标记的属性在 ViewData 中设置。
public string Title { get; set; }
public void OnGet()
{
Title = "Home Page"; //ViewData["Title"] 的值将设置为"Home Page"。
ViewData["Subtitle"] = "Welcome"; //您可以直接在 ViewData 字典中设置键。
}
}
您可以像以前一样在模板中显示值:
<h1>@ViewData["Title"]</h3>
<h2>@ViewData["Subtitle"]</h3>
我没有发现
[ViewData]
属性特别有用,但它是另一个需要注意的功能。 相反,我为任何ViewData
键创建一组全局静态常量,并引用它们而不是重复键入“标题”。 您将获得值的IntelliSense
,它们将是重构安全的,并且您将避免难以发现的拼写错误。
正如我之前提到的,除了 PageModel
属性和 ViewData
之外,还有其他机制可以用来传递数据,但是这两个是我个人使用的唯一机制,因为你可以用它们做你需要的一切。 提醒一下,请始终尽可能使用 PageModel
属性,因为您可以从强类型和 IntelliSense 中受益。 仅针对需要在 Razor 视图之外访问的值回退到 ViewData
。
使用 Razor 创建动态网页
您可能很高兴知道您可以在 C# 中执行的几乎所有操作都可以在 Razor 语法中实现。 在幕后,.cshtml 文件被编译成普通的 C# 代码(带有用于原始 HTML 部分的字符串),因此可以创建您需要的任何奇怪和奇妙的行为!
话虽如此,仅仅因为您可以做某事并不意味着您应该做某事。如果您使文件尽可能简单,您会发现使用和维护文件会容易得多。 几乎所有编程都是如此,但我发现 Razor 模板尤其如此。
在 Razor 模板中使用 C#
使用 Razor 模板时最常见的要求之一是将您在 C# 中计算的值呈现到 HTML。 例如,您可能希望打印当前年份以在 HTML 中与版权声明一起使用,以给出以下结果:
<p>Copyright 2020 ©</p>
或者您可能想要打印计算结果:
<p>The sum of 1 and 2 is <i>3</i><p>
您可以通过两种方式执行此操作,具体取决于您需要执行的确切 C# 代码。 如果代码是单条语句,可以使用@符号表示要将结果写入 HTML 输出,如图 7.4 所示。 您已经看到它用于从 PageModel 或 ViewData 中写出值。
图 7.4 将 C# 表达式的结果写入 HTML。 @ 符号表示 C# 代码的开始位置,表达式在语句的结尾处结束,在本例中为空格。 |
---|
如果你要执行的C#是需要空格的,那么你需要用括号来划定C#,如图7.5所示。
图 7.5 当 C# 表达式包含空格时,您必须 使用 @() 将其括在括号中,以便 Razor 引擎知道 C# 停止和 HTML 开始的位置。 |
---|
这两种将 C# 计算并直接写入 HTML 输出的方法称为 Razor 表达式。
如果要编写文字
@
字符,而不是 C# 表达式,请使用第二个@
字符:@@
。
有时你会想要执行一些 C#,但你不需要输出值。我们在 ViewData 中设置值时使用了这种技术:
@{
ViewData["Title"] = "Home Page";
}
此示例演示了一个 Razor 代码块,它是普通的 C# 代码,由 @{}
结构标识。 这里没有写入 HTML 输出; 就像你在任何其他普通的 C# 文件中编写的一样编译它。
在代码块内执行代码时,它必须是有效的 C#,因此需要添加分号。 相反,当您使用 Razor 表达式直接将值写入响应时,您不需要它们。 如果您的输出 HTML 意外中断,请留意丢失或恶意的额外分号。
Razor 表达式是将数据从 PageModel
写入 HTML 输出的最常用方法之一。 您将在下一章看到另一种方法,使用标签助手。 然而,Razor 的功能远不止于此,正如您将在下一节中看到的那样,您将学习如何在模板中包含传统的 C# 结构。
向 Razor 模板添加循环和条件
与静态 HTML 相比,使用 Razor 模板的最大优势之一是能够动态生成输出。 能够使用 Razor 表达式将值从 PageModel 写入 HTML 是其中的关键部分,但另一个常见用途是循环和条件。 例如,使用这些,您可以隐藏 UI 的各个部分,或为列表中的每个项目生成 HTML。
循环和条件包括 if
和 for
循环等结构。 在 Razor 模板中使用它们几乎与 C# 相同,但您需要在它们的用法前加上 @ 符号。 如果您还没有掌握 Razor 的窍门,如果有疑问,请输入另一个 @!
Razor 在 ASP.NET Core 上下文中的一大优势是它使用您已经熟悉的语言:C# 和 HTML。 没有必要为其他模板语言学习一套全新的原语:你已经知道的 if
、foreach
和 while
构造是一样的。 当您不需要它们时,您正在编写原始 HTML,因此您可以准确地看到用户将在他们的浏览器中获得什么。
在清单 7.6 中,我在模板中应用了许多不同的技术来显示待办事项。 PageModel
有一个 bool IsComplete
属性,以及一个名为 Tasks 的 List<string>
属性,其中包含所有未完成的任务。
清单 7.6 用于渲染 ToDoItemViewModel 的 Razor 模板
@page
@model ToDoItemModel //@model 指令指示模型中 PageModel 的类型。
<div>
//if 控制结构在运行时检查 PageModel 的 IsComplete 属性的值。
@if (Model.IsComplete)
{
<strong>Well done, you’re all done!</strong>
}
else
{
<strong>The following tasks remain:</strong>
<ul>
//foreach 结构将为 Model.Tasks 中的每个任务生成一次 <li> 元素。
@foreach (var task in Model.Tasks)
{
//Razor 表达式用于将任务写入 HTML 输出。
<li>@task</li>
}
</ul>
}
</div>
这段代码绝对符合混合 C# 和 HTML 的承诺! 在任何普通程序中都有传统的 C# 控制结构,例如 if
和 foreach
,它们散布着要发送到浏览器的 HTML 标记。 如您所见,@
符号用于指示您何时开始控制语句,但通常让 Razor 模板推断您何时在 HTML 和 C# 之间来回切换。
该模板显示了如何在运行时生成动态 HTML,具体取决于提供的确切数据。 如果模型有未完成的任务,HTML 将为每个任务生成一个列表项,产生类似于图 7.6 所示的输出。
图 7.6 Razor 模板根据运行时传递给视图的数据为每个剩余任务生成一个 |
---|
ASP.NET Core 团队的一个常见比喻是,他们试图确保您在构建应用程序时“落入成功的陷阱”。 这指的是,默认情况下,做某事的最简单方法应该是正确的做事方法。 这是一个伟大的理念,因为这意味着如果您遵循标准方法,您不应该被安全问题所困扰。 但是,有时您可能需要越过安全栏杆; 一个常见的用例是当您需要将 C# 对象中包含的一些 HTML 呈现到输出时,您将在下一节中看到。
使用 Raw 渲染 HTML
在前面的示例中,我们通过使用 @task
Razor 表达式编写字符串 task 将任务列表呈现为 HTML。 但是如果任务变量包含您要显示的 HTML,而不是“Check oil”它包含“<strong>Check oil</strong>
”怎么办? 如果你像以前一样使用 Razor 表达式来输出这个,你可能希望得到这个:
<li><strong>Check oil</strong></li>
但事实并非如此。 生成的 HTML 如下所示:
<li><strong>Check oil</strong></li>
嗯,看起来很奇怪,对吧? 这里发生了什么? 为什么模板没有像之前的示例那样将变量写入 HTML? 如果您查看浏览器如何显示此 HTML,如图 7.7 所示,那么希望它更有意义。
图 7.7 第二个项目,“<strong>Check oil<strong> ”已经被 HTML 编码,所以 <strong> 元素作为任务的一部分对用户是可见的。 这样可以避免任何安全问题,因为用户无法将恶意脚本注入您的 HTML。 |
---|
Razor 模板在将 C# 表达式写入输出流之前对其进行编码。这主要是出于安全原因; 将任意字符串写入您的 HTML 可能允许用户将恶意数据和 JavaScript 注入您的网站。 因此,您在 Razor 模板中打印的 C# 变量将被编写为 HTML 编码的值。
在某些情况下,您可能需要直接将包含在字符串中的 HTML 写入响应。 如果你发现自己处于这种情况,首先,停下来。 你真的需要这样做吗? 如果您正在编写的值是由用户输入的,或者是根据用户提供的值创建的,那么在您的网站中创建安全漏洞的风险很大。
如果您确实需要将变量写入 HTML 流,您可以使用视图页面上的 Html 属性并调用 Raw 方法来执行此操作:
<li>@Html.Raw(task)</li>
通过这种方式,task 中的字符串将直接写入输出流,生成您最初想要的 HTML,<li><strong>Check oil</strong></li>
,呈现如图 7.8 所示。
图 7.8 第二个项目,“Check oil”已经使用 Html.Raw() 输出,所以它没有被 HTML 编码。 元素导致第二个项目以粗体显示。 应尽可能避免以这种方式使用 Html.Raw(),因为它存在安全风险。 |
---|
在用户输入上使用 Html.Raw 会产生安全风险,用户可能会使用该风险将恶意代码注入您的网站。 尽可能避免使用 Html.Raw。
本节中显示的 C# 构造可能很有用,但它们会使您的模板更难阅读。 通常更容易理解主要是 HTML 标记而不是 C# 的 Razor 模板的意图。
在以前的 ASP.NET 版本中,这些结构,特别是 Html 帮助器属性,是生成动态标记的标准方法。 您仍然可以通过在 Html 属性上使用各种 HtmlHelper3 方法在 ASP.NET Core 中使用这种方法,但这些方法在很大程度上已被一种更简洁的技术所取代:标记帮助程序。
Tag Helpers 是 ASP.NET Core 中 Razor 的一项新功能,但许多其他功能已从以前的 ASP.NET 版本中继承。 在本章的下一部分中,您将看到如何创建嵌套的 Razor 模板并使用局部视图来减少视图中的重复量。
布局、局部视图和 _ViewStart
在本节中,您将了解布局和局部视图,它们允许您提取通用代码以减少重复。 这些文件使您可以更轻松地对 HTML 进行一次影响多个页面的更改。 你还将学习如何使用 _ViewStart
和 _ViewImports 为每个 Razor 页面运行通用代码,以及如何在页面中包含可选部分。
每个 HTML 文档都有一定数量的必需元素:<html>
、<head>
和 <body>
。 同样,在应用程序的每一页上经常会重复一些常见的部分,例如页眉和页脚,如图 7.9 所示。 应用程序中的每个页面也可能会引用相同的 CSS 和 JavaScript 文件。
图 7.9 典型的 Web 应用程序具有基于块的布局,其中一些块对应用程序的每个页面都是通用的。 整个应用程序的标题块可能相同,但侧边栏可能仅与一个部分中的页面相同。 应用程序中每个页面的正文内容都会有所不同。 |
---|
所有这些不同的元素加起来就是一场维护噩梦。 如果您必须在每个视图中手动包含这些内容,那么进行任何更改将是一个费力且容易出错的过程,涉及编辑每个页面。 相反,Razor 允许您将这些常见元素提取到布局中。
定义 Razor 中的布局是包含通用代码的模板。 它不能直接渲染,但可以与普通的 Razor 视图一起渲染。
通过将常用标记提取到布局中,您可以减少应用程序中的重复。 这使更改更容易,使您的视图更易于管理和维护,并且通常是良好的做法!
使用布局进行共享标记
在大多数情况下,布局文件是普通的 Razor 模板,其中包含多个页面的通用标记。 一个 ASP.NET Core 应用可以有多个布局,并且布局可以引用其他布局。 这样做的一个常见用途是为应用程序的不同部分设置不同的布局。 例如,一个电子商务网站可能对大多数页面使用三列视图,但当您进入结帐页面时使用单列布局,如图 7.10 所示。
图 7.10 https://manning.com 网站对 Web 应用程序的不同部分使用不同的布局。 产品页面使用三列布局,但购物车页面使用单列布局。 |
---|
您经常会在许多不同的 Razor 页面中使用布局,因此它们通常放置在 Pages/Shared 文件夹中。 您可以为它们命名任何您喜欢的名称,但有一个通用约定是使用 _Layout.cshtml 作为应用程序中基本布局的文件名。 这是 Visual Studio 和 .NET CLI 中 Razor 页面模板使用的默认名称。
一个常见的约定是在布局文件前加上下划线 (_),以将它们与 Pages 文件夹中的标准 Razor 模板区分开来。
布局文件看起来类似于普通的 Razor 模板,但有一个例外:每个布局都必须调用 @RenderBody()
函数。 这告诉模板引擎在哪里插入来自子视图的内容。 下面的清单显示了一个简单的布局。 通常,您的应用程序将在布局中引用您的所有 CSS 和 JavaScript 文件,并包含所有常见元素,例如页眉和页脚,但此示例几乎包含最低限度的 HTML。
清单 7.7 调用 RenderBody 的基本 _Layout.cshtml 文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewData["Title"]</title> //ViewData 是将数据从视图传递到布局的标准机制。
//每个页面共有的元素,例如您的 CSS,通常可以在布局中找到。
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
@RenderBody() //告诉模板引擎在哪里插入子视图的内容
</body>
</html>
如您所见,布局文件包括所需的元素,例如 <html>
和 <head>
,以及您在每个页面上需要的元素,例如 <title>
和 <link>
。 这个例子还展示了将页面标题存储在 ViewData
中的好处; 布局可以在 <title>
元素中呈现它,以便它显示在浏览器的选项卡中,如图 7.11 所示。
图 7.11 |
---|