第五章 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>
布局是视图的特殊形式, 在上面的代码中有两个@
表达式. @RenderBody
将View()
传递的视图插入布局中, @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提供的一些特性.
上一篇: Springmvc中@RequestParam传值中文乱码解决方案
下一篇: 需调试bug