1 准备工作
添加包:dotnet add package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation --version 3.1.1
2 开始使用视图
2.1 配置应用程序
HTML响应是使用视图创建的,视图则是混合了HTML元素和C#表达式的文件。
配置Startup来启用HTML响应。
services.AddControllersWithViews().AddRazorRuntimeCompilation();
之前使用AddController方法来启用MVC框架,但它只支持Web服务控制器。这里我们启用AddControllersWithViews方法来支持视图。使用AddRazorRuntimeCompilation方法是为了启用安装包所提供的功能,方便开发过程中使用视图。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute("Default", "{controller=Home}/{action=Index}/{id?}");
});
在端点路由配置中添加了MapControllerRoute方法,默认页面的路由。
2.2 创建html控制器
public class HomeController : Controller
{
private DataContext _context;
public HomeController(DataContext ctx)
{
_context = ctx;
}
public async Task<IActionResult> Index(long id =1)
{
return View(await _context.Products.FindAsync(1));
}
}
html控制器基类是Controller,它派生自Web服务控制器ControllerBase,并提供用于视图的方法。
(1)理解约定式路由
html控制器依赖于约定式路由,而不是Route特性。约定式路由指的是使用控制器名称和操作方法名称来配置路由系统。
endpoints.MapControllerRoute("Default", "{controller=Home}/{action=Index}/{id?}");
这条语句路由分三段,前面两个指代控制器和操作方法名称,而第三段允许收到一个名为id的参数。对于不包含所有片段的URL默认值将选择Home控制器的Index方法。这个是常用配置,也可以简写为下面这样。
endpoints.MapDefaultControllerRoute();
(2)理解Razor视图约定
当操作方法调用View方法时,将创建一个ViewResult,告诉MVC框架使用默认约定来寻找视图。Razor视图引擎将寻找一个与操作方法同名、且带有cshtml文件扩展名的视图。视图保存在Views文件中,按照关联的控制器进行分组。
因为操作方法时由Home控制器定义,所以首先搜索Views/Home文件夹。如果没有找到Index.cshtml,就检查Views/Shared文件夹,这里包含控制器之间的共享视图。
2.3 创建Razor视图
在Views/Home目录下创建Index.cshtml视图。
<!DOCTYPE html>
<html>
<head>
<link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<h6 class="bg-primary text-white text-center m-2 p-2">Product Table</h6>
<div class="m-2">
<table class="table table-sm table-striped table-bordered">
<tbody>
<tr><th>Name</th><td>@Model.Name</td></tr>
<tr><th>Price</th><td>@Model.Price.ToString("c")</td></tr>
<tr><th>Category ID</th><td>@Model.CategoryId</td></tr>
</tbody>
</table>
</div>
</body>
</html>
2.4 通过名称选择视图
通过View方法提供一个名称Watersports作为参数选择视图,同时创建Watersports.cshtml。
public async Task<IActionResult> Index(long id = 1)
{
var model = await _context.Products.FindAsync(id);
if (model.CategoryId == 1)
{
return View("Watersports", model);
}
return View(model);
}
使用共享视图
在Views/Shared文件夹中添加一个名为Common.cshtml的Razor视图文件。
<!DOCTYPE html>
<html>
<head>
<link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<h6 class="bg-secondary text-white text-center m-2 p-2">Shared View</h6>
</body>
</html>
在HomeController添加一个操作方法,它依赖于使用方法名称作为视图名称。
public IActionResult Common()
{
return View();
}
添加一个SecondController,它的一个操作方法返回View,参数使用Common。
public class SecondController : Controller
{
public IActionResult Index()
{
return View("Common");
}
}
可以看到结果,http://localhost:5000/home/common和http://localhost:5000/second都显示的是Common视图。
注意:也可以不用视图引擎选择文件,而自己指定相对路径的完整路径来选择某个视图。
return View("/Views/Shared/Common.cshtml");
3 使用Razor视图
Razor视图包含html表达式和C#表达式。混杂在html元素中,用@字符表示。Razor视图将被转换为C#类,它继承自RazorPage类,像其他C#类一样被编译。
可以通过obj/Debug/netcoreapp3.1/Razor/Views文件夹下内容,看到生成的视图类。
cshtml文件中的一个片段,会被 ExecuteAsync方法转换为一系列C#语句,再把html片段和表达式的计算结果写入响应得到最终html。
<tr><th>Name</th><td>@Model.Name</td></tr>
WriteLiteral("<tr><th>Name</th><td>");
Write(Model.Name);
WriteLiteral("</td></tr>");
设置视图模型类型
为Watersports.cshtml文件生成的类派生自RazorPage<T>
,但是Razor不知道操作方法为视图模型使用什么类型,所以选择了RazorPage<dynamic>
作为泛型类型实参。这样即便写一个不存在的属性编译也不会报错,直到运行时才会报错。
为在开发中检查表达式,可使用model关键字指定Model对象类型。这样当生成C#类时将使用RazorPage<Product>
这样作为泛型实参。
@model MyWebApp.Models.Product
<!DOCTYPE html>
......
使用视图导入文件
通过在项目中添加视图导入文件,能够指定在一组名称空间中搜索类型。视图导入文件的名称为_ViewImports.cshtml,包含在Views文件夹中。
@using MyWebApp.Models
通过使用@using表达式,并在后面加上名称空间,可以指定哪些名称空间中搜索Razor视图中使用的类,该名称空间包含Watersports.cshtml,就不用加MyWebApp.Models了。
@model Product
<!DOCTYPE html>
......
4 理解Razor语法
4.1 理解指令
指令是向Razor视图引擎发送命令的表达式。
- @model:指定视图模型的类型;
- @using:导入名称空间
- @page:指示Razor Page。
- @section:指示布局节。
- @addTagHelper:在视图中添加标签助手。
- @namespace:设置从视图生成C#类的名称空间。
- @functions:向从视图生成的C#类添加属性和方法,常用于Razor Page中。
- @attribute:向从视图生成的C#类添加特性。
- @implements:声明从视图生成的C#类继承某个接口,或派生自某个基类。
- @inherits:设置了从视图生成的C#类的基类。
- @inject:使视图能通过依赖注入直接访问某个服务。
4.2 理解内容表达式
Razor内容表达式生成的内容将包含在视图生成的输出中。
@<expression>
:这是基本的Razor表达式,其计算结果将被插入响应中。@if
、@switch
:条件表达式。@foreach
:为序列中每个元素生成内容。@{...}
:定义一个代码块。@:
:指示没有包含在html中的内容。@try
:用于捕获异常。@await
:用于执行异步操作,结果将被插入响应中。
4.3 设置元素内容
视图可包含更复杂表达式,但要求把这些表达式放到括号内,以便区分代码和静态内容。
<tr><th>Tax</th><td>@Model.Price * 0.2m</td></tr>
<tr><th>Tax</th><td>@(Model.Price * 0.2m)</td></tr>
4.4 设置特性值
表达式可用于设置元素特性的值。
<table class="table table-sm table-striped table-bordered" data-id="@Model.ProductId">
4.5 使用条件表达式
使用@if。
@if (Model.Price > 200)
{
<tr><th>Name</th><td>Luxury @Model.Name</td></tr>
}
else
{
<tr><th>Name</th><td>Basic @Model.Name</td></tr>
}
使用@switch,@:前缀来处理未包含在html元素中的字面量值
<tr>
<th>Name</th>
<td>
@switch (Model.Name)
{
case "Kayak":
@:Small Boat
break;
case "Lifejacket":
@:Flotation Aid
break;
default:
@Model.Name
break;
}
</td>
</tr>
4.6 枚举序列
HomeController中添加List方法。
public IActionResult List()
{
return View(_context.Products);
}
添加List.cshtml.
@model IEnumerable<Product>
<!DOCTYPE html>
<html>
<head>
<link href="/lib/twitter-bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<h6 class="bg-secondary text-white text-center m-2 p-2">Products</h6>
<div class="m-2">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr><th>Name</th><th>Price</th><th></th></tr>
</thead>
<tbody>
@foreach (Product p in Model)
{
<tr>
<td>@p.Name</td>
<td>@p.Price</td>
</tr>
}
</tbody>
</table>
</div>
</body>
</html>
4.7 使用Razor代码块
代码块是一段C#代码,它不生成内容,但能够为那些生成内容的表达式提供支持。
@{
decimal average = Model.Average(a => a.Price);
}
<td>平均值的@((p.Price / average * 100).ToString("F1"))% </td>