Loading

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; 它不需要知道这些数据来自哪里。
image

一个典型的请求遵循图 7.1 所示的步骤:

  1. 中间件管道接收请求,路由中间件确定要调用的端点——在本例中,是 ToDo 文件夹中的 View Razor Page。

  2. 模型绑定器(Razor Pages 框架的一部分)使用请求为页面构建绑定模型,正如您在上一章中看到的那样。 绑定模型在 Razor 页面上设置为属性,或者在执行处理程序时作为参数传递给页面处理程序方法。 页面处理程序检查您是否为待办事项传递了有效的 id,从而使请求有效。

  3. 假设一切正常,页面处理程序调用构成应用程序模型的各种服务。 这可能会从数据库或文件系统加载有关待办事项的详细信息,然后将它们返回给处理程序。 作为此过程的一部分,应用程序模型或页面处理程序本身会生成要传递给视图的值,并将它们设置为 Razor Page PageModel 上的属性。

    页面处理程序执行后,PageModel 应该包含呈现视图所需的所有数据。 在此示例中,它包含有关待办事项本身的详细信息,但它也可能包含其他数据:您还剩下多少待办事项、您今天是否有任何待办事项安排、您的用户名等等——任何控制 如何为请求生成最终 UI。

  4. 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 中的重复内容,否则您必须编写这些内容。
image

使用 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 循环而不是显式写出每个

  • 元素的优势应该是不言而喻的。 在下一节中,我将深入探讨 Razor 的更多 C# 功能。 渲染时,清单 7.2 中的模板将生成以下 HTML。

    清单 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
  • 元素。
  • image

    在这个例子中,为了简单起见,我对列表值进行了硬编码——没有提供动态数据。 这在简单的 Razor 页面上通常是这种情况,就像您在主页上可能拥有的那样——您需要显示一个几乎静态的页面。 对于您的应用程序的其余部分,您需要显示某种类型的数据将更为常见,通常作为 PageModel 上的属性公开。

    将数据传递给视图

    在 ASP.NET Core 中,您可以通过多种方式将数据从 Razor 页面中的页面处理程序传递到其视图。 哪种方法最好取决于您尝试传递的数据,但通常您应该按以下顺序使用这些机制:

    1. PageModel 属性——您通常应该公开任何需要在 PageModel 上显示为属性的数据。 任何特定于关联 Razor 视图的数据都应该以这种方式公开。 PageModel 对象在渲染时在视图中可用,您很快就会看到。
    2. ViewData——这是一个带有字符串键的对象字典,可用于将任意数据从页面处理程序传递到视图。 此外,它还允许您将数据传递给 _layout 文件,正如您将在第 7.4 节中看到的那样。 这是使用 ViewData 而不是在 PageModel 上设置属性的主要原因。
    3. HttpContext - 从技术上讲,HttpContext 对象在页面处理程序和 Razor 视图中都可用,因此您可以使用它在它们之间传输数据。 但是不要——你不需要使用其他可用的方法。
    4. @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# 代码的开始位置,表达式在语句的结尾处结束,在本例中为空格。
    image

    如果你要执行的C#是需要空格的,那么你需要用括号来划定C#,如图7.5所示。

    图 7.5 当 C# 表达式包含空格时,您必须 使用 @() 将其括在括号中,以便 Razor 引擎知道 C# 停止和 HTML 开始的位置。
    image

    这两种将 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。

    循环和条件包括 iffor 循环等结构。 在 Razor 模板中使用它们几乎与 C# 相同,但您需要在它们的用法前加上 @ 符号。 如果您还没有掌握 Razor 的窍门,如果有疑问,请输入另一个 @!

    Razor 在 ASP.NET Core 上下文中的一大优势是它使用您已经熟悉的语言:C# 和 HTML。 没有必要为其他模板语言学习一套全新的原语:你已经知道的 ifforeachwhile 构造是一样的。 当您不需要它们时,您正在编写原始 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# 控制结构,例如 ifforeach,它们散布着要发送到浏览器的 HTML 标记。 如您所见,@ 符号用于指示您何时开始控制语句,但通常让 Razor 模板推断您何时在 HTML 和 C# 之间来回切换。

    该模板显示了如何在运行时生成动态 HTML,具体取决于提供的确切数据。 如果模型有未完成的任务,HTML 将为每个任务生成一个列表项,产生类似于图 7.6 所示的输出。

    图 7.6 Razor 模板根据运行时传递给视图的数据为每个剩余任务生成一个
  • 项。 您可以使用 if 块根据模型中的值呈现完全不同的 HTML。
  • image

    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>&lt;strong&gt;Check oil&lt;/strong&gt;</li>
    

    嗯,看起来很奇怪,对吧? 这里发生了什么? 为什么模板没有像之前的示例那样将变量写入 HTML? 如果您查看浏览器如何显示此 HTML,如图 7.7 所示,那么希望它更有意义。

    图 7.7 第二个项目,“<strong>Check oil<strong>”已经被 HTML 编码,所以 <strong> 元素作为任务的一部分对用户是可见的。 这样可以避免任何安全问题,因为用户无法将恶意脚本注入您的 HTML。
    image

    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(),因为它存在安全风险。
    image

    在用户输入上使用 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 应用程序具有基于块的布局,其中一些块对应用程序的每个页面都是通用的。 整个应用程序的标题块可能相同,但侧边栏可能仅与一个部分中的页面相同。 应用程序中每个页面的正文内容都会有所不同。
    image

    所有这些不同的元素加起来就是一场维护噩梦。 如果您必须在每个视图中手动包含这些内容,那么进行任何更改将是一个费力且容易出错的过程,涉及编辑每个页面。 相反,Razor 允许您将这些常见元素提取到布局中。

    定义 Razor 中的布局是包含通用代码的模板。 它不能直接渲染,但可以与普通的 Razor 视图一起渲染。

    通过将常用标记提取到布局中,您可以减少应用程序中的重复。 这使更改更容易,使您的视图更易于管理和维护,并且通常是良好的做法!

    使用布局进行共享标记

    在大多数情况下,布局文件是普通的 Razor 模板,其中包含多个页面的通用标记。 一个 ASP.NET Core 应用可以有多个布局,并且布局可以引用其他布局。 这样做的一个常见用途是为应用程序的不同部分设置不同的布局。 例如,一个电子商务网站可能对大多数页面使用三列视图,但当您进入结帐页面时使用单列布局,如图 7.10 所示。

    图 7.10 https://manning.com 网站对 Web 应用程序的不同部分使用不同的布局。 产品页面使用三列布局,但购物车页面使用单列布局。
    image

    您经常会在许多不同的 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 元素的内容用于命名用户浏览器中的选项卡,在本例中为 Home Page。</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175758998-1006661377.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <blockquote> <p>布局文件不是独立的 Razor 页面,不参与路由,因此它们不以 @page 指令开头。</p> </blockquote> <p>视图可以通过在 Razor 代码块中设置 Layout 属性来指定要使用的布局文件。</p> <blockquote> <p>清单 7.8 从视图中设置 Layout 属性</p> </blockquote> <pre><code class="language-html">@{ Layout = "_Layout"; //将页面的布局设置为 _Layout.cshtml。 ViewData["Title"] = "Home Page"; //ViewData 是一种将数据从 Razor 视图传递到布局的便捷方式。 } //Razor 视图中要在布局内渲染的内容 <h1>@ViewData["Title"]</h1> <p>This is the home page</p> </code></pre> <p>视图中的任何内容都将在布局中呈现,在该布局中调用 <code>@RenderBody()</code>。 结合前面的两个清单将生成以下 HTML 并将其发送给用户。</p> <blockquote> <p>清单 7.9 结合视图及其布局的渲染输出</p> </blockquote> <pre><code class="language-html"><!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Home Page</title> //视图中设置的 ViewData 用于渲染布局。 <link rel="stylesheet" href="/css/site.css" /> </head> <body> //RenderBody 调用呈现视图的内容。 <h1>Home Page</h1> <p>This is the home page</p> </body> <html> </code></pre> <p>明智地使用布局对于减少页面之间的重复非常有用。 默认情况下,布局仅提供一个位置,您可以在调用 <code>@RenderBody</code> 时从视图中呈现内容。 如果这限制性太强,您可以使用部分呈现内容。</p> <h3 id="使用部分覆盖父布局">使用部分覆盖父布局</h3> <p>当您开始在应用程序中使用多个布局时,一个常见的要求是能够在布局中的多个位置呈现来自子视图的内容。考虑使用两列的布局的情况。 视图需要一种机制来表示“在左列中呈现此内容”和“在右列中呈现此其他内容”。 这是通过使用部分来实现的。</p> <blockquote> <p>注意 请记住,本章中概述的所有功能都特定于 Razor,它是一个服务器端渲染引擎。 如果您使用客户端 SPA 框架来构建应用程序,您可能会以其他方式处理这些要求,无论是在客户端代码中还是通过向 Web API 端点发出多个请求。</p> </blockquote> <p>部分提供了一种组织视图元素应放置在布局中的位置的方法。 它们在视图中使用@section 定义进行定义,如下面的清单所示,它定义了与主要内容分开的侧边栏的 HTML 内容,位于名为 Sidebar 的部分中。 @section 可以放在文件中的任何位置,无论是顶部还是底部,只要方便。</p> <blockquote> <p>清单 7.10 在视图模板中定义一个部分</p> </blockquote> <pre><code class="language-html">@{ Layout = "_TwoColumn"; } @section Sidebar { <p>This is the sidebar content</p> //大括号内的所有内容都是侧边栏部分的一部分,而不是主体内容。 } //任何不在@section 内的内容都将由@RenderBody 调用呈现。 <p>This is the main content </p> </code></pre> <p>该部分通过调用@RenderSection() 在父布局中呈现。 这会将子部分中包含的内容呈现到布局中。 部分可以是必需的,也可以是可选的。 如果需要,视图必须声明给定的<code>@section</code>; 如果它们是可选的,它们可以被省略,并且布局将跳过它们。 跳过的部分不会出现在呈现的 HTML 中。 下面的清单显示了一个布局,其中包含一个名为 <code>Sidebar</code> 的必需部分和一个名为 <code>Scripts</code> 的可选部分。</p> <blockquote> <p>清单 7.11 渲染布局文件中的一个部分,_TwoColumn.cshtml</p> </blockquote> <pre><code class="language-html">@{ Layout = "_Layout"; //此布局嵌套在布局本身内。 } <div class="main-content"> @RenderBody() //呈现不属于某个部分的视图中的所有内容 </div> <div class="side-bar"> //渲染侧边栏部分; 如果侧边栏部分未在视图中定义,则抛出错误 @RenderSection("Sidebar", required: true) </div> //渲染脚本部分; 如果视图中未定义脚本部分,请忽略它。 @RenderSection("Scripts", required: false) </code></pre> <blockquote> <p>在您的布局页面中有一个称为脚本的可选部分是很常见的。 这可用于渲染某些视图所需的额外 JavaScript,但并非每个视图都需要。 一个常见的例子是用于客户端验证的 jQuery Unobtrusive Validation 脚本。 如果视图需要脚本,它会将适当的 @section 脚本添加到 Razor 标记。</p> </blockquote> <p>你可能会注意到前面的清单定义了一个 Layout 属性,即使它是一个布局本身,而不是一个视图。 这是完全可以接受的,并且可以让您创建嵌套的布局层次结构,如图 7.12 所示。</p> <p>如今,大多数网站都需要“响应式”,因此它们可以在各种设备上运行。 您通常不应该为此使用布局。 不要根据发出请求的设备为单个页面提供不同的布局。 相反,为所有设备提供相同的 HTML,并在客户端使用 CSS 来根据需要调整网页的显示。</p> <table> <thead> <tr> <th>图 7.12 可以嵌套多个布局以创建复杂的层次结构。 这允许您在基本布局中保留所有视图共有的元素,并将多个视图共有的布局提取到子布局中。</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175815387-703589827.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <p>布局文件和部分为构建复杂的 UI 提供了很大的灵活性,但它们最重要的用途之一是减少应用程序中的代码重复。 它们非常适合避免重复您需要为每个视图编写的内容。 但是当你发现你想在其他地方重用视图的一部分时呢? 对于这些情况,您有部分视图。</p> <h3 id="使用局部视图封装标记">使用局部视图封装标记</h3> <p>部分视图正是它们听起来的样子——它们是视图的一部分。 它们提供了一种将更大的视图分解成更小的、可重用的块的方法。 这对于通过将大视图拆分为多个部分视图来降低大视图的复杂性或允许您在另一个视图中重用视图的一部分都非常有用。</p> <p>大多数使用服务器端渲染的 Web 框架都有这种能力——Ruby on Rails 有局部视图,Django 有包含标签,Zend 有局部视图。 所有这些都以相同的方式工作,将通用代码提取到小的、可重用的模板中。 即使是客户端框架(如 Angular 和 Ember)使用的客户端模板引擎(如 Mustache 和 Handlebars)也具有类似的“局部视图”概念。</p> <p>再次考虑一个待办事项列表应用程序。 您可能会发现您有一个名为 <code>ViewToDo.cshtml</code> 的 Razor 页面,它显示具有给定 ID 的单个待办事项。 稍后,您将创建一个新的 Razor 页面,<code>RecentToDos.cshtml</code>,它显示五个最近的待办事项。 您可以创建一个名为 <code>_ToDo.cshtml</code> 的局部视图,而不是将代码从一个页面复制并粘贴到另一个页面,如下面的清单所示。</p> <blockquote> <p>清单 7.12 部分视图 _ToDo.cshtml 用于显示 ToDoItemViewModel</p> </blockquote> <pre><code class="language-html">@model ToDoItemViewModel <h2>@Model.Title</h2> <ul> @foreach (var task in Model.Tasks) { <li>@task</li> } </ul> </code></pre> <p>部分视图有点像没有 <code>PageModel</code> 和处理程序的 Razor Pages。 部分视图纯粹是关于呈现 HTML 的一小部分,而不是处理请求、模型绑定和验证以及调用应用程序模型。 它们非常适合封装您需要在多个 Razor 页面上生成的少量可用 HTML。</p> <p><code>ViewToDo.cshtml</code> 和 <code>RecentToDos.cshtml</code> Razor 页面都可以呈现 <code>_ToDo.cshtml</code> 部分视图,该视图处理为单个类生成 HTML。 部分视图使用 <code><partial /></code> 标签助手渲染,提供要渲染的部分视图的名称和要渲染的数据(模型)。 例如,<code>RecentToDos.cshtml</code> 视图可以实现这一点,如下面的清单所示。</p> <blockquote> <p>清单 7.13 从 Razor 页面渲染部分视图</p> </blockquote> <pre><code class="language-csharp">@page //这是一个 Razor 页面,因此它使用 @page 指令。 部分视图不使用@page。 @model RecentToDoListModel //PageModel 包含要呈现的最近项目的列表。 //循环浏览最近的项目。 todo 是一个 ToDoItemViewModel,根据部分视图的要求。 @foreach(var todo in Model.RecentItems) { //使用部分标签助手渲染 _ToDo 部分视图,传入模型进行渲染。 <partial name="_ToDo" model="todo" /> } </code></pre> <p>当您在不提供绝对路径或文件扩展名的情况下渲染部分视图时,例如清单 7.13 中的 _ToDo,框架会尝试通过搜索 Pages 文件夹来定位视图,从调用它的 Razor 页面开始。 例如,如果您的 Razor 页面位于 Pages/Agenda/ToDos/RecentToDos.chstml,则框架将在以下位置查找名为 _ToDo.chstml 的文件:</p> <pre><code>Pages/Agenda/ToDos/ (the current Razor Page’s folder) Pages/Agenda/ Pages/ Pages/Shared/ Views/Shared/ </code></pre> <p>将选择包含名为 _ToDo.cshtml 的文件的第一个位置。 如果在引用局部视图时包含 .cshtml 文件扩展名,则框架将仅在当前 Razor 页面的文件夹中查找。 此外,如果您提供部分的绝对路径,例如 /Pages/Agenda/ToDo.cshtml,那是框架唯一可以查看的位置。</p> <blockquote> <p>与布局一样,部分视图通常以前导下划线命名。</p> </blockquote> <p>局部视图中包含的 Razor 代码几乎与标准视图相同。 主要区别在于部分视图仅从其他视图中调用。 另一个区别是局部视图在执行时不会运行 <code>_ViewStart.cshtml</code>,您很快就会看到。</p> <p>局部视图并不是减少视图模板中重复的唯一方法。 Razor 还允许您将常见元素(例如命名空间声明和布局配置)提取到集中式文件中。 在下一节中,您将看到如何使用这些文件来清理您的模板。</p> <h3 id="使用-_viewstart-和-_viewimports-在每个视图上运行代码">使用 <code>_ViewStart</code> 和 <code>_ViewImports</code> 在每个视图上运行代码</h3> <p>由于视图的性质,您不可避免地会发现自己重复编写某些内容。如果您的所有视图都使用相同的布局,那么在每个页面的顶部添加以下代码感觉有点多余:</p> <pre><code class="language-html">@{ Layout = "_Layout"; } </code></pre> <p>同样,如果您发现需要在 Razor 视图中引用来自不同命名空间的对象,那么必须将 @using WebApplication1.Models 添加到每个页面的顶部可能会很麻烦。 值得庆幸的是,ASP.NET Core 包含两种处理这些常见任务的机制:_ViewImports.cshtml 和 _ViewStart.cshtml。</p> <h4 id="使用-_viewimports-导入常用指令">使用 _VIEWIMPORTS 导入常用指令</h4> <p>_ViewImports.cshtml 文件包含将插入每个视图顶部的指令。 这包括你已经看到的 @using 和 @model 语句之类的东西——基本上是任何 Razor 指令。 为避免向每个视图添加 using 语句,您可以将其包含在 _ViewImports.cshtml 中,而不是您的 Razor 页面中。</p> <blockquote> <p>清单 7.14 导入额外命名空间的典型 _ViewImports.cshtml 文件</p> </blockquote> <pre><code class="language-csharp">@using WebApplication1 @using WebApplication1.Pages //应用程序的默认命名空间和 Pages 文件夹 @using WebApplication1.Models //添加此指令以避免将其放置在每个视图中。 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers //使标签助手在您的视图中可用,默认添加 </code></pre> <p><code>_ViewImports.cshtml</code> 文件可以放在任何文件夹中,它将应用于该文件夹中的所有视图和子文件夹。 通常,它被放置在根 <code>Pages</code> 文件夹中,以便它适用于应用程序中的每个 Razor 页面和局部视图。</p> <p>请务必注意,您应该只将 Razor 指令放在 <code>_ViewImports.cshtml</code> 中——不能将任何旧的 C# 放在其中。 正如您在前面的清单中看到的,这仅限于诸如 <code>@using</code> 或 <code>@addTagHelper</code> 指令之类的东西,您将在下一章中了解这些指令。 如果您想在应用程序中每个视图的开头运行一些任意 C#,例如设置 <code>Layout</code> 属性,您应该改用 <code>_ViewStart.cshtml</code> 文件。</p> <h4 id="使用-_viewstart-为每个视图运行代码">使用 _VIEWSTART 为每个视图运行代码</h4> <p>通过将 <code>_ViewStart.cshtml</code> 文件添加到应用程序中的 <code>Pages</code> 文件夹,您可以轻松地在每个 Razor 页面的开头运行通用代码。 该文件可以包含任何 Razor 代码,但它通常用于设置应用程序中所有页面的布局,如下面的清单所示。 然后,您可以从使用默认布局的所有页面中省略 <code>Layout</code> 语句。 如果视图需要使用非默认布局,您可以通过在 Razor 页面本身中设置值来覆盖它。</p> <blockquote> <p>清单 7.15 一个典型的 _ViewStart.cshtml 文件设置默认布局</p> </blockquote> <pre><code class="language-csharp">@{ Layout = "_Layout"; } </code></pre> <p>_<code>ViewStart.cshtml</code> 文件中的任何代码都会在视图执行之前运行。 请注意,<code>_ViewStart.cshtml</code> 仅针对 Razor 页面视图运行 - 它不适用于布局或部分视图。另请注意,这些特殊 Razor 文件的名称是强制执行的,而不是您可以更改的约定。</p> <blockquote> <p>您必须使用名称 <code>_ViewStart.cshtml</code> 和 <code>_ViewImports.cshtml</code> 以便 Razor 引擎正确定位和执行它们。 要将它们应用到应用程序的所有页面,请将它们添加到 Pages 文件夹的根目录,而不是 Shared 子文件夹。</p> </blockquote> <p>您可以通过将其他 _ViewStart.cshtml 或 _ViewImports.cshtml 文件包含在 Pages 的子文件夹中来为您的视图子集运行。 子文件夹中的文件将在根 Pages 文件夹中的文件之后运行。</p> <p>在本章中,我重点介绍了将 Razor 视图与 Razor Page 框架一起使用,因为如果你正在创建服务器端呈现的 ASP.NET Core 应用程序,我建议使用这种方法。 然而,正如我在第 4 章中所描述的,在某些情况下您可能想要使用 MVC 控制器。 在本章的最后一节中,我们将了解如何从 MVC 控制器操作中渲染 Razor 视图,以及框架如何定位要渲染的正确 Razor 视图。</p> <h2 id="从-mvc-控制器中选择视图">从 MVC 控制器中选择视图</h2> <p>如果您遵循我在第 3 章中的建议,您应该在服务器端呈现的应用程序中使用 Razor Pages,而不是在 1.x 和 2.x 版本中常见的 MVC 控制器。 Razor Pages 提供的一大优势是将 Razor 视图与关联的页面处理程序紧密耦合,而不必在解决方案中的多个文件夹之间导航。</p> <p>如果由于某种原因您确实需要使用 MVC 控制器而不是 Razor Pages,那么了解在执行操作方法后如何选择要呈现的视图非常重要。 图 7.13 显示了这个过程的放大视图,就在动作调用应用程序模型并接收到一些数据之后。</p> <table> <thead> <tr> <th>图 7.13 使用 ViewResult 从 MVC 控制器生成 HTML 的过程。 这与 Razor 页面的过程非常相似。 主要区别在于,对于 Razor Pages,视图是 Razor Page 不可分割的一部分; 对于 MVC 控制器,视图必须位于运行时。</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175832172-1869437733.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <p>这个图的某些部分应该很熟悉——它是第 3 章中图 3.6 的下半部分(有几个补充),是图 7.1 的 MVC 等价物。 它显示 MVC 控制器操作方法使用 <code>ViewResult</code> 对象来指示应该呈现 Razor 视图。 这个 <code>ViewResult</code> 包含要渲染的 Razor 视图模板的名称和一个视图模型,一个包含要渲染的数据的任意 POCO 类。</p> <blockquote> <p>我在第 3 章讨论了 <code>ViewResults</code>。它们是 Razor Page 的 <code>PageResult</code> 的 MVC 等价物。 主要区别在于 ViewResult 包含要呈现的视图名称和要传递给视图模板的模型,而 <code>PageResult</code> 始终呈现 Razor 页面的关联视图并将 PageModel 传递给视图模板。</p> </blockquote> <p>从操作方法返回 <code>ViewResult</code> 后,控制流返回到 MVC 框架,该框架使用一系列启发式方法根据提供的模板名称定位视图。 找到 Razor 视图模板后,Razor 引擎将视图模型从 <code>ViewResult</code> 传递到视图并执行模板以生成最终的 HTML。 最后一步,呈现 HTML,本质上与 Razor 页面的过程相同。</p> <p>您在第 4 章中看到了如何创建控制器,在本节中您将看到如何创建视图和 ViewResult 对象以及如何指定要呈现的模板。 您可以通过在解决方案资源管理器中右键单击 MVC 应用程序并选择 Add > New Item,然后从对话框中选择 Razor View,将新的视图模板添加到 Visual Studio 中的应用程序,如图 7.14 所示。 如果您不使用 Visual Studio,请在 Views 文件夹中创建一个带有文件扩展名 .cshtml 的空白新文件</p> <p>创建视图模板后,您现在需要调用它。 在大多数情况下,您不会直接在操作方法中创建 ViewResult。 相反,您将使用 Controller 基类上的 View 帮助器方法之一。 这些辅助方法简化了视图模型的传递和视图模板的选择,但它们并没有什么神奇之处——它们所做的只是创建 ViewResult 对象。</p> <table> <thead> <tr> <th>图 7.14 添加新项目对话框。 选择 Razor View - Empty 将向您的应用程序添加一个新的 Razor 视图模板文件。</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175845994-873845750.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <p>在最简单的情况下,您可以不带任何参数调用 View 方法,如下面的清单所示。 这个辅助方法返回一个 ViewResult ,它将使用约定来查找要呈现的视图模板,并且在执行视图时不会提供视图模型。</p> <blockquote> <p>清单 7.16 使用默认约定从操作方法返回 ViewResult</p> </blockquote> <pre><code class="language-csharp">public class HomeController : Controller //从 Controller 基类继承使得 View 辅助方法可用。 { public IActionResult Index() { return View(); //View 帮助器方法返回一个 ViewResult。 } } </code></pre> <p>在此示例中,View 帮助器方法返回 ViewResult 而不指定要运行的模板的名称。 相反,要使用的模板名称基于控制器的名称和操作方法的名称。 假设控制器名为 HomeController,方法名为 Index,默认情况下,Razor 模板引擎会在 Views/Home/Index.cshtml 位置查找模板,如图 7.15 所示。</p> <table> <thead> <tr> <th>图 7.15 视图文件根据命名约定在运行时定位。 Razor 视图文件驻留在基于关联 MVC 控制器名称的文件夹中,并以请求它们的操作方法的名称命名。 任何控制器都可以使用 Shared 文件夹中的视图。</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175900436-1655835554.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <p>这是在 MVC 中使用约定来减少您必须编写的样板数量的另一种情况。 与往常一样,约定是可选的。 您还可以将要作为字符串运行的模板的名称显式传递给 View 方法。 例如,如果 Index 方法返回 View("ListView"),则模板引擎将查找名为 ListView.cshtml 的模板。 您甚至可以指定视图文件的完整路径,相对于应用程序的根文件夹,例如 View("Views/global.cshtml"),它将在 Views/global.chtml 位置查找模板。</p> <blockquote> <p>指定视图的绝对路径时,必须在路径中包含顶级视图文件夹和 .cshtml 文件扩展名。 这类似于定位局部视图模板的规则。</p> </blockquote> <p>定位 MVC Razor 视图的过程与定位要渲染的局部视图的过程非常相似,如您在第 7.4 节中所见。 框架在多个位置搜索以找到请求的视图。 不同之处在于,对于 Razor 页面,搜索过程仅发生在部分视图渲染中,因为要渲染的主要 Razor 视图是已知的——它是 Razor 页面的视图模板。</p> <p>图 7.16 显示了 MVC 框架用于在从 MVC 控制器返回 ViewResult 时找到正确的 View 模板以执行的完整过程。 可能有多个模板符合条件,例如 Home 和 Shared 文件夹中都存在 Index.chstml 文件。 与定位局部视图的规则类似,引擎将使用它找到的第一个模板。</p> <blockquote> <p>您可以在初始配置期间修改所有这些约定,包括图 7.16 中所示的算法。 事实上,如果需要,您可以替换整个 Razor 模板引擎,但这超出了本书的范围。</p> </blockquote> <table> <thead> <tr> <th>图 7.16 描述 Razor 模板引擎如何定位要执行的正确视图模板的流程图。 避免此图的复杂性是我建议尽可能使用 Razor Pages 的原因之一!</th> </tr> </thead> <tbody> <tr> <td><img src="https://img2022.cnblogs.com/blog/1469373/202209/1469373-20220903175912777-1449630988.png" alt="image" loading="lazy"></td> </tr> </tbody> </table> <p>您可能会发现显式提供要在控制器中呈现的视图文件的名称很诱人; 如果是这样,我会鼓励你克服这种冲动。 如果您按原样接受约定并顺其自然,您将拥有更简单的时间。 这延伸到其他任何查看您的代码的人; 如果您遵守标准约定,那么当他们查看您的应用程序时,您会感到非常熟悉。 那只能是好事!</p> <p>除了提供视图模板名称外,您还可以传递一个对象来充当 Razor 视图的视图模型。 此对象应与视图的 @model 指令中指定的类型匹配,并且访问方式与 Razor 页面完全相同; 使用模型属性。 以下清单显示了将视图模型传递给视图的两个示例。</p> <blockquote> <p>清单 7.17 使用默认约定从操作方法返回 ViewResult</p> </blockquote> <pre><code class="language-csharp">public class ToDoController : Controller { public IActionResult Index() { //创建视图模型的实例以传递给 Razor 视图。 var listViewModel = new ToDoListModel(); return View(listViewModel); //视图模型作为参数传递给 View。 } public IActionResult View(int id) { var viewModel = new ViewToDoModel(); return View("ViewToDo", viewModel); //您可以在提供视图模型的同时提供视图模板名称。 } } </code></pre> <p>找到 Razor 视图模板后,将使用您在本章中看到的 Razor 语法呈现视图。 您可以使用您已经看到的所有功能——例如布局、局部视图、<code>_ViewImports</code> 和 <code>_ViewStart</code>。 从 Razor 视图的角度来看,Razor Pages 视图和 MVC Razor 视图之间没有区别。</p> <p>我们第一次使用 Razor 模板引擎渲染 HTML 到此结束。 在下一章中,您将了解 Tag Helpers 以及如何使用它们来构建 HTML 表单,这是现代 Web 应用程序的主要内容。 Tag Helpers 是 ASP.NET Core 中 Razor 与以前版本相比最大的改进之一,因此掌握它们将使编辑视图成为一种更愉快的整体体验!</p> <h2 id="总结">总结</h2> <ul> <li>在 MVC 设计模式中,视图负责为您的应用程序生成 UI。</li> <li>Razor 是一种模板语言,它允许您使用 HTML 和 C# 的混合生成动态 HTML。</li> <li>HTML 表单是从浏览器向服务器发送数据的标准方法。 您可以使用 Tag Helpers 轻松生成这些表单。</li> <li>Razor Pages 可以通过在 PageModel 上设置公共属性将强类型数据传递到 Razor 视图。 要访问视图模型的属性,视图应该使用 @model 指令声明模型类型。</li> <li>页面处理程序可以使用 ViewData 字典将键值对传递给视图。</li> <li>Razor 表达式使用 @ 或 @() 将 C# 值呈现到 HTML 输出。 使用 Razor 表达式时,不需要在语句后包含分号。</li> <li>Razor 代码块,使用@{} 定义,执行 C# 不输出 HTML。Razor 代码块中的 C# 必须是完整的语句,因此必须包含分号。</li> <li>循环和条件可用于在模板中轻松生成动态 HTML,但最好限制 if 语句的数量,以使您的视图易于阅读。</li> <li>如果您需要将字符串呈现为原始 HTML,您可以使用 Html.Raw,但要谨慎使用 - 呈现原始用户输入可能会在您的应用程序中产生安全漏洞。</li> <li>Tag Helpers 允许您将数据模型绑定到 HTML 元素,从而更容易生成动态 HTML,同时保持编辑器友好。</li> <li>您可以在布局中放置多个视图共有的 HTML。 布局将在调用 @RenderBody 的位置呈现来自子视图的任何内容。</li> <li>将常用的 Razor 代码片段封装在局部视图中。 可以使用 <partial /> 标记呈现局部视图。</li> <li>_ViewImports.cshtml 可用于在每个视图中包含常用指令,例如 @using 语句。</li> <li>_ViewStart.cshtml 在每个 Razor Page 执行之前调用,可用于执行所有 Razor Page 共有的代码,例如设置默认布局页面。 它不适用于布局或局部视图。</li> <li>_ViewImports.cshtml 和 _ViewStart.cshtml 是分层的——根文件夹中的文件首先执行,然后是控制器特定视图文件夹中的文件。</li> <li>控制器可以通过返回 ViewResult 来调用 Razor 视图。 这可能包含要渲染的视图的名称,以及渲染视图时使用的可选视图模型对象。 如果未提供视图名称,则使用约定选择视图。</li> <li>按照约定,MVC Razor 视图的名称与调用它们的操作方法相同。 它们要么位于与操作方法的控制器同名的文件夹中,要么位于 Shared 文件夹中。</li> </ul>
  • posted @ 2022-09-03 17:59  F(x)_King  阅读(322)  评论(0编辑  收藏  举报