第五章 Razor

第五章 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响应.

这里写图片描述

使用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>

这里写图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值