在ASP.NET Core MVC应用程序中,视图引擎(view engine)负责处理发送给客户端的内容。MVC框架中默认的视图引擎称为Razor,用来为HTML文件添加注释说明并将这些动态内容插入发送给浏览器的输出中。
本章将介绍一个用于快速解读Razor语法的工具,这样当你看到它的时候,就可以马上将其识别出来。本章不会非常详细地介绍Razor的内容,把本章看作Razor语法的速成指南即可。随着本书其他章节介绍MVC的其他功能,我们再慢慢深入介绍。关于在具体语境中如何理解Razor,参见表5-1。
表5-1 在具体语境中理解Razor
问题 | 答案 |
它是什么? | Razor是负责将数据合并到HTML文档中的视图引擎 |
它有什么作用? | 动态生成内容的能力对编写Web应用程序是必不可少的,Razor提供了可以使C#语句轻松地与ASP.NET Core MVC的其他部分协同工作的能力 |
怎样使用? | 将Razor表达式添加到视图文件的静态HTML中,Razor表达式可协助生成客户端请求的响应 |
有哪些容易出现的问题或限制? | Razor表达式可以包含几乎所有的C#语句,并且很难决定逻辑应该属于视图还是控制器,从而削弱MVC关注点分离的核心理念 |
有其他的做法吗? | 可以编写自己的视图引擎。也有一些第三方的视图引擎可以使用,但它们往往在特定情况下可行,不提供长期支持 |
表5-2列出了本章要完成的操作。
表5-2 本章要完成的操作
操作 | 解决方法 | 代码清单 |
访问视图模型 | 使用@model表达式定义模型类型,使用@Model表达式访问模型对象 | 代码清单5-5、代码清单5-14、代码清单5-17 |
使用类型名称而不限定它们的命名空间 | 创建视图导入文件 | 代码清单5-6和代码清单5-7 |
定义多视图共享的内容 | 使用布局 | 代码清单5-8~代码清单5-10 |
定义默认布局 | 使用视图启动文件 | 代码清单5-11~代码清单5-13 |
将数据从控制器传递到视图模型之外的其他视图 | 使用视图包 | 代码清单5-15和代码清单5-16 |
有选择地生成内容 | 使用Razor条件表达式 | 代码清单5-18和代码清单5-19 |
为数组和集合中的每个元素生成内容 | 使用Razor的foreach表达式 | 代码清单5-20和代码清单5-21 |
5.1 准备示例项目
为了演示Razor是怎样工作的,首先创建一个ASP.NET Core Web Application(.NET Core)项目并取名为Razor,就像之前的章节描述的那样。接下来,在Startup.cs文件中启用了MVC的默认配置,如代码清单5-1所示。
代码清单5-1 在Razor文件夹下的Startup.cs文件中启用MVC的默认配置
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace Razor {
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
//app.Run(async (context) => {
// await context.Response.WriteAsync("Hello World!");
//});
app.UseMvcWithDefaultRoute();
}
}
}
5.1.1 定义模型
接下来,创建模型文件夹Models,并在其中添加名为Product.cs的类文件,用于定义代码清单5-2中的简单模型类。
代码清单5-2 Models文件夹下的Product.cs文件的内容
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; }
}
}
5.1.2 创建控制器
Startup.cs文件中的配置如下:默认情况下,MVC将请求发送到名为Home的控制器。创建Controllers文件夹,并在其中添加名为HomeController.cs的类文件,用于定义代码清单5-3中的简单控制器。
代码清单5-3 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
};
return View(myProduct);
}
}
}
以上控制器定义了一个名为Index的操作方法来创建和填充Product对象的属性。将Product对象传递给View方法,以便渲染视图时将其用作模型。在调用View方法时,并不指定视图文件的名称,这样操作方法便会使用默认视图。
5.1.3 创建视图
为了给 Index 操作方法创建默认视图,创建Views/Home文件夹,在其中添加MVC视图页面文件Index.cshtml,其中的内容如代码清单5-4所示。
代码清单5-4 Views/Home文件夹下的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 页面文件。如果你仍记得我们始终试图建立可以发送到客户端的HTML页面,那么就会觉得Razor所做的一切都是有意义的。如果运行示例应用程序,你将看到图5-1所示的输出。
图5-1 示例应用程序的输出
5.2 使用模型对象
让我们从Index.cshtml视图文件的第一行开始。
...
@model Razor.Models.Product
...
Razor表达式以@字符开头。在本例中,@model表达式声明了将从操作方法传递到视图的模型对象的类型。这样就可以通过@model来访问视图模型对象的方法、字段和属性,代码清单5-5显示了对Index视图所做的简单补充。
代码清单5-5 在Views/Home文件夹下的Index.cshtml文件中引用视图模型对象的属性
@model Razor.Models.Product
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
@Model.Name
</body>
</html>
注 意
这里使用@model声明视图模型对象的类型,而使用@Model访问Name属性。
如果运行应用程序,你将看到图5-2所示的输出。
使用@model表达式指定类型的视图称为强制类型视图(strongly typed view)。当键入@model和句点时,Visual Studio便会弹出@model表达式成员可以使用的名称建议,如图5-3所示。
Visual Studio对成员名称的可视化建议有助于避免在Razor中出现错误。根据个人意愿,可以忽略这些建议,Visual Studio将高亮显示成员名称有问题,以便进行更正,就像使用常规的C#类文件一样。图5-4列举了一个示例,开发人员在其中试图引用@Model.NotARealProperty。Visual Studio已意识到开发人员在模型类型中指定的Product类没有这样的属性,因而在编辑器中高亮显示错误。
使用视图导入
当在Index.cshtml文件的开头定义模型对象时,必须导入包含模型类的命名空间,如下所示:
...
@model Razor.Models.Product
...
默认情况下,在强制类型的Razor视图中引用的所有类型都必须使用命名空间进行限定。当模型对象有唯一的引用类型时,这不是什么大问题;但是当需要编写更复杂的Razor表达式时,就会使视图的可读性变差。
可以通过在项目中添加视图导入文件来指定要搜索类型的命名空间集合。视图导入文件放在Views文件夹中,并命名为_ViewImports.cshtml。
注 意
Views文件夹中名称以下画线(_)开头的文件不会返回给用户,从而将想要呈现的视图文件和支持它们的文件区分开。视图导入文件和布局模板(稍后介绍)是以下画线为前缀的。
要创建视图导入文件,请在解决方案资源管理器(Solution Explorer)中右击Views文件夹,从弹出菜单中选择Add→New Item,然后从ASP.NET类别中选择MVC View Imports Page模板,如图5-5所示。
Visual Studio会自动将文件的名称设置为_ViewImports. cshtml,单击Add按钮以创建该文件。代码清单5-6展示了添加完表达式之后的视图。
代码清单5-6 Views文件夹下的_ViewImports.cshtml文件的内容
@using Razor.Models
Razor视图中用来搜索类的命名空间由@ using表达式指定,后面跟着相应的命名空间。在代码清单5-6中,已为Razor.Models命名空间添加了一项,其中包含了示例应用程序的模型类。
现在,Razor.Models命名空间就包含在视图导入文件中了,可以从Index.cshtml文件中移除命名空间,如代码清单5-7所示。
代码清单5-7 在Views/Home文件夹下的Index.cshtml文件中不使用命名空间引用模型类
@model Product
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
@Model.Name
</body>
</html>
提 示
还可以将@using表达式添加到单个视图文件中,这将允许在单个视图中使用没有命名空间的类型。
5.3 使用布局
Index.cshtml视图文件中还有另外一个重要的Razor表达式:
...
@{
Layout = null;
}
...
这是一个Razor代码块,允许在视图中包含C#语句。Razor代码块以@“{”开始,以“}”结束,里面包含的语句将在呈现视图时执行。
以上Razor代码块将Layout属性的值设置为null。Razor视图被编译为MVC应用程序中的C#类,并且用基类定义了Layout属性。第21章将介绍具体的工作方式。将Layout属性设置为null的效果就是告诉MVC视图是自包含的,并将渲染客户端所需的所有内容。
自包含的视图对于简单的示例应用程序表现比较好,但是一个真正的项目可以有几十个视图,有些视图将共享内容。在视图中复制共享内容又很难管理,尤其是当需要进行更改并且必须跟踪所有需要更改的视图时。
当一个模板包含公共的内容并且可以应用于一个或多个视图时,比较好的方法就是使用Razor布局。对布局进行更改时,更改将自动影响使用布局的所有视图。
5.3.1 创建布局
布局通常由多个控制器使用的视图共享,并存储在Views/Shared文件夹中,这是在查找文件时Razor会自动查看的位置之一。为了创建布局,先创建Views/Shared文件夹,右击后从弹出菜单中选择Add→New Item。从ASP.NET类别中选择MVC View Layout Page模板,并将文件名设置为_BasicLayout.cshtml,如图5-6所示。单击Add按钮创建这个文件(与视图导入文件一样,布局文件的名称也以下画线开头)。
代码清单5-8显示了由Visual Studio创建的_BasicLayout.cshtml文件的初始内容。
代码清单5-8 Views/Shared文件夹下的_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方法的调用会将操作方法定义的视图内容插入布局标记中。
...
<div>
@RenderBody()
</div>
...
布局中的另一个Razor表达式会查找名为ViewBag.Title的属性,以便设置title元素的内容。
...
<title>@ViewBag.Title</title>
...
ViewBag允许在应用程序中传递数据值,本例中是指在视图和布局之间传递。在将布局应用于视图时,你将看到这是如何工作的。
布局中的HTML元素将应用于任何使用它们的视图,并且提供了一个用于定义公共内容的模板。在代码清单5-9中,在布局中添加了一些简单的标记,这样模板效果就很明显了。
代码清单5-9 在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>
这里添加了标头元素以及一些CSS来为包含@RenderBody表达式的div元素的内容设置样式,这样就可以清楚地知道哪些内容来自布局,哪些内容来自视图。
5.3.2 使用布局
要将布局应用于视图,我们需要设置Layout属性的值,并移除现在由布局提供的HTML,如代码清单5-10所示的html、head和body元素。
代码清单5-10 在Views/Home文件夹下的Index.cshtml文件中应用布局
@model Product
@{
Layout = "_BasicLayout";
ViewBag.Title = "Product Name";
}
Product Name: @Model.Name
Layout属性将指定用于视图的布局文件,但不使用.cshtml文件扩展名。Razor将在/Views/Home和Views/Shared文件夹中查找指定的布局文件。
上面还设置了ViewBag.Title属性。在渲染视图的时候,布局会使用该属性设置title元素的内容。
即使是非常简单的应用,视图的这种转换也是非常激动人心的。布局包含了任何HTML响应所需的所有结构,这使得视图只需要关注向用户呈现数据的动态内容就可以了。当MVC处理Index.cshtml文件时,将应用布局来创建统一的HTML响应,如图5-7所示。
5.3.3 应用视图启动文件
另一个需要交代的地方,就是必须为将要使用的每个视图指定布局文件。因此,如果需要重命名布局文件,就必须找到引用布局文件的每个视图并进行更改,这非常容易出错。最重要的是,这与MVC应用程序的易维护宗旨背道而驰。
这个问题可以通过使用视图启动文件(view start file)来解决。在渲染视图的时候,MVC将查找一个名为_ViewStart.cshtml的文件。这个文件的内容将被视为包含在视图文件本身中,我们可以使用这个功能自动设置Layout属性的值。
要创建视图启动文件,请右击Views文件夹,从弹出菜单中选择Add→New Item,然后从ASP.NET类别中选择MVC View Start Page模板,如图5-8所示。
Visual Studio会自动将文件的名称设置为_ViewStart.cshtml,单击Add按钮,Visual Studio为这个文件创建的初始内容如代码清单5-11所示。
代码清单5-11 Views文件夹下的_ViewStart.cshtml文件的初始内容
@{
Layout = "_Layout";
}
为了将布局应用于应用程序中的所有视图,更改分配给Layout属性的值,如代码清单5-12所示。
代码清单5-12 在Views文件夹下的_ViewStart.cshtml文件中应用默认视图
@{
Layout = "_BasicLayout";
}
因为视图启动文件包含Layout属性的值,所以可以从Index.cshtml文件中删除相应的表达式,如代码清单5-13所示。
代码清单5-13 更新Views/Home文件夹下的Index.cshtml文件以体现视图启动文件的使用
@model Product
@{
ViewBag.Title = "Product Name";
}
Product Name: @Model.Name
不必指定要使用的视图启动文件。MVC将自动定位视图启动文件并使用里面的内容。视图启动文件中定义的值优先,这样就可以方便地重写视图启动文件。
还可以使用多个视图启动文件为应用程序的不同部分设置默认值。Razor会查找离正在处理的视图最近的视图启动文件,这意味着可以通过在Views/Home或Views/Shared文件夹中添加视图启动文件来覆盖默认设置。
警 告
务必了解从视图启动文件中省略Layout属性与将Layout属性设置为null之间的区别。如果视图是自包含的,并且不希望使用布局,可将Layout属性设置为nulll。如果省略Layout属性,MVC将认为需要布局,并且应该使用已在视图启动文件中找到的值。
5.4 使用Razor表达式
至此,我们已经了解了视图和布局的基本知识,接下来介绍Razor支持的各种不同类型的表达式,以及如何使用它们来创建视图内容。在优秀的MVC应用程序中,操作方法和视图的角色之间存在着明确的差异,如表5-3所示。
表5-3 操作方法和视图的角色差异
内容 | 可以做什么 | 不能做什么 |
操作方法 | 将视图模型对象传递到视图 | 将格式化数据传递到视图 |
视图 | 使用视图模型对象向用户显示内容 | 修改视图模型对象 |
为了充分发挥MVC的优势,我们需要尊重并保证应用程序不同部分之间的分离。正如你将看到的,Razor可以为我们做很多工作,包括使用C#语句,但是不能使用Razor执行业务逻辑或以任何方式操作域模型对象。
作为一个简单的示例,代码清单5-14显示了一种在Index.cshtml文件中添加表达式的方式。
如下章节略,以上回答摘自刚刚上架的《精通ASP.NET Core MVC 第7版》全书694页,作者:[美] 亚当·弗里曼(Adam Freeman) 著,郝冠军,孙臻,闫小迪,张淯易 译
1.畅销书升级版,详细介绍了ASP.NET Core MVC的架构、功能和应用,讲述了开发可扩展的Web应用程序的工具和技术。
2.结合具体实例和代码,展示了ASP.NET Core MVC的方方面面,揭示了如何用ASP.NET Core MV构建Web应用程序。
3.第7版新增了大量内容:
- Visual Studio、C#、Entity Framework、.NET Core 2的功能;
- 配置应用程序和元包的方法;
- Visual Studio Code的用法和在非Windows平台上使用.NET Core的方法。
通过阅读本书,你可以实现以下目标:
- 透彻理解ASP.NET Core MVC框架;
- 理解MVC和测试驱动的开发的原理;
- 学会在日常工作中应用ASP.NET Core MVC的功能;
- 明白如何为单页应用程序创建REST风格的Web服务;
- 基于已有的MVC知识快速搭建新的编程模型。