本章包括(请点击这里阅读其他章节

• 创建 Razor 视图以向用户显示 HTML
• 使用 C# 和 Razor 标记语法动态生成 HTML
• 使用布局和局部视图重用公共代码

Razor Pages PageModel、页面处理程序和Razor视图中涉及的术语很容易混淆,特别是其中一些术语描述了具体的特性,而其他术语描述了模式和概念。在前几章中,我们已经详细介绍了所有这些术语,但重要的是要记住它们:

• Razor Pages——Razor Pages通常指基于页面的范例,它使用Razor视图将路由、模型绑定和HTML生成结合在一起。
• Razor Page——Razor Page代表一个页面或“端点”。它通常由两个文件组成:一个包含Razor视图的.cshtml文件,一个包含页面PageModel的.cshtml.cs文件。
• PageModel——Razor Page的PageModel是大多数操作发生的地方。它是定义页面绑定模型的地方,页面从传入请求中提取数据。它也是定义页面的页面处理程序的地方。
• Page Handler——每个RazorPage通常处理一个路由,但它可以处理多个HTTP谓词,如GET和POST。每个页面处理程序通常处理一个HTTP谓词。
• Razor view——Razor views(也称为Razor模板)用于生成HTML。它们通常用于RazorPage的最后阶段,以生成HTML响应并发送回用户。

在前面的四章中,我已经介绍了Razor Pages的整个体系,包括MVC设计模式、Razor Page PageModel、页面处理程序、路由和绑定模型。本章介绍了MVC模式的最后一部分,使用视图生成交付给用户浏览器的HTML。

在ASP.NET Core中,视图通常使用Razor标记语法(有时被描述为模板语言)创建,该语法使用HTML和C#的混合来生成最终的HTML。本章介绍Razor的一些功能,以及如何使用它为应用程序构建视图模板。一般来说,用户将与应用程序进行两种交互:他们将读取应用程序显示的数据,并将数据或命令发送回应用程序。Razor语言包含许多构造,使构建这两种类型的应用程序变得简单。

在显示数据时,您可以使用Razor语言轻松地将静态HTML与PageModel中的值相结合。Razor可以使用C#作为一种控制机制,因此添加条件元素和循环是简单的,单靠HTML是无法实现的。

向Web应用程序发送数据的常规方法是HTML表单。实际上,您构建的每个动态应用程序都将使用表单;有些应用程序几乎只不过是表单!ASP.NET Core和Razor模板语言包括许多标记帮助程序,使生成HTML表单变得容易。

注:在下一节中,您将简要了解Tag Helper,但我将在下一章中详细探讨它们。

在本章中,我们将主要关注使用Razor显示数据和生成HTML,而不是创建表单。您将看到如何将PageModel中的值呈现为HTML,以及如何使用C#控制生成的输出。最后,您将学习如何将视图的公共元素提取到称为布局和局部视图的子视图中,以及如何组合它们以创建最终的HTML页面。

7.1 视图:渲染用户界面

在本节中,我将简要介绍如何使用Razor视图渲染HTML。我们将回顾Razor Pages使用的MVC设计模式,以及视图的适用范围。然后,我将向您介绍Razor语法如何允许您混合C#和HTML来生成动态UI。

正如您在前面关于MVC设计模式的章节中所知,Razor Page的页面处理程序的任务是选择返回给客户端的内容。例如,如果您正在开发一个待办事项列表应用程序,请想象一个查看特定待办事项的请求,如图7.1所示。

图7.1使用ASP.NET Core RazorPages处理待办事项列表项请求。页面处理程序构建视图所需的数据,并将其作为PageModel上的属性公开。视图仅基于提供的数据生成HTML;它不需要知道数据来自何处。

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

  1. 中间件管道接收请求,路由中间件确定要调用的端点(在本例中为ToDo文件夹中的ViewRazorPage)。
  2. 模型绑定器(RazorPages框架的一部分)使用请求为页面构建绑定模型,正如您在上一章中看到的那样。绑定模型被设置为RazorPage上的属性,或者在执行处理程序时作为参数传递给页面处理程序方法。页面处理程序检查您是否为待办事项传递了有效的id,从而使请求有效。
  3. 假设一切看起来都很好,页面处理程序调用组成应用程序模型的各种服务。这可能会从数据库或文件系统加载有关待办事项的详细信息,并将其返回给处理程序。作为此过程的一部分,应用程序模型或页面处理程序本身生成要传递给视图的值,并将其设置为RazorPage PageModel上的属性。
    一旦执行了页面处理程序,PageModel应该包含呈现视图所需的所有数据。在本例中,它包含关于待办事项本身的详细信息,但也可能包含其他数据:您还剩多少待办事项、今天是否有待办事项、用户名等等——控制如何为请求生成最终UI的所有数据。
  4.  Razor视图模板使用PageModel生成最终响应,并通过中间件管道将其返回给用户。

在整个MVC讨论中,一个共同的主线是MVC带来的关注点的分离,当涉及到您的视图时,这没有什么不同。在应用程序模型或控制器操作中直接生成HTML是很容易的,但通常我们会通过视图将责任委托给单个组件。

但更重要的是,您还可以使用PageModel上的属性将构建视图所需的数据与构建视图的过程分开。这些属性应包含视图生成最终输出所需的所有动态数据。

提示:视图不应调用PageModel上的方法——视图通常只应访问已经收集并作为属性公开的数据。

RazorPage处理程序声明Razor视图应该通过返回PageResult(或返回void)来呈现,如第4章所示。RazorPages基础结构执行与给定RazorPage关联的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">    <!-- 普通HTML将发送到浏览器,不会发生变化。-->
    <div class="col-md-6">
        <form method="post">
            <div class="form-group">
                <label asp-for="NewUser"></label>    <!--标记帮助程序附加到HTML元素以创建表单。-->
                <input class="form-control" asp-for="NewUser" />
                <span asp-validation-for="NewUser"></span>
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success">Add</button>
            </div>
        </form>
    </div>
</div>

<h4>Number of users: @Model.ExistingUsers.Count</h4>    <!--值可以从C#对象写入HTML。-->
<div class="row">
    <div class="col-md-6">
        <ul class="list-group">
            @foreach (var user in Model.ExistingUsers)    <!--类似for循环的C#构造可以在Razor中使用。-->
            {
                <li class="list-group-item d-flex justify-content-between">
                    <span>@user</span>
                    <a class="btn btn-info" asp-page="ViewUser" asp-route-userName="@user">View</a>    <!--标记帮助程序也可以在表单之外使用,以帮助其他HTML生成。-->
                </li>
            }
        </ul>
    </div>
</div>

此示例演示了各种Razor功能。表面看起来是一种混合的HTML,一部分未经修改地写入响应输出,还有各种C#结构用于动态生成HTML。此外,您可以看到几个标记帮助程序。这些看起来像以"asp-"开头的普通HTML属性,但它们是Razor语言的一部分。他们可以自定义附加的HTML元素,改变其呈现方式。它们使构建HTML表单比其他表单简单得多。不要担心,如果这个模板目前有点难以理解;我们将在您完成本章和下一章时将其全部分解。

RazorPages是在构建应用程序时编译的。在后台,它们只是应用程序中的另一个C#类。还可以启用RazorPages的运行时编译。这允许您在应用程序运行时修改RazorPages,而无需显式停止和重建。这在本地开发时很方便,但在部署到生产环境时最好避免。

注意:像ASP.NET Core中的大多数东西一样,可以更换Razor模板引擎,并用自己的服务器端渲染引擎替换它。你不能用Angular或React这样的客户端框架取代Razor。如果您想采用这种方法,您可以改用WebAPI。我将在第9章详细讨论WebAPI。

在下一节中,我们将更详细地了解Razor视图如何适合RazorPages框架,以及如何将RazorPage处理程序中的数据传递到Razor视图以帮助构建HTML响应。

7.2 创建Razor视图

在本节中,我们将了解Razor视图如何适应RazorPages框架。您将学习如何将数据从页面处理程序传递到Razor视图,以及如何使用这些数据生成动态HTML。

使用ASP.NET Core,每当您需要向用户显示HTML响应时,都应该使用视图来生成它。虽然可以直接从页面处理程序中生成字符串,该字符串将在浏览器中呈现为HTML,但这种方法不符合MVC分离的关注点,会让您很容易抓狂。

注意:某些中间件,如您在第3章中看到的WelcomePageMiddleware,可能会在不使用视图的情况下生成HTML响应,这在某些情况下是有意义的。但是RazorPage和MVC控制器应该始终使用视图生成HTML。

相反,通过依赖Razor视图生成响应,您可以访问各种功能,以及编辑器工具来提供帮助。本节简要介绍Razor视图、您可以使用它们做的事情以及向它们传递数据的各种方式。

7.2.1 Razor视图和代码隐藏

在本书中,您已经看到RazorPages通常由两个文件组成:

  • .cshtml文件,通常称为Razor视图。
  • .cshtml.cs文件,通常称为代码隐藏,其中包含PageModel。

Razor视图包含@page指令,这使其成为RazorPage,如第4章所示。如果没有此指令,RazorPages框架将不会将请求路由到页面,并且出于大多数目的,该文件将被忽略。

定义:指令是Razor文件中的一条语句,用于更改模板的解析或编译方式。另一个常见的指令是@using newNamespace指令,它使newNamespace命名空间中的对象可用。

cshtml.cs文件后面的代码包含关联RazorPage的PageModel。它包含响应请求的页面处理程序,RazorPage通常在这里与应用程序的其他部分交互。

尽管.cshtml和.cshtml.cs文件共享相同的名称,例如ToDoItem.chtml和ToDoItem.shtml.cs,但将它们链接在一起的不是文件名。但如果不是按文件名,RazorPages框架如何知道哪个PageModel与给定的RazorPage视图文件关联?

在每个RazorPage的顶部,就在@page指令之后,有一个带有Type的@model方向,指示哪个PageModel与Razor视图关联。例如,以下指令指示ToDoItemModel是与RazorPage关联的PageModel:

@page
@model ToDoItemModel

一旦请求被路由到RazorPage,如我们在第5章中所述,框架就会查找@model指令来决定使用哪个PageModel。根据所选的PageModel,它绑定到PageModel中标记有[BindProperty]特性的所有属性(如我们在第6章中所述),并执行相应的页面处理程序(基于请求的HTTP动词)。

注:从技术上讲,PageModel和@model指令是可选的。如果不指定PageModel,框架将执行默认页面处理程序,如第5章所示。还可以将.cshtml和.cshtml.cs文件合并为一个.cshhtml文件。在实践中,这两种方法都不是很常见,即使是对于简单的页面,但如果遇到这种情况,也需要注意。

除了@page和@model指令之外,Razor视图文件还包含执行Razor模板以生成HTML响应。

7.2.2 Razor模板简介

Razor视图模板包含HTML和C#代码的混合体,它们相互穿插。HTML标记可以让您轻松地准确描述应该发送到浏览器的内容,而C#代码可以用于动态更改呈现的内容。例如,下面的列表显示了Razor渲染字符串列表的示例,表示待办事项。

清单7.2 用于呈现字符串列表的Razor模板

@page 
@{
    var tasks = new List<string> { "Buy milk", "Buy eggs", "Buy bread" };    <!--可以在模板中执行任意C#。变量在整个页面中保持。-->
}
<h1>Tasks to complete</h1>    <!--标准HTML标记将以不变的方式呈现到输出中。-->
<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文件一样。

注:Razor语法能够知道何时在HTML和C#之间切换,这一能力有时既不可思议又令人愤怒。我在第7.3节中讨论了如何控制这种过渡的细节。

除了HTML,您还可以在其中看到许多C#语句。例如,能够使用for循环而不必显式写出每个<li>元素的优点应该是不言自明的。在下一节中,我将深入了解Razor的更多C#特性。当呈现时,清单7.2中的模板将生成以下HTML。

清单7.3 呈现Razor模板生成的HTML输出

<h1>Tasks to complete</h1>    <!--Razor模板中的HTML直接写入输出。-->
<ul>
     <!--<li>元素是根据数据动态生成的。-->
    <li>0 - Buy milk</li>
    <li>1 - Buy eggs</li>
    <li>2 - Buy bread</li>
</ul>     <!--Razor模板中的HTML直接写入输出。-->

如您所见,Razor模板渲染后的最终输出是简单的HTML。这里没有什么复杂的东西,只有可以发送到浏览器并呈现的HTML标记。图7.3显示了浏览器如何呈现它。

图7.3 Razor模板可用于在运行时从C#对象动态生成HTML。在本例中,for循环用于创建重复的HTML<li>元素。

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

7.2.3 向视图传递数据

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

  • PageModel属性——通常应该公开需要在PageModel上显示为属性的任何数据。任何特定于关联Razor视图的数据都应该以这种方式公开。PageModel对象在呈现时在视图中可用,您很快就会看到。
  • ViewData——这是一个带有字符串键的对象字典,可用于将任意数据从页面处理程序传递到视图。此外,它允许您将数据传递到_layout文件,如第7.4节所示。这是使用ViewData而不是在PageModel上设置属性的主要原因。
  • HttpContext——从技术上讲,HttpContext对象在页面处理程序和Razor视图中都可用,因此您可以使用它在它们之间传输数据。但不要——没有必要使用其他方法。
  • @注入服务——您可以使用依赖注入使服务在视图中可用,尽管这通常应该非常谨慎地使用。我在第10章中描述了依赖注入和@inject指令。

从页面处理程序向视图传递数据的最佳方法是使用PageModel上的属性。属性本身没有什么特别之处;您可以在那里存储任何东西来保存所需的数据。

注:许多框架都有绑定UI组件的数据上下文的概念。PageModel是一个类似的概念,因为它包含要在UI中显示的值,但绑定只有一个方向;PageModel向UI提供值,一旦UI构建并作为响应发送,PageModel就会被销毁。

正如我在7.2.1节中所描述的,Razor视图顶部的@model指令描述了与给定RazorPage关联的PageModel类型。与RazorPage关联的PageModel包含一个或多个页面处理程序,并将数据公开为属性以供Razor视图使用。

清单7.4 将数据作为PageModel上的属性公开

public class ToDoItemModel : PageModel    //PageModel在执行时传递给Razor视图。
{
    //可以从Razor视图访问公共属性。
    public List<string> Tasks { get; set; } 
    public string Title { get; set; }

    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属性中提供的字符串,从而生成<h1>Tasks For today</h1>HTML。

提示:@model指令应该位于视图的顶部,正好在@page指令之后,并且它有一个小写的m。model属性可以在视图中的任何位置访问,并且有一个大写的M。

在绝大多数情况下,在PageModel上使用公共属性是一种方法;它是在页面处理程序和视图之间传递数据的标准机制。但在某些情况下,PageModel上的属性可能不是最合适的。当您想要在视图布局之间传递数据时,通常会出现这种情况(您将在第7.4节中看到这是如何工作的)。

一个常见的例子是页面的标题。您需要为应用程序中的每个页面提供一个标题,这样您就可以创建一个带有title属性的基类,并使每个PageModel从中继承。但这非常麻烦,因此这种情况的常见方法是使用ViewData集合传递数据。

事实上,默认情况下,标准RazorPage模板通过在视图本身的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]
    public string Title { get; set; }    //用[ViewData]属性标记的属性在ViewData中设置。

    public void OnGet()
    {
        Title = "Home Page";     //ViewData[“Title”]的值将设置为“Home Page”。
        ViewData["Subtitle"] = "Welcome";    //您可以直接在ViewData字典中设置键。
    }
}

您可以以与之前相同的方式显示模板中的值:

<h1>@ViewData["Title"]</h3>
<h2>@ViewData["Subtitle"]</h3>

提示:我不觉得[ViewData]属性特别有用,但它是另一个值得注意的特性。相反,我为任何ViewData键创建一组全局静态常量,并引用这些常量,而不是重复键入“Title”。您将获得值的IntelliSense,它们将是重构安全的,并且您将避免难以识别的错误。

正如我之前提到的,除了PageModel属性和ViewData之外,还有其他机制可以用来传递数据,但这两个机制是我个人使用的唯一机制,因为您可以使用它们做任何需要的事情。作为提醒,请尽可能使用PageModel属性,因为您可以从强类型和IntelliSense中受益。对于需要在Razor视图之外访问的值,只能回退到ViewData。

您已经对Razor模板中可用的功能略知一二,但在下一节中,我们将深入了解一些可用的C#功能。

7.3 使用Razor创建动态网页

您可能很高兴知道,在Razor语法中,您可以在C#中做任何事情。.cshtml文件会被编译成普通的C#代码(带有原始HTML部分的字符串),因此可以创建任何您需要的奇怪和奇妙的行为!

尽管如此,仅仅因为你能做一些事情并不意味着你应该做。如果您使文件尽可能简单,那么您会发现使用和维护文件要容易得多。几乎所有编程都是如此,但我发现Razor模板尤其如此。

本节介绍您可以使用的一些更常见的C#构造。如果您发现需要实现一些更奇特的东西,请参阅Razor语法文档http://mng.bz/opj2.

7.3.1 在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的开始位置。

这两种方法被称为Razor表达式,其中C#被求值并直接写入HTML输出。

提示:如果要编写文字@字符,而不是C#表达式,请使用两个@字符:@@。

有时您需要执行一些C#,但不需要输出值。我们在ViewData中设置值时使用了此技术:

@{
  ViewData["Title"] = "Home Page";
}

这个示例演示了Razor代码块,它是由@{ }结构标识的普通C#代码。这里没有向HTML输出写入任何内容;所有这些都是编译的,就像您在任何其他正常的C#文件中编写一样。

提示:在代码块中执行代码时,它必须是有效的C#,因此需要添加分号。相反,当您使用Razor表达式将值直接写入响应时,您不需要它们。如果您的输出HTML意外中断,请注意是否缺少或错误的额外分号。

Razor表达式是将数据从页面模型写入HTML输出的最常见方法之一。在下一章中,您将看到另一种方法,即使用Tag Helpers。然而,正如您将在下一节中看到的,Razor的功能远远不止于此,您将学习如何在模板中包含传统的C#结构。

7.3.2 向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指令指示model中PageModel的类型。-->
<div>
    @if (Model.IsComplete)    <!--if语名在运行时检查PageModel的IsComplete属性的值。-->
    {
        <strong>Well done, you’re all done!</strong>
    }
    else
    {
        <strong>The following tasks remain:</strong>
        <ul>
            @foreach (var task in Model.Tasks)    <!--foreach结构将为Model.Tasks中的每个任务生成一次<li>元素。-->
            {
                <li>@task</li>    <!--Razor表达式用于将任务写入HTML输出。-->
            }
        </ul>
    }
</div>

这段代码绝对符合混合C#和HTML的承诺!有一些传统的C#控件结构,比如if和foreach,你可以在任何正常的程序中使用,中间穿插着你想要发送到浏览器的HTML标记。正如您所看到的,@符号用于指示何时启动控制语句,但您通常会让Razor模板推断何时在HTML和C#之间来回切换。

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

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

IntelliSense和工具支持
在书中,C#和HTML的混合看起来很难读懂,这是一个合理的抱怨。这也是试图使Razor模板尽可能简单的另一个有效理由。
幸运的是,如果您使用的是像Visual Studio或Visual Studio Code这样的编辑器,该工具会有所帮助。如图所示,代码的C#部分带有阴影,有助于将其与周围的HTML区分开来。

Visual Studio对代码的C#区域进行着色,并突出显示C#转换为HTML的@符号。这使得Razor模板更容易阅读。
尽管使用循环和条件的能力非常强大(这是Razor相对于静态HTML的优势之一),但它们也增加了视图的复杂性。尽量限制视图中的逻辑量,使其尽可能易于理解和维护。

ASP.NET Core团队的一个常见比喻是,他们试图确保您在构建应用程序时“跌入成功的深渊”。这是指默认情况下,做某事最简单的方式应该是正确的方式。这是一个伟大的哲学,因为它意味着如果你遵循标准方法,你不应该被安全问题所困扰。然而,有时,您可能需要跨过安全栏杆;一个常见的用例是当您需要将C#对象中包含的一些HTML呈现到输出时,如您将在下一节中看到的。

7.3.3 使用Raw渲染HTML

在前面的示例中,我们通过使用@task Razor表达式编写字符串任务,将任务列表呈现为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中。

Razor模板在将C#表达式写入输出流之前对其进行编码。这主要是出于安全原因;将任意字符串写入HTML可能会允许用户将恶意数据和JavaScript注入网站。因此,您在Razor模板中打印的C#变量将被写成HTML编码值。

在某些情况下,您可能需要将字符串中包含的HTML直接写入响应。如果你发现自己处于这种情况,首先,停止。你真的需要这样做吗?如果您正在编写的值是由用户输入的,或者是基于用户提供的值创建的,那么在您的网站中存在创建安全漏洞的严重风险。

如果确实需要将变量写入HTML流,可以使用视图页面上的HTML属性并调用Raw方法:

<li>@Html.Raw(task)</li>

使用这种方法,任务中的字符串将直接写入输出流,生成您最初想要的HTML,<li><strong>Check oil</strong></li>,呈现如图7.8所示。

图7.8 第二项“<strong>检查油<strong>”是使用Html.Raw()输出的,因此它没有经过Html编码。<strong>元素导致第二项以粗体显示。尽可能避免以这种方式使用Html.Raw(),因为这是一种安全风险。

警告:在用户输入中使用Html.Raw会产生安全风险,用户可以使用该风险将恶意代码注入您的网站。尽可能避免使用Html.Raw。

本节中显示的C#构造可能很有用,但它们会使模板更难阅读。通常更容易理解Razor模板的意图,这些模板主要是HTML标记,而不是C#。

在ASP.NET的早期版本中,这些构造,特别是Html助手属性,是生成动态标记的标准方法。通过在Html属性上使用各种HtmlHelper方法,您仍然可以在ASP.NET Core中使用此方法,但这些方法基本上已被一种更干净的技术所取代:Tag Helpers。

注:我将在下一章讨论标记帮助器,以及如何使用它们来构建HTML表单。

标记帮助程序是ASP.NET Core中Razor的一个新功能,但许多其他功能都是从ASP.NET的早期版本中继承的。在本章的下一节中,您将看到如何创建嵌套的Razor模板并使用部分视图来减少视图中的重复量。

7.4 布局、局部视图和 _ViewStart

在本节中,您将了解布局和局部视图,它们允许您提取公共代码以减少重复。这些文件使您更容易对HTML进行更改,同时影响多个页面。您还将学习如何使用_ViewStart和_ViewImports为每个RazorPage运行公共代码,以及如何在页面中包含可选部分。

每个HTML文档都需要一定数量的元素:<HTML>、<head>和<body>。此外,在应用程序的每个页面上经常会重复一些常见的部分,例如页眉和页脚,如图7.9所示。应用程序中的每个页面也可能引用相同的CSS和JavaScript文件。

图7.9 一个典型的web应用程序有一个基于块的布局,其中一些块是应用程序的每个页面所共有的。标题块在整个应用程序中可能是相同的,但是侧边栏可能只在一个部分中的页面中是相同的。应用程序中每个页面的正文内容都不同。

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

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

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

7.4.1 使用共享标记的布局

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

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

您通常会在许多不同的RazorPages中使用布局,因此它们通常放在Pages/Shared文件夹中。您可以随意命名它们,但有一个通用约定,即使用_Layout.cshtml作为应用程序中基本布局的文件名。这是Visual Studio和.NET CLI中RazorPage模板使用的默认名称。

提示:一个常见的惯例是在布局文件前加下划线(_),以将其与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是从视图向布局传递数据的标准机制。-->
        <link rel="stylesheet" href="~/css/site.css" />    <!--每个页面常见的元素,如CSS,通常都在布局中找到。-->
    </head>
    <body>
        @RenderBody()    <!--告诉模板引擎在何处插入子视图的内容-->
    </body>
</html>

如您所见,布局文件包括所需的元素,如<html>和<head>,以及您在每个页面上需要的元素,例如<title>和<link>。此示例还显示了在ViewData中存储页面标题的好处;布局可以在<title>元素中呈现它,以便它显示在浏览器的选项卡中,如图7.11所示。

图7.11<title>元素的内容用于命名用户浏览器中的选项卡,在本例中为Home Page。

注意:布局文件不是独立的RazorPages,不参与路由,因此它们不会以@page指令开头。

视图可以通过在Razor代码块中设置layout属性来指定要使用的布局文件。

清单7.8 从视图设置Layout属性

@{
    Layout = "_Layout";    <!--将页面布局设置为_layout.chtml。-->
    ViewData["Title"] = "Home Page";    <!--ViewData是将数据从Razor视图传递到布局的便捷方式。-->
}
<h1>@ViewData["Title"]</h1>    <!--要在布局内呈现的Razor视图中的内容-->
<p>This is the home page</p>

视图中的任何内容都将在布局中调用@Render-Body()的位置呈现生。结合前面的两个列表将生成以下HTML并发送给用户。

清单7.9 将视图与其布局组合后的渲染输出

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Home Page</title>    <!--视图中的ViewData集用于渲染布局。-->
        <link rel="stylesheet" href="/css/site.css" />
    </head>
    <body>
        <h1>Home Page</h1>    <!--RenderBody调用渲染视图的内容。-->
        <p>This is the home page</p>
    </body>
<html>

合理使用布局对于减少页面之间的重复非常有用。默认情况下,在调用@RenderBody时,布局仅提供一个可以从视图中渲染内容的位置。在限制性太强的情况下,可以使用节渲染内容。

7.4.2 使用节(Section)覆盖父布局

在应用程序中开始使用多个布局时,一个常见的要求是能够在布局中的多个位置呈现子视图中的内容。考虑使用两列的布局的情况。视图需要一种机制来表示“在左列中呈现此内容”和“在右列中呈现其他内容”。这是通过使用节实现的。

注意:请记住,本章中概述的所有功能都特定于Razor,这是一个服务器端渲染引擎。如果您使用客户端SPA框架来构建应用程序,您可能会以其他方式处理这些需求,可以在客户端代码中,也可以通过向Web API端点发出多个请求。

节提供了一种组织视图元素在布局中放置位置的方法。它们是在视图中使用@section定义定义的,如下面的列表所示,该列表定义了一个与主内容分离的侧边栏的HTML内容,位于一个名为sidebar的部分中。@section可以放在文件的任何地方,无论是顶部还是底部,只要方便。

清单7.10 在视图模板中定义节

@{
    Layout = "_TwoColumn";
}
@section Sidebar {
    <p>This is the sidebar content</p>    <!--大括号内的所有内容都是提要栏部分的一部分,而不是主体内容。-->
}
<p>This is the main content </p>    <!--任何不在@节内的内容都将由@RenderBody调用呈现。-->

通过调用@RenderSection()在父布局中渲染该节。这会将子部分中包含的内容呈现到布局中。节可以是必需的,也可以是可选的。如果需要,视图必须声明给定的@节;如果它们是可选的,可以省略它们,布局将跳过它们。跳过的部分不会出现在呈现的HTML中。下面的列表显示了一个布局,该布局具有一个名为Sidebar的必需部分和一个称为Scripts的可选部分。

清单7.11 在布局文件_TwoColumn.cshtml中呈现一个节

@{
    Layout = "_Layout";    <!--此布局嵌套在布局本身内部。-->
}
<div class="main-content"> 
    @RenderBody()    <!--呈现不属于分区的视图中的所有内容-->
</div>
<div class="side-bar">
    @RenderSection("Sidebar", required: true)    <!--渲染提要栏部分;如果未在视图中定义提要栏部分,则引发错误-->
</div>
@RenderSection("Scripts", required: false)    <!--渲染脚本部分;如果视图中未定义脚本部分,请忽略它。-->

提示:在布局页面中通常有一个名为脚本的可选部分。这可以用于呈现某些视图所需的附加JavaScript,但并非每个视图都需要。一个常见的示例是用于客户端验证的jQueryUnobrusiveValidation脚本。如果视图需要脚本,它会向Razor标记添加适当的@section脚本。

您可能会注意到,上一个列表定义了Layout属性,尽管它是布局本身,而不是视图。这是完全可以接受的,允许您创建布局的嵌套层次结构,如图7.12所示。

提示:现在大多数网站都需要“响应”,因此它们可以在各种各样的设备上工作。您通常不应该为此使用布局。不要根据发出请求的设备为单个页面提供不同的布局。相反,为所有设备提供相同的HTML,并在客户端使用CSS根据需要调整网页的显示。

图7.12 可以嵌套多个布局以创建复杂的层次结构。这允许您保持基本布局中所有视图的公共元素,并将多个视图的公共布局提取到子布局中。

布局文件和节为构建复杂的UI提供了很大的灵活性,但它们最重要的用途之一是减少应用程序中的代码重复。它们非常适合避免为每个视图编写的内容重复。但是,当你发现你想在其他地方重用视图的一部分时,你会怎么办呢?对于这些情况,您有部分视图。

7.4.3 使用部分视图标记

部分视图听起来就像是视图的一部分。它们提供了一种将更大的视图分解为更小的、可重用的块的方法。这对于通过将大视图拆分为多个局部视图来降低其复杂性,或者允许您在另一个视图中重用一个视图的一部分都非常有用。

大多数使用服务器端渲染的web框架都具有这样的功能:RubyonRails具有部分视图,Django具有包含标签,Zend具有部分视图。所有这些都以相同的方式工作,将公共代码提取到可重用的小模板中。即使是客户端模板引擎(如Mustache和Handlebars),由Angular和Ember等客户端框架使用,也有类似的“部分视图”概念。

再次考虑待办事项列表应用程序。您可能会发现,您有一个名为ViewToDo.cshtml的RazorPage,它显示具有给定id的单个待办事项。稍后,您将创建一个新的Razor Page RecentToDos.cshtml,它显示五个最近的待办事项。您可以创建一个名为_ToDo.cshtml的局部视图,而不是将代码从一个页面复制并粘贴到另一个页面,如下所示。

清单7.12 用于显示ToDoItemViewModel的部分视图_ToDo.chtml

@model ToDoItemViewModel    <!--部分视图可以绑定到Model属性中的数据,就像普通RazorPage使用PageModel一样。-->
<!--部分视图的内容,以前存在于ViewToDo.cshtml文件中-->
<h2>@Model.Title</h2>
<ul>
@foreach (var task in Model.Tasks)
{
    <li>@task</li>
}
</ul>

部分视图有点像没有PageModel和处理程序的RazorPages。部分视图纯粹是关于呈现HTML的小部分,而不是处理请求、模型绑定和验证以及调用应用程序模型。它们非常适合定义您需要在多个RazorPages上生成的HTML的可用位。

ViewToDo.cshtml和RecentToDos.cshtml RazorPages都可以呈现_ToDo.cshtml部分视图,该视图处理生成单个类的HTML。使用<Partial />TagHelper渲染局部视图,提供要渲染的局部视图的名称和要渲染的数据(模型)。例如,RecentToDos.cshtml视图可以实现这一点,如下表所示。

清单7.13 从RazorPage呈现部分视图

@page    <!--这是一个RazorPage,因此它使用@page指令。部分视图不使用@page。-->
@model RecentToDoListModel    <!--PageModel包含要呈现的最近项的列表。-->
@foreach(var todo in Model.RecentItems)    <!--循环浏览最近的项目。todo是部分视图所需的ToDoItemViewModel。-->
{
    <partial name="_ToDo" model="todo" />    <!--使用局部标记助手渲染_ToDo局部视图,传入要渲染的模型。-->
}

当您渲染部分视图而不提供绝对路径或文件扩展名时,如清单7.13中的_ToDo,框架会尝试从调用该视图的RazorPage开始搜索Pages文件夹来定位该视图。例如,如果RazorPages位于Pages/Agenda/ToDos/RecentToDos.chstml,则框架会在以下位置查找名为_ToDo.chstml的文件:

  • Pages/Agenda/ToDos/ (当前RazorPage的文件夹)
  • Pages/Agenda/
  • Pages/
  • Pages/Shared/
  • Views/Shared/

将选择包含名为_ToDo.cshtml的文件的第一个位置。如果在引用部分视图时包含.cshtml文件扩展名,则框架将仅在当前RazorPage的文件夹中查找。此外,如果您提供了部分的绝对路径,例如/Pages/Agenda/ToDo.cshtml,那么这是框架将要查看的唯一位置。

注:与布局一样,局部视图通常使用前导下划线命名。

部分视图中包含的Razor代码与标准视图几乎相同。主要区别在于,部分视图仅从其他视图调用。另一个区别是,部分视图在执行时不会运行_ViewStart.cshtml,稍后您将看到这一点。

ASP.NET Core中的子动作(child action)
在ASP.NET MVC的早期版本中,有一个子动作的概念。这是一个可以从视图内部调用的操作方法。这是渲染与主动作方法无关的复杂布局的各个独立部分的主要机制。例如,子动作方法可能会在电子商务站点上呈现购物车。
这种方法意味着您不必使用呈现购物车所需的视图模型项来调用每个页面的视图模型,但它通过从视图中直接引用控制器,从根本上打破了MVC设计模式。
在ASP.NET Core中,子操作不再是这样。视图组件已替换它们。它们在概念上非常相似,因为它们允许执行任意代码和呈现HTML,但它们不直接调用控制器操作。您可以将它们视为一个更强大的局部视图,在局部视图需要包含重要代码或业务逻辑的任何地方都应该使用它。您将在第20章中看到如何构建一个小视图组件。

部分视图并不是减少视图模板中重复的唯一方法。Razor还允许您将命名空间声明和布局配置等常见元素拉入中心文件。在下一节中,您将看到如何使用这些文件来清理模板。

7.4.4 使用_ViewStart和_ViewImports在每个视图上运行代码

由于观点的性质,你不可避免地会发现自己在重复写某些东西。如果所有视图都使用相同的布局,那么在每个页面的顶部添加以下代码有点多余:

@{
  Layout = "_Layout";
}

类似地,如果您发现需要在Razor视图中引用来自不同命名空间的对象,那么必须在每个页面的顶部添加@usingWebApplication1.Models可能会成为一件麻烦事。幸运的是,ASP.NET Core包含两种处理这些常见任务的机制:_ViewImports.cshtml和_ViewStart.cshtml。

使用_VIEWIMPORTS导入公共指令

_ViewImports.cshtml文件包含将插入每个视图顶部的指令。这包括您已经看到的@using和@model语句——基本上已经是所有的可用Razor指令。为了避免向每个视图添加using语句,可以将其包含在_ViewImports.cshtml中,而不是RazorPages中。

清单7.14 导入其他名称空间的典型_ViewImports.cshtml文件

@using WebApplication1 
@using WebApplication1.Pages    <!--应用程序和Pages文件夹的默认命名空间-->
@using WebApplication1.Models    <!--添加此指令以避免将其放置在每个视图中。-->
@addTagHelper *, Microsoft.ASP.NET Core.Mvc.TagHelpers    <!--使标记辅助对象在视图中可用,默认情况下添加-->

_ViewImports.cshtml文件可以放在任何文件夹中,它将应用于该文件夹中的所有视图和子文件夹。通常,它被放置在根Pages文件夹中,以便应用于应用程序中的每个RazorPage和部分视图。

需要注意的是,您只应将Razor指令放在_ViewImports.cshtml中——不能在其中放任何其他的C#。正如您在上一个列表中看到的,这仅限于@using或@addTagHelper指令,您将在下一章中了解这些指令。如果要在应用程序中每个视图的开头运行任意C#,例如设置Layout属性,则应使用_ViewStart.cshtml文件。

使用_VIEWSTART为每个视图运行代码

通过将_ViewStart.cshtml文件添加到应用程序中的Pages文件夹中,可以在每个RazorPage的开头轻松运行公共代码。该文件可以包含任何Razor代码,但它通常用于设置应用程序中所有页面的布局,如以下列表所示。然后,可以从使用默认布局的所有页面中省略Layout语句。如果视图需要使用非默认布局,可以通过在RazorPage本身中设置值来覆盖它。

清单7.15 设置默认布局的典型_ViewStart.cshtml文件

@{
    Layout = "_Layout";
}

_ViewStart.cshtml文件中的任何代码都会在视图执行之前运行。请注意,_ViewStart.cshtml只针对RazorPage视图运行,而不针对布局或部分视图运行。

还请注意,这些特殊Razor文件的名称是强制执行的,而不是您可以更改的惯例。

警告:Razor引擎必须使用名称_ViewStart.cshtml和_ViewImports.cshtml来正确定位和执行它们。要将它们应用到应用程序的所有页面,请将它们添加到pages文件夹的根目录,而不是共享子文件夹。

您可以指定要为视图的子集运行的其他_ViewStart.cshtml或_ViewImports.cshtml文件,方法是将它们包含在Pages的子文件夹中。子文件夹中的文件将在根Pages文件夹中文件之后运行。

部分视图、布局和AJAX
本章描述了使用Razor在服务器端呈现完整的HTML页面,然后在传统的web应用程序中将其发送到用户的浏览器。在构建web应用程序时,一种常见的替代方法是使用JavaScript客户端框架来构建单页应用程序(SPA),该应用程序在浏览器中呈现HTML客户端。
SPAs通常使用的技术之一是AJAX(异步JavaScript和XML),其中浏览器向ASP.NET Core应用程序发送请求,而无需重新加载整个新页面。对于使用服务器端渲染的应用程序,也可以使用AJAX请求。为此,您可以使用JavaScript请求更新页面的一部分。
如果你想在使用Razor的应用程序中使用AJAX,你应该考虑广泛使用部分视图。然后,您可以通过其他RazorPage处理程序公开这些内容,如本文所示:http://mng.bz/vzB1.使用AJAX可以减少需要在浏览器和应用程序之间来回发送的数据总量,并且可以让应用程序感觉更流畅、响应更快,因为它需要更少的全页面加载。但将AJAX与Razor结合使用会增加复杂性,尤其是对于较大的应用程序。如果你预见到自己会大量使用AJAX来构建一个高度动态的web应用程序,你可能会考虑使用带有客户端框架的web API控制器(见第9章),或者考虑使用Blazor。

在本章中,我重点介绍了Razor视图与RazorPage框架的结合,如果您要创建服务器端呈现的ASP.NET Core应用程序,我建议采用这种方法。然而,正如我在第4章中所描述的,在某些情况下,您可能需要使用MVC控制器。在本章的最后一节中,我们将讨论如何从MVC控制器操作渲染Razor视图,以及框架如何定位要渲染的正确Razor。

7.5 从MVC控制器中选择视图

本节包括

  • MVC控制器如何使用ViewResults渲染Razor视图
  • 如何创建新的Razor视图
  • 框架如何定位要渲染的Razor视图

如果您遵循我在第4章中的建议,您应该在服务器端呈现的应用程序中使用RazorPages,而不是在1.x和2.x版本中常见的MVC控制器。

如果出于某种原因,您确实需要使用MVC控制器而不是RazorPages,那么了解在执行操作方法后如何选择要渲染的视图很重要。图7.13显示了在动作调用应用程序模型并接收到一些数据后的放大视图。

图7.13 使用ViewResult从MVC控制器生成HTML的过程。这与RazorPage的过程非常相似。主要区别在于,对于RazorPages,视图是RazorPage的组成部分;对于MVC控制器,视图必须位于运行时。

这个图中的一些应该很熟悉,它是第4章中图4.6的下半部分(有一些补充),是图7.1的MVC等价物。它显示MVC控制器动作方法使用ViewResult对象来指示应该呈现Razor视图。此ViewResult包含要渲染的Razor视图模板的名称和一个视图模型,一个包含要渲染数据的任意POCO类。

注:我在第4章中讨论了ViewResults。它们是RazorPage的PageResult的MVC等价物。主要区别在于ViewResult包括要呈现的视图名称和要传递给视图模板的模型,而PageResult始终呈现RazorPage的关联视图并将PageModel传递给视图样板。

从操作方法返回ViewResult后,控制流返回到MVC框架,该框架基于提供的模板名称使用一系列探索法来定位视图。找到Razor视图模板后,Razor引擎将视图模型从ViewResult传递到视图,并执行模板以生成最终的HTML。最后一步,呈现HTML,本质上与RazorPages的过程相同。

您在第4章中了解了如何创建控制器,在本节中,您将了解如何创建视图和ViewResult对象以及如何指定要渲染的模板。通过右键单击SolutionExplorer中的MVC应用程序并选择add>NewItem,然后从对话框中选择RazorView,可以在VisualStudio中向应用程序添加新的视图模板,如图7.14所示。如果您没有使用Visual Studio,请在Views文件夹中创建一个新的空白文件,文件扩展名为.cshtml。

创建视图模板后,您现在需要调用它。在大多数情况下,您不会直接在操作方法中创建ViewResult。相反,您将使用Controller基类上的一个View助手方法。这些助手方法简化了传递视图模型和选择视图模板,但它们并没有什么神奇之处,它们所做的只是创建ViewResult对象。

图7.14“添加新项目”对话框。选择Razor View-Empty将为应用程序添加一个新的Razor视图模板文件。

在最简单的情况下,您可以在没有任何参数的情况下调用View方法,如下表所示。此助手方法返回一个ViewResult,它将使用约定来查找要渲染的视图模板,并且在执行视图时不会提供视图模型。

清单7.16 使用默认约定从操作方法返回ViewResult

public class HomeController : Controller    //从Controller基类继承使View助手方法可用。
{
    public IActionResult Index()
    {
        return View();    //View助手方法返回ViewResult。
    }
}

在此示例中,View助手方法返回ViewResult,而不指定要运行的模板的名称。相反,要使用的模板的名称基于控制器的名称和操作方法的名称。假定控制器名为HomeController,方法名为Index,默认情况下Razor模板引擎在Views/Home/Index.cshtml位置查找模板,如图7.15所示。

图7.15 基于命名约定,视图文件位于运行时。Razor视图文件位于基于关联MVC控制器名称的文件夹中,并使用请求它们的操作方法的名称命名。任何控制器都可以使用共享文件夹中的视图。

这是另一个在MVC中使用约定来减少必须编写的样板的例子。一如既往,这些约定是可选的。还可以将要作为字符串运行的模板的名称显式传递给View方法。例如,如果Index方法返回View(“ListView”),则模板引擎将查找名为ListView.cshtml的模板。您甚至可以指定视图文件的完整路径,相对于应用程序的根文件夹,例如view(“Views/global.cshtml”),它将在Views/global.html位置查找模板。

注意:指定视图的绝对路径时,必须在路径中同时包含顶级Views文件夹和.cshtml文件扩展名。这与查找局部视图样板的规则类似。

查找MVC Razor视图的过程与查找要渲染的部分视图的过程非常相似,如第7.4节所示。框架在多个位置搜索以查找请求的视图。不同的是,对于RazorPages,搜索过程只发生在部分视图渲染中,因为要渲染的主要Razor视图是已知的——这是RazorPage的视图模板

图7.16显示了当从MVC控制器返回ViewResult时,MVC框架用于查找要执行的正确视图模板的完整过程。可能有多个模板符合条件,例如,如果主文件夹和共享文件夹中都存在Index.chtml文件。与定位局部视图的规则类似,引擎将使用找到的第一个模板。

图7.16 描述Razor模板引擎如何定位要执行的正确视图模板的流程图。避免这个图表的复杂性是我建议尽可能使用RazorPages的原因之一!

提示:您可以在初始配置期间修改所有这些约定,包括图7.16所示的算法。事实上,如果需要,您可以替换整个Razor模板引擎,但这超出了本书的范围。

您可能会发现,显式提供要在控制器中渲染的视图文件的名称很有吸引力;如果是的话,我鼓励你克服这种冲动。如果你接受现有的惯例并随波逐流,你会节省相当多的时间。扩展到任何其他查看您代码的人;如果你坚持标准惯例,当他们看你的应用程序时,会有一种令人欣慰的熟悉感。这当然是一件好事!

除了提供视图模板名称之外,还可以传递一个对象作为Razor视图的视图模型。该对象应与视图的@model指令中指定的类型匹配,并且其访问方式与RazorPages完全相同;使用Model属性。下面的列表显示了将视图模型传递给视图的两个示例。

清单7.17 使用默认约定从操作方法返回ViewResult

public class ToDoController : Controller
{
    public IActionResult Index()
    {
        var listViewModel = new ToDoListModel();     //创建要传递给Razor视图的视图模型实例。
        return View(listViewModel);    //视图模型作为参数传递给view。
    }
    public IActionResult View(int id)
    {
        var viewModel = new ViewToDoModel(); 
        return View("ViewToDo", viewModel);    //可以在提供视图模型的同时提供视图样板名称。
    }
}

找到Razor视图模板后,将使用您在本章中看到的Razor语法渲染视图。您可以使用已经看到的所有功能,例如布局、局部视图、_ViewImports和_ViewStart。从Razor视图的角度来看,RazorPages视图和MVC Razor视图没有区别。

这就是我们第一次使用Razor模板引擎渲染HTML的结论。在下一章中,您将了解标记帮助器,以及如何使用它们来构建HTML表单,这是现代Web应用程序的主要内容。标记助手是ASP.NET Core中Razor比上一版本最大的改进之一,因此掌握它们将使编辑视图成为一种总体上更愉快的体验!

总结

  • 在 MVC 设计模式中,视图负责为应用程序生成 UI。
  • Razor 是一种模板语言,它允许您使用 HTML 和 C# 的混合生成动态 HTML。
  • HTML 表单是从浏览器向服务器发送数据的标准方法。您可以使用标记帮助程序轻松生成这些表单。
  • RazorPages 可以通过在 PageModel上 设置公共属性将强类型数据传递给 Razor 视图。要访问视图模型上的属性,视图应使用 @model 指令声明模型类型。
  • 页面处理程序可以使用 ViewData 字典将键值对传递给视图。
  • Razor 表达式使用@或@( )将 C# 值呈现给 HTML 输出。使用 Razor 表达式时,不需要在语句后面包含分号。
  • 使用@{ }定义的 Razor 代码块在不输出 HTML 的情况下执行 C#。Razor 代码块中的 C# 必须是完整的语句,因此必须包含分号。
  • 循环和条件可用于在模板中轻松生成动态 HTML,但最好限制 if 语句的数量,以使视图易于阅读。
  • 如果您需要将字符串呈现为原始 HTML,则可以使用 HTML.raw,但这样做会在应用程序中存在安全漏洞。
  • 标记帮助程序允许您将数据模型绑定到 HTML 元素,从而更容易生成动态 HTML,同时保持编辑器友好。
  • 您可以在布局中放置多个视图共用的 HTML。布局将在调用 @RenderBody 的位置呈现子视图中的任何内容。
  • 在部分视图中封装常用的 Razor 代码片段。可以使用<partial />标记渲染局部视图。
  • _ViewImports.cshtml 可用于在每个视图中包含公共指令,例如 @using 语句。
  • _ViewStart.cshtml 在执行每个 RazorPage 之前调用,可用于执行所有 RazorPages 通用的代码,例如设置默认布局页面。它不会对布局或局部视图执行。
  • _ViewImports.cshtml 和 _ViewStart.cshtml 是根文件夹中的分层文件,首先执行,然后是控制器特定视图文件夹中的文件。
  • 控制器可以通过返回 ViewResult 来调用 Razor 视图。这可能包含要渲染的视图的名称,也可能包含渲染视图时要使用的视图模型对象。如果未提供视图名称,则使用约定选择视图。
  • 按照惯例,MVC Razor 视图的命名与调用它们的操作方法相同。它们要么位于与操作方法的控制器同名的文件夹中,要么位于共享文件夹中。

(请点击这里阅读其他章节

posted on 2022-12-28 11:23  生活的倒影  阅读(1173)  评论(0编辑  收藏  举报