欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第五章 Razor

程序员文章站 2022-04-03 08:15:13
...

第五章 Razor

在ASP.NET Core MVC应用中, 使用一个称为”视图引擎”的组件来生成HTML并发送到客户端. 默认视图引擎是Razor, 它处理指令并将数据插入HTML, 然后发送回浏览器.
在这一章中, 我将简要介绍Razor语法, 这样你就能识别Razor表达式. 我不会在这一章中提供详细的Razor语法参考, 你可以将它看作是速成课程. 在之后的章节中, 我还会结合MVC的其他特性来接受Razor.

问题 答案
Razor是什么? Razor是负责将数据置入HTML文档的视图引擎
Razor有什么用? 生成动态网站是编写web应用的基础, Razor提供了一些特性, 使得MVC的其他部分更容易实现
Razor如何使用? Razor表达式被加入到视图文件的静态HTML中. 表达式会在控制器生成响应时计算
Razor有什么缺陷? Razor表达式可以包含绝大多数C#语句, 所以可能很难决定哪些逻辑应当属于视图, 哪些应当属于控制器, 这会破坏MVC模式关注点分离的中心原则
Razor有替代品吗? 你可以实现自己的视图引擎, 详见第二十一章. 有许多第三方视图引擎, 但它们通常仅适合特定情境, 且不提供长期支持

章节简述

问题 解决方案
访问视图模型 使用@model表达式来定义模型类型, 使用@Model来访问模型对象
使用类型名, 不使用名称空间限定 创建view import文件
定义在多个视图中使用的内容 布局
指定默认布局 创建view start文件
将数据从控制器传递到视图模型之外的视图 View Bag
生成数据敏感的内容 使用Razor条件表达式
为集合中的每一项生成内容 使用Razorforeach表达式

准备示例项目

同上一章, 创建空的ASP.NET Core web应用, 项目名为”Razor” 并启用MVC组件

// StartUp.cs

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    }
    app.UseMvcWithDefaultRoute();
}

创建模型

namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

创建控制器

using Microsoft.AspNetCore.Mvc;
using Razor.Models;

namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

创建视图

创建视图文件Index.cshtml

@model Razor.Models.Product

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

接下来我将介绍Razor视图的不同部分, 并演示它可以做的事情. 在学习Razor时需要记住: 视图的存在是为了向用户表达模型的一个或多个部分, 即生成一个HTML来显示一个或多个对象的数据.

模型对象

在视图中使用@Model来访问模型对象, 例如

<body>
    @Model.Name
</body>

注意: 使用@model来限定模型类型, 使用@Model访问模型对象

使用@model指定类型的视图称为强类型视图, VS将能够智能联想模型的属性名. 如果不指定强类型视图也可以正常使用, 但容易出错(引用了不存在的属性, 引发RuntimeBinderException)

视图引入

之前在Index.cshtml中指定模型类型时, 必须加上名称空间, 就像这样:

@model Razor.Models.Product

默认情况下, 强类型视图中引用的所有类型都必须具有名称空间, 但在编写复杂的Razor表达式时会使得代码很难阅读. 可以向项目中添加view imports文件来制定名称空间, 存放在\Views\_ViewImports.cshtml文件中

要创建该文件, VS中右键添加”Razor视图导入”, 会自动将文件名填写为_ViewImports.cshtml(其实就是一个空文件), 添加以下内容:

@using Razor.Models

除了@, 没有;, 和C#代码的引入语句一样.

之后在视图中就可以这样写

@model Product

也就是全局引入, 也可以在单个文件中引入

@using Razor.Models
@model Product

布局

Index.cshtml中另一句很重要的Razor表达式是

@{
    Layout = null;
}

这是一个Razor代码块的例子, 可以在视图中包含C#语句. 代码块以@{}包围, 在视图被渲染的时候计算.
MVC应用中的Razor视图会被编译为C#类, 基类定义了Layout属性, 将在第二十一章中详细描述. 将Layout设为null, 告诉MVC, 视图是自包含的, 渲染且仅渲染该视图中的全部内容.
自包含视图适合一些小型示例项目, 但真正的项目会有很多视图, 且一些视图会有同样的内容(导航栏等), 重复这些内容会导致难以维护.
更好的方法是使用Razor布局, 即HTML模板, 布局的改动会影响所有使用该布局的视图.

新建布局

布局通常由多个控制器使用的视图共享, 并存储在/Views/Shared中. 要新建一个布局文件, 解决方案资源管理器右键新建Razor布局, 设置布局名为_BasicLayout.cshtml. (和视图导入文件一样, 布局文件的名称以下划线开头), 自动填充内容如下

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>

布局是视图的特殊形式, 在上面的代码中有两个@表达式.
@RenderBodyView()传递的视图插入布局中, @ViewBag.Title插入标题.
ViewBag是一个方便的用于在应用中传递数据的特性. 在本例中, 从视图向布局传递数据. 布局中的HTML元素将应用于使用它的任何视图, 可以在布局中加入一些CSS代码或CSS引入.

<!-- \Views\Shared\_BasicLayout.cshtml -->

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>

使用布局

在视图中应用布局

<!-- \Views\Home\Index.cshtml -->

@model Product

@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product Name";
}

Product Name: @Model.Name

指定布局不需要拓展名, MVC搜索文件的约定与搜索视图相同. 通过ViewBag.Title设置标题.
即使对于这样一个简单的应用, 视图的转换也很引人注目. 布局包含HTML结构, 视图则只需要关注向用户呈现的数据的动态内容. 当MVC处理Index.cshtml时, 使用布局来创建统一的HTML响应.

第五章 Razor

使用View Start文件

还有一个问题, 就是我必须为每个视图指定布局文件, 这很容易出错, 所以需要一个设置布局默认值的地方.
使用一个view start文件就可以解决. 在渲染视图的时候, MVC首先查找_ViewStart.cshtml文件. 该文件的内容会被视为包含在各个视图文件中, 可以利用这个特性来自动设置布局属性的值.
Views文件夹中右键创建, VS会自动命名_ViewStart.cshtml, 并填充默认内容

@{
    Layout = "_Layout";
}

要使用之前的布局文件, 修改这个属性的值

@{
    Layout = "_BasicLayout";
}

之后就可以删掉Index.cshtml中设置的布局属性了.
并不一定要采用_ViewStart.cshtml的值, 视图文件中为属性赋值会覆盖掉_ViewStart.cshtml中的赋值.
可以使用多个view start文件来为应用的不同部分设置不同的默认值. Razor查找视图最近的view start文件, 这意味着你可以通过添加/Views/Home/_ViewStart.cshtml/Views/Shared/_ViewStart.cshtml, 来覆盖/Views/_ViewStart.cshtml

注意: 需要理解忽略Layout属性和将其设置为null的区别, 置为null则不使用任何布局, 忽略则使用一个默认布局

Razor表达式

在展示过视图和布局后, 我将展示Razor支持的不同种类的语句, 以及如何使用. 在一个风格良好的MVC程序中, 行为方法和视图的区别非常明显

部件 做什么 不做什么
行为方法 传递视图模型对象到视图 传递有格式的数据到视图
视图 使用视图模型对象, 向用户呈现内容 修改视图模型对象

要从MVC中收益, 就必须注重应用程序不同部分的分离. 你可以使用Razor做很多事情, 包括C#语句, 但不应该用Razor来执行业务逻辑或操作领域模型对象.
下面的代码向Index视图中添加了一个新的表达式

<!-- \Views\Home\Index.cshtml -->

@model Product
@{
    ViewBag.Title = "Product Name";
}
<p>Product Name: @Model.Name</p>
<p>Product Price: @($"{Model.Price:C2}")</p>

可以在行为中格式化Price的值并传递给视图, 这是可行的, 但会破坏MVC模式的优势. ASP.NET Core MVC并不强制使用MVC模式, 你必须始终认识到你的设计和编码决策的影响.

!处理数据 vs 格式化数据

“处理”和”格式化”直接区别很大. 视图负责格式化数据, 这就是为什么我直接直接将Product对象传递给视图, 而不是将对象的属性格式化成一个字符串. 处理数据(包括选择要显示的数据对象)是控制器的职责, 控制器将调用模型来获取和修改所需数据. 有时很难区分处理和格式化的界线, 但根据经验, 最好将除简单表达式外的任何东西都放入控制器中.

插入数据值

Razor表达式最简单的用法就是插入数据值到HTML标记中. 最常见的方法是使用@Model表达式. 如

<p>Product Name: @Model.Name</p>

还可以使用ViewBag来插入值, 在布局中设置标题时就用到了这个特性. 可以使用ViewBag将数据从控制器传递给视图, 来补充模型, 如下

// Controllers\HomeController.cs

using Microsoft.AspNetCore.Mvc;
using Razor.Models;

namespace Razor.Controllers {
    public class HomeController : Controller {
    public ViewResult Index() {
            Product myProduct = new Product {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };

            ViewBag.StockLevel = 2;

            return View(myProduct);
        }
    }
}

ViewBag属性返回一个动态对象. 动态对象不必实现声明属性名, 可以任意为其复赋值, 但也容易出错, VS不会给出自动完成建议.
什么时候使用view bag, 什么时候扩展模型, 取决于个人风格. 我的风格是只使用view bag来提供一些关于如何呈现数据的提示, 而不用于显示数据值. 如果要使用view bag, 使用@ViewBag访问

<!-- \Views\Home\Index.cshtml -->
@model Product

@{
    ViewBag.Title = "Product Name";
}

<p>Product Name: @Model.Name</p>
<p>Product Price: @($"{Model.Price:C2}")</p>
<p>Stock Level: @ViewBag.StockLevel</p>

第五章 Razor

设置DOM属性值

目前为止的例子都是设置DOM元素内容, 但也可以使用Razor来设置DOM元素属性. 示例如下

<!-- \Views\Home\Index.cshtml -->

@model Product

@{
    ViewBag.Title = "Product Name";
}

<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">
    <p>Product Name: @Model.Name</p>
    <p>Product Price: @($"{Model.Price:C2}")</p>
    <p>Stock Level: @ViewBag.StockLevel</p>
</div>

提示data-为前缀的数据属性, 作为非正式的习惯存在多年, 并成为了HTML5的标准. 经常使用它们来方便JS和CSS的定位.

如果运行应用, 在浏览器中查看DOM将显示如下

<div data-productid="1" data-stocklevel="2">
    <p>Product Name: Kayak</p>
    <p>Product Price: £275.00</p>
    <p>Stock Level: 2</p>
</div>

条件语句

Razor可以处理条件语句, 这意味着可以根据视图中的数据值定制输出内容. 这种技术是Razor的核心, 使你可以创建复杂流畅的布局, 且维护和理解起来非常简单. 在下面的实例中, 为Index视图添加了一个条件语句

<!-- \Views\Home\Index.cshtml -->

@model Product

@{
    ViewBag.Title = "Product Name";
}

<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">
    <p>Product Name: @Model.Name</p>
    <p>Product Price: @($"{Model.Price:C2}")</p>
    <p>Stock Level:
        @switch (ViewBag.StockLevel) {
            case 0:
                @:Out of Stock
                break;
            case 1:
            case 2:
            case 3:
                <b>Low Stock (@ViewBag.StockLevel)</b>
                break;
            default:
                @: @ViewBag.StockLevel in Stock
                break;
        }
    </p>
</div>

使用@符号来开始一个条件语句, 然后在Razor块中可以包括HTML元素和Razor表达式, 就像这样

<b>Low Stock (@ViewBag.StockLevel)</b>

不需要添加引号, Razor会自动处理, 要在Razor代码块中插入文本碎块(没有被HTML标签包围), 则需要使用@:符号, 如@: @ViewBag.StockLevel in Stock.

条件语句在Razor视图中很重要, 因为它们允许视图依据行为传递过来的数据值来改变内容. 下面的示例显示了向Index.cshtml中添加if语句

<!-- \Views\Home\Index.cshtml -->

@model Product

@{
    ViewBag.Title = "Product Name";
}

<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">
    <p>Product Name: @Model.Name</p>
    <p>Product Price: @($"{Model.Price:C2}")</p>
    <p>Stock Level:
        @if (ViewBag.StockLevel == 0) {
            @:Out of Stock
        } else if (ViewBag.StockLevel > 0 && ViewBag.StockLevel <= 3) {
            <b>Low Stock (@ViewBag.StockLevel)</b>
        } else {
            @: @ViewBag.StockLevel in Stock
        }
    </p>
</div>

这个语句产生了与switch相同的结果. 但我想演示将C#语句与Razor视图结合起来. 我将在第二十一章详细描述.

数组和集合列举

写MVC应用时, 经常会遇到要列举数组或其他类型的集合的对象的具体内容. 本节展示了如何实现列举

// Controllers\HomeController.cs

using Microsoft.AspNetCore.Mvc;
using Razor.Models;

namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            Product[] array = {
                new Product {Name = "Kayak", Price = 275M},
                new Product {Name = "Lifejacket", Price = 48.95M},
                new Product {Name = "Soccer ball", Price = 19.50M},
                new Product {Name = "Corner flag", Price = 34.95M}
            };
            return View(array);
        }
    }
}

行为提供了一个Product[], 然后修改Index.cshtml

<!-- \Views\Home\Index.cshtml -->

@model Product[]
@{
    ViewBag.Title = "Product Name";
}
<table>
    <thead>
        <tr><th>Name</th><th>Price</th></tr>
    </thead>
    <tbody>
        @foreach (Product p in Model) {
            <tr>
                <td>@p.Name</td>
                <td>@($"{p.Price:C2}")</td>
            </tr>
        }
    </tbody>
</table>

@foreach列举了模型数组的内容.

第五章 Razor

总结

在本章中概述了Razor引擎, 以及如何用它生成HTML. 展示了如何引用@Model@ViewBag, 以及使用Razor表达式根据数据值定制HTML内容. 在本书的其余部分, 你将看到Razor的不同示例. 在第二十一章, 我将详细描述MVC视图机制的工作原理.
下一章, 我将介绍VS为ASP.NET Core MVC提供的一些特性.