使用 ASP.NET Core 创建 Razor Pages Web 应用
此系列教程介绍了生成 Razor Pages Web 应用的基础知识。
有关面向熟悉控制器和视图的开发人员的更高级介绍,请参阅 Razor Pages 简介。
结束时,你将有一个应用来显示和管理电影数据库。
1、创建 Razor Pages Web 应用 跳转至 下一知识点
先决条件
- Visual Studio
- Visual Studio Code
- Visual Studio for Mac
- 具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2019 16.4 或更高版本
-
.NET Core 3.1 SDK 或更高版本
创建 Razor Pages Web 应用
-
从 Visual Studio“文件” 菜单中选择“新建” >“项目” 。
-
创建新的 ASP.NET Core Web 应用程序,然后选择“下一步
-
将项目命名为“RazorPagesMovie” 。 将项目命名为“RazorPagesMovie”非常重要,这样在复制和粘贴代码时命名空间就会匹配 。
-
-
在下拉列表中选择“ASP.NET Core 3.1”,然后依次选择“Web 应用程序”和“创建” 。
创建以下初学者项目:
运行应用
-
按 Ctrl+F5 以在不使用调试程序的情况下运行。
Visual Studio 会显示以下对话框:
如果信任 IIS Express SSL 证书,请选择“是” 。
将显示以下对话框:
如果你同意信任开发证书,请选择“是”。
Visual Studio 启动 IIS Express 并运行应用。 地址栏显示
localhost:port#
,而不是显示example.com
。 这是因为localhost
是本地计算机的标准主机名。 Localhost 仅为来自本地计算机的 Web 请求提供服务。 Visual Studio 创建 Web 项目时,为 Web 服务器使用随机端口。
检查项目文件
下面是主项目文件夹和文件的概述,将在后续教程中使用。
Pages 文件夹
包含 Razor 页面和支持文件。 每个 Razor 页面都是一对文件:
- 一个 .cshtml 文件,其中包含使用 Razor 语法的 C# 代码的 HTML 标记 。
- 一个 .cshtml.cs 文件,其中包含处理页面事件的 C# 代码 。
支持文件的名称以下划线开头。 例如,_Layout.cshtml 文件可配置所有页面通用的 UI 元素 。此文件设置页面顶部的导航菜单和页面底部的版权声明。 有关详细信息,请参阅 ASP.NET Core 中的布局。
wwwroot 文件夹
包含静态文件,如 HTML 文件、JavaScript 文件和 CSS 文件。 有关详细信息,请参阅 ASP.NET Core 中的静态文件。
appSettings.json
包含配置数据,如连接字符串。 有关详细信息,请参阅 ASP.NET Core 中的配置。
Program.cs
包含程序的入口点。 有关详细信息,请参阅 .NET 通用主机。
Startup.cs
包含配置应用行为的代码。 有关详细信息,请参阅 ASP.NET Core 中的应用启动。
2、向 Razor Pages 应用添加模型 跳转至 上个任务点 下个任务点
在 ASP.NET Core 中向 Razor Pages 应用添加模型
在该部分,会添加类管理影片。 应用的模型类使用 Entity Framework Core (EF Core) 来处理数据库。 EF Core 是一种对象关系映射器 (ORM),可简化数据访问。
模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系。 它们定义数据库中存储的数据属性。
添加数据模型
右键单击“RazorPagesMovie” 项目 >“添加” > “新建文件夹” 。 将文件夹命名为“Models” 。
右键单击“Models”文件夹 。 选择“添加” > “类” 。 将类命名“Movie” 。
向 Movie
类添加以下属性:
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Movie
类包含:
-
数据库需要
ID
字段以获取主键。 -
[DataType(DataType.Date)]
:DataType 属性指定数据的类型(日期)。 通过此特性:- 用户无需在数据字段中输入时间信息。
- 仅显示日期,而非时间信息。
DataAnnotations 会在后续教程中介绍。
生成项目以验证没有任何编译错误。
搭建“电影”模型的基架
在此部分,将搭建“电影”模型的基架。 确切地说,基架工具将生成页面,用于对“电影”模型执行创建、读取、更新和删除 (CRUD) 操作。
创建“Pages/Movies”文件夹 :
- 右键单击“Pages” 文件夹 >“添加” >“新建文件夹” 。
- 将文件夹命名为“Movies”
右键单击“Pages/Movies” 文件夹 >“添加” >“新搭建基架的项目” 。
在“添加基架” 对话框中,依次选择“使用实体框架的 Razor Pages (CRUD)” >“添加” 。
完成“使用实体框架(CRUD)添加 Razor Pages”对话框 :
- 在“模型类”下拉列表中,选择“Movie (RazorPagesMovie.Models) 。
- 在“数据上下文类”行中,选择 +(加号)并将生成的名称从 RazorPagesMovie.Models .RazorPagesMovieContext 更改为 RazorPagesMovie.Data .RazorPagesMovieContext 。 不需要此更新。 它创建具有正确命名空间的数据库上下文类。
- 选择“添加” 。
appsettings.json 文件通过用于连接到本地数据的连接字符串进行更新。
创建的文件
在搭建基架时,会创建并更新以下文件:
- Pages/Movies:“创建”、“删除”、“详细信息”、“编辑”和“索引”。
- Data/RazorPagesMovieContext.cs
已更新
- Startup.cs
创建和更新的文件将在下一节中说明。
初始迁移
在此部分中,程序包管理器控制台 (PMC) 用于:
- 添加初始迁移。
- 使用初始迁移来更新数据库。
从“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台” 。
在 PMC 中,输入以下命令:
Add-Migration InitialCreate
Update-Database
前面的命令生成以下警告:“No type was specified for the decimal column 'Price' on entity type 'Movie'. This will cause values to be silently truncated if they do not fit in the default precision and scale. Explicitly specify the SQL server column type that can accommodate all the values using 'HasColumnType()'.”
你可以忽略该警告,它将后面的教程中得到修复。
migrations 命令生成用于创建初始数据库架构的代码。 该架构基于在 DbContext
中指定的模型。 InitialCreate
参数用于为迁移命名。可以使用任何名称,但是按照惯例,会选择可说明迁移的名称。
update
命令在尚未应用的迁移中运行 Up
方法。 在这种情况下,update
在用于创建数据库的 Migrations/<time-stamp>_InitialCreate.cs 文件中运行 Up
方法 。
检查通过依赖关系注入注册的上下文
ASP.NET Core 通过依赖关系注入进行生成。 服务(例如 EF Core 数据库上下文)在应用程序启动期间通过依赖关系注入进行注册。 需要这些服务(如 Razor 页面)的组件通过构造函数提供相应服务。 本教程的后续部分介绍了用于获取 DB 上下文实例的构造函数代码。
基架工具自动创建 DB 上下文并将其注册到依赖关系注入容器。
检查 Startup.ConfigureServices
方法。 基架添加了突出显示的行:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("RazorPagesMovieContext")));
}
RazorPagesMovieContext
为 Movie
模型协调 EF Core 功能(创建、读取、更新、删除等)。 数据上下文 (RazorPagesMovieContext
) 派生自 Microsoft.EntityFrameworkCore.DbContext。 数据上下文指定数据模型中包含哪些实体。
using Microsoft.EntityFrameworkCore;
namespace RazorPagesMovie.Models
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; }
}
}
前面的代码为实体集创建 DbSet<Movie> 属性。 在实体框架术语中,实体集通常与数据表相对应。 实体对应表中的行。
通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文。 进行本地开发时, ASP.NET Core 配置系统 在 appsettings.json 文件中读取数据库连接字符串。
测试应用
- 运行应用并将
/Movies
追加到浏览器中的 URL (http://localhost:port/movies
)。
如果收到错误:
SqlException: Cannot open database "RazorPagesMovieContext-GUID" requested by the login. The login failed.
Login failed for user 'User-name'.
缺少迁移步骤。
-
测试“创建” 链接。
运行结果
3、基架(生成)Razor Pages 跳转至 上个任务点 下个任务点
ASP.NET Core 中已搭建基架的 Razor 页面
“创建”、“删除”、“详细信息”和“编辑”页面
检查 Pages/Movies/Index.cshtml.cs 页面模型:
// Unused usings removed.
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; }
public async Task OnGetAsync()
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor 页面派生自 PageModel
。 按照约定,PageModel
派生的类称为 <PageName>Model
。 此构造函数使用依赖关系注入将 RazorPagesMovieContext
添加到页。 所有已搭建基架的页面都遵循此模式。 请参阅异步代码,了解有关使用实体框架的异步编程的详细信息。
对页面发出请求时,OnGetAsync
方法向 Razor 页面返回影片列表。 调用 OnGetAsync
或 OnGet
以初始化页面的状态。 在这种情况下,OnGetAsync
将获得影片列表并显示出来。
当 OnGet
返回 void
或 OnGetAsync
返回 Task
时,不使用任何返回语句。 当返回类型是 IActionResult
或 Task<IActionResult>
时,必须提供返回语句。 例如,Pages/Movies/Create.cshtml.cs OnPostAsync
方法 :
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
检查 Pages/Movies/Index.cshtml Razor 页面 :
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Razor 可以从 HTML 转换为 C# 或 Razor 特定标记。 当 @
符号后跟 Razor 保留关键字时,它会转换为 Razor 特定标记,否则会转换为 C#。
@page 指令
@page
Razor 指令将文件转换为一个 MVC 操作,这意味着它可以处理请求。 @page
必须是页面上的第一个 Razor 指令。 @page
是转换到 Razor 特定标记的一个示例。有关详细信息,请参阅 Razor 语法。
检查以下 HTML 帮助程序中使用的 Lambda 表达式:
@Html.DisplayNameFor(model => model.Movie[0].Title)
DisplayNameFor
HTML 帮助程序检查 Lambda 表达式中引用的 Title
属性来确定显示名称。 检查 Lambda 表达式(而非求值)。 这意味着当 model
、model.Movie
或 model.Movie[0]
为 null
或为空时,不会存在任何访问冲突。 对 Lambda 表达式求值时(例如,使用 @Html.DisplayFor(modelItem => item.Title)
),将求得该模型的属性值。
@model 指令
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@model
指令指定传递给 Razor 页面的模型类型。 在前面的示例中,@model
行使 PageModel
派生的类可用于 Razor 页面。 在页面上的 @Html.DisplayNameFor
和 @Html.DisplayFor
HTML 帮助程序中使用该模型。
布局页
选择菜单链接(“RazorPagesMovie” 、“主页” 和“隐私” )。 每页显示相同的菜单布局。 菜单布局是在 Pages/Shared/_Layout.cshtml 文件中实现。 打开 Pages/Shared/_Layout.cshtml 文件。
布局模板允许 HTML 容器具有如下布局:
- 在一个位置指定。
- 应用于站点中的多个页面。
查找 @RenderBody()
行。 RenderBody
是显示全部页面专用视图的占位符,已包装 在布局页中。 例如,选择“隐私” 链接后,Pages/Privacy.cshtml 视图在 RenderBody
方法中呈现。
ViewData 和布局
考虑来自 Pages/Movies/Index.cshtml 文件中的以下标记:
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
前面突出显示的标记是 Razor 转换为 C# 的一个示例。 {
和 }
字符括住 C# 代码块。
PageModel
基类包含 ViewData
字典属性,可用于将数据传递到某个视图。 可以使用键/值模式将对象添加到 ViewData
字典。 在前面的示例中,"Title"
属性被添加到 ViewData
字典。
"Title"
属性用于 Pages/Shared/_Layout.cshtml 文件 。 以下标记显示 _Layout.cshtml 文件的前几行 。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
@*Markup removed for brevity.*@
行 @*Markup removed for brevity.*@
为 Razor 注释。 与 HTML 注释不同 (<!-- -->
),Razor 注释不会发送到客户端。
更新布局
更改 Pages/Shared/_Layout.cshtml 文件中的 <title>
元素以显示 Movie 而不是 RazorPagesMovie 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie</title>
在 Pages/Shared/_Layout.cshtml 文件中,查找以下定位点元素。
<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
将前面的元素替换为以下标记:
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
前面的定位点元素是一个标记帮助程序。 此处它是定位点标记帮助程序。 asp-page="/Movies/Index"
标记帮助程序属性和值可以创建指向 /Movies/Index
Razor 页面的链接。 asp-area
属性值为空,因此在链接中未使用区域。 有关详细信息,请参阅区域。
保存所做的更改,并通过单击“RpMovie” 链接测试应用。 如果遇到任何问题,请参阅 GitHub 中的 _Layout.cshtml 文件。
测试其他链接(“主页” 、“RpMovie” 、“创建” 、“编辑” 和“删除” )。 每个页面都设置有标题,可以在浏览器选项卡中看到标题。将某个页面加入书签时,标题用于该书签。
备注
可能无法在 Price
字段中输入十进制逗号。 若要使 jQuery 验证支持使用逗号(“,”)表示小数点的的非英语区域设置,以及支持非美国英语日期格式,必须执行使应用全球化的步骤。 有关添加十进制逗号的说明,请参阅 GitHub 问题 4076。
在 Pages/_ViewStart.cshtml 文件中设置 Layout
属性:
@{
Layout = "_Layout";
}
前面的标记针对所有 Razor 文件将布局文件设置为 Pages 文件夹下的 Pages/Shared/_Layout.cshtml 。 请参阅布局了解详细信息。
“创建”页面模型
检查 Pages/Movies/Create.cshtml.cs 页面模型:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System;
using System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
OnGet
方法初始化页面所需的任何状态。 “创建”页没有任何要初始化的状态,因此返回 Page
。 在本教程的后面部分中,将介绍 OnGet
初始化状态的示例。 Page
方法创建用于呈现 Create.cshtml 页的 PageResult
对象。
Movie
属性使用 [BindProperty]
特性来选择加入模型绑定。 当“创建”表单发布表单值时,ASP.NET Core 运行时将发布的值绑定到 Movie
模型。
当页面发布表单数据时,运行 OnPostAsync
方法:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
如果不存在任何模型错误,将重新显示表单,以及发布的任何表单数据。 在发布表单前,可以在客户端捕获到大部分模型错误。 模型错误的一个示例是,发布的日期字段值无法转换为日期。 本教程后面讨论了客户端验证和模型验证。
如果不存在模型错误,将保存数据,并且浏览器会重定向到索引页。
创建 Razor 页面
检查 Pages/Movies/Create.cshtml Razor 页面文件:
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label"></label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio 以用于标记帮助程序的特殊加粗字体显示以下标记:
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
<form method="post">
元素是一个表单标记帮助程序。 表单标记帮助程序会自动包含防伪令牌。
基架引擎在模型中为每个字段(ID 除外)创建 Razor 标记,如下所示:
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
验证标记帮助程序(<div asp-validation-summary
和 <span asp-validation-for
)显示验证错误。 本系列后面的部分将更详细地讨论有关验证的信息。
标签标记帮助程序 (<label asp-for="Movie.Title" class="control-label"></label>
) 生成标签描述和 Title
属性的 for
特性。
输入标记帮助程序 (<input asp-for="Movie.Title" class="form-control">
) 使用 DataAnnotations 属性并在客户端生成 jQuery 验证所需的 HTML 属性。
有关标记帮助程序(如 <form method="post">
)的详细信息,请参阅 ASP.NET Core 中的标记帮助程序。
5、更新 Razor Pages 跳转至 上个任务点 下个任务点
8、添加验证 跳转至 上个任务点