您现在的位置是:首页 >技术教程 >ASP.NET Core系列学习(2)网站首页技术教程
ASP.NET Core系列学习(2)
Asp.net Core 6系列学习
Razor Pages Web 应用概述
Razor Pages Web 应用的基础知识
一、入门
概述了将在后续教程中使用的主要项目文件夹和文件。
1.1 Pages 文件夹
包含 Razor 页面和支持文件。 每个 Razor 页面都是一对文件:
- 一个 .cshtml 文件,其中包含使用 Razor 语法的 C# 代码的 HTML 标记。
- 一个 .cshtml.cs 文件,其中包含处理页面事件的 C# 代码。
支持文件的名称以下划线开头。 例如,_Layout.cshtml 文件可配置所有页面通用的 UI 元素。 此文件设置页面顶部的导航菜单和页面底部的版权声明。
1.2 wwwroot文件夹
包含静态资产,如 HTML 文件、JavaScript 文件和 CSS 文件。
1.3 appsettings.json
包含配置数据,如连接字符串。
1.4 Program.cs
var builder = WebApplication.CreateBuilder(args);
// 将服务添加到容器中.
builder.Services.AddRazorPages();
var app = builder.Build();
// 配置HTTP请求管道.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// 默认的HSTS值为30天.
app.UseHsts();
}
app.UseHttpsRedirection();//将 HTTP 请求重定向到 HTTPS。
app.UseStaticFiles();//使能够提供 HTML、CSS、映像和 JavaScript 等静态文件。
app.UseRouting();//向中间件管道添加路由匹配。
app.UseAuthorization();//向中间件管道添加路由匹配。
app.MapRazorPages();//授权用户访问安全资源。 此应用不使用授权,因此可删除此行。
app.Run();//运行应用。
此文件中的以下代码行会创建一个带有预配置默认值的 WebApplicationBuilder,向依赖项注入 (DI) 容器添加 Razor Pages 支持,并生成应用:
var builder = WebApplication.CreateBuilder(args);
// 将服务添加到容器中.
builder.Services.AddRazorPages();
var app = builder.Build();
开发人员异常页默认启用,并提供有关异常的有用信息。 生产应用不得在开发模式中运行,原因是开发人员异常页可能会泄露敏感信息。
下面的代码会将异常终结点设置为 /Error,并且当应用未在开发模式中运行时,启用 HTTP 严格传输安全协议 (HSTS):
// 配置HTTP请求管道.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// 默认的HSTS值为30天.
app.UseHsts();
}
二、添加模型
在本教程中,添加了用于管理数据库中的电影的类。 应用程序的模型类使用 Entity Framework Core (EF Core) 来处理数据库。 EF Core 是一种对象关系映射器 (O/RM),可简化数据访问。
首先要编写模型类,然后 EF Core 将创建数据库。
模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系。 它们定义数据库中存储的数据属性。
2.1 添加数据模型
-
在“解决方案资源管理器”中,右键单击“RazorPagesMovie”项目 >“添加”>“新建文件夹”。 将该文件夹命名为 Models注册一个免费试用帐户。
-
右键单击 Models 文件夹。 选择“添加”>“类” 。 将类命名“Movie”。
-
向 Movie 类添加以下属性:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }//数据库需要 ID 字段以获取主键。
public string Title { get; set; } = string.Empty;
[DataType(DataType.Date)]
//[DataType] 属性,用于指定 ReleaseDate 属性中的数据类型。 通过此特性:
//用户无需在日期字段中输入时间信息。
//仅显示日期,而非时间信息。
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}
2.2 搭建“电影”模型的基架
在此部分,将搭建“电影”模型的基架。 确切地说,基架工具将生成页面,用于对“电影”模型执行创建、读取、更新和删除 (CRUD) 操作。
- 添加基架工具所需的 NuGet 包 Microsoft.EntityFrameworkCore.Design。
- 在“工具”菜单中,依次选择“NuGet 包管理器”>“管理解决方案的 NuGet 包”
- 选择“浏览”选项卡
- 输入并从列表中选择 Microsoft.EntityFrameworkCore.Design
- 选中“项目”,然后选择“安装”
- 在“许可证接受”对话框中,选择“我接受” - 创建“Pages/Movies”文件夹:
- 右键单击 Pages 文件夹 >“添加”>“新建文件夹”
- 将文件夹命名为“Movies” - 右键单击 Pages/Movies 文件夹 >“添加”>“已搭建基架的新项”
- 在“添加新基架”对话框中,依次选择“使用实体框架的 Razor Pages (CRUD)”>“添加”
- 完成“添加使用实体框架的 Razor 页面 (CRUD)”对话框:
- 在“模型类”下拉列表中,选择“Movie (RazorPagesMovie.Models)”
- 在“数据上下文类”行中,选择 +(加号)在“添加数据上下文”对话框中,生成类名 RazorPagesMovie.Data.RazorPagesMovieContext
- 选择 添加
2.3 创建和更新的文件
在搭建基架时,会创建以下文件:
- Pages/Movies:“创建”、“删除”、“详细信息”、“编辑”和“索引”。
- Data/RazorPagesMovieContext.cs
基架进程将以下显示的代码添加到 Program.cs 文件中:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext") ?? throw new InvalidOperationException("Connection string 'RazorPagesMovieContext' not found.")));
三.使用 EF 的迁移功能创建初始数据库架构
Entity Framework Core 中的迁移功能提供了一种方法来执行以下操作:
- 创建初始数据库架构。
- 以增量的方式更新数据库架构,使其与应用的数据模型保持同步。 保存数据库中的现有数据。
在此部分中,程序包管理器控制台 (PMC) 窗口用于:
- 添加初始迁移。
- 使用初始迁移来更新数据库。
- 在“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台”。
- 在PMC中,输入以下命令:
Add-Migration InitialCreate
Update-Database
上述命令安装 Entity Framework Core 工具,并运行 migrations 命令以生成可创建初始数据库架构的代码。
3.1 检查通过依赖关系注入注册的上下文
ASP.NET Core 通过依赖关系注入进行生成。 在应用程序启动过程中通过依赖关系注入注册相关服务(例如 EF Core 数据库上下文)。 需要这些服务(如 Razor Pages)的组件通过构造函数参数提供。 本教程的后续部分介绍了用于获取数据库上下文实例的构造函数代码。
基架工具自动创建数据库上下文并将其注册到依赖关系注入容器。 基架将以下显示的代码添加到 Program.cs 文件中:
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext") ?? throw new InvalidOperationException("Connection string 'RazorPagesMovieContext' not found.")));
数据上下文 RazorPagesMovieContext:
- 派生自 Microsoft.EntityFrameworkCore.DbContext。
- 指定数据模型中包含哪些实体。
- 为 Movie 模型协调 EF Core 功能,例如“创建”、“读取”、“更新”和“删除”。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
public DbSet<RazorPagesMovie.Models.Movie>? Movie { get; set; }
}
}
前面的代码为实体集创建 DbSet 属性。 在实体框架术语中,实体集通常与数据表相对应。 实体对应表中的行。
通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文。 进行本地开发时,配置系统在 appsettings.json 文件中读取连接字符串。
四、基架
4.1 “创建”、“删除”、“详细信息”和“编辑”页面
Razor 页面派生自 PageModel。 按照约定,PageModel 派生的类称为 PageNameModel。 例如,“索引”页命名为 IndexModel。
检查 Pages/Movies/Index.cshtml.cs 页面模型:
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
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; } = default!;
public async Task OnGetAsync()
{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}
}
此构造函数使用依赖关系注入将 RazorPagesMovieContext 添加到页面:
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
对页面发出请求时,OnGetAsync 方法向 Razor 页面返回影片列表。 OnGetAsync 或 OnGet 在 Razor 页面上调用,以初始化该页面的状态。 在这种情况下,OnGetAsync 将获得影片列表并显示出来。
当 OnGet 返回 void 或 OnGetAsync 返回 Task 时,不使用任何返回语句。 例如,检查 Privacy 页面:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
当返回类型是 IActionResult 或 Task 时,必须提供返回语句。 例如,Pages/Movies/Create.cshtml.csOnPostAsync 方法:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
检查 Pages/Movies/Index.cshtmlRazor 页面:
@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#。
4.2 @page 指令
@pageRazor 指令将文件转换为一个 MVC 操作,这意味着它可以处理请求。 @page 必须是页面上的第一个 Razor 指令。 @page 和 @model 是转换为 Razor 特定标记的示例。
4.3 @model 指令
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@model 指令指定传递到 Razor 页面的模型类型。 在前面的示例中,@model 行使 PageModel 派生的类可用于 Razor 页面。 在页面上的 @Html.DisplayNameFor 和 @Html.DisplayForHTML 帮助程序中使用该模型。
HTML 帮助程序:HTML 帮助程序只是返回字符串的方法。 字符串可以表示所需的任何类型的内容。
( 例如,可以使用 HTML 帮助程序来呈现标准 HTML 标记,如 HTML 和 标记。 还可以使用 HTML 帮助程序来呈现更复杂的内容,例如制表符条或 HTML 数据库数据表。)
检查以下 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)),将求得该模型的属性值。
4.4 布局页
选择菜单链接(“RazorPagesMovie”、“Home”和“Privacy”)。 每页显示相同的菜单布局。 菜单布局在 Pages/Shared/_Layout.cshtml 文件中实现。
打开并检查 Pages/Shared/_Layout.cshtml 文件。
布局模板允许 HTML 容器具有如下布局:
- 在一个位置指定。
- 应用于站点中的多个页面。
查找 @RenderBody() 行。 RenderBody 是显示全部页面专用视图的占位符,已包装在布局页中。 例如,选择 Privacy 链接后,Pages/Privacy.cshtml 视图在 RenderBody 方法中呈现。
4.5 ViewData和布局
考虑来自 Pages/Movies/Index.cshtml 文件的以下标记:
@{
ViewData["Title"] = "Index";
}
前面突出显示的标记是 Razor 转换为 C# 的一个示例。 { 和 } 字符括住 C# 代码块。
PageModel 基类包含 ViewData 字典属性,可用于将数据传递到某个视图。 可以使用键值模式将对象添加到 ViewData 字典。 在前面的示例中,Title 属性被添加到 ViewData 字典。
Title 属性用于 Pages/Shared/_Layout.cshtml 文件。 以下标记显示 _Layout.cshtml 文件的前几行。
<!DOCTYPE html>
<html lang="en">
<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.*@
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
行 @Markup removed for brevity.@ 是 Razor 注释。 与 HTML 注释 不同,Razor 注释不会发送到客户端。
4.6 更新布局
- 更改 Pages/Shared/_Layout.cshtml 文件中的
元素以显示 Movie 而不是 RazorPagesMovie。
<title>@ViewData["Title"] - Movie</title>
- 查找 Pages/Shared/_Layout.cshtml 文件中的以下定位点元素。
<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>
3.将前面的元素替换为以下标记:
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
前面的定位点元素是一个标记帮助程序。 此处它是定位点标记帮助程序。 asp-page=“/Movies/Index” 标记帮助程序属性和值可以创建指向 /Movies/Index Razor 页面的链接。 asp-area 属性值为空,因此在链接中未使用区域。
4.保存所做的更改,并通过选择“RpMovie”链接测试应用。
5.测试“Home”、“RpMovie”、“创建”、“编辑”和“删除”链接。 每个页面都设置有标题,可以在浏览器选项卡中看到标题。将某个页面加入书签时,标题用于该书签。
在 Pages/_ViewStart.cshtml 文件中设置 Layout 属性:
@{
Layout = "_Layout";
}
前面的标记针对 Pages 文件夹下的所有 Razor 文件将布局文件设置为 Pages/Shared/_Layout.cshtml。
4.7 "创建"页面模型
检查 Pages/Movies/Create.cshtml.cs 页面模型:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
//OnGet 方法初始化页面所需的任何状态。 “创建”页没有任何要初始化的状态,因此返回 Page
public IActionResult OnGet()
{
return Page();//Page 方法创建用于呈现 Create.cshtml 页的 PageResult 对象。
}
[BindProperty]//Movie 属性使用 [BindProperty] 特性来选择加入模型绑定。
// 当“创建”表单发布表单值时,ASP.NET Core 运行时将发布的值绑定到 Movie 模型。
public Movie Movie { get; set; } = default!;
//当页面发布表单数据时,运行 OnPostAsync 方法:
public async Task<IActionResult> OnPostAsync()
{
//如果不存在任何模型错误,将重新显示表单,以及发布的任何表单数据。
if (!ModelState.IsValid || _context.Movie == null || Movie == null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
4.8 “创建 Razor”页面
检查 Pages/Movies/Create.cshtmlRazor 页面文件:
@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">@*元素是一个表单标记帮助程序。 表单标记帮助程序会自动包含防伪令牌。*@
@*基架引擎在模型中为每个字段(ID 除外)创建 Razor 标记,如下所示:*@
@*div asp-validation-summary 和 span asp-validation-for显示验证错误 *@
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
@*label asp-for="Movie.Title" class="control-label" 生成标签描述和 Title 属性的 [for] 特性。*@
<label asp-for="Movie.Title" class="control-label"></label>
@*input asp-for="Movie.Title" class="form-control" 使用 DataAnnotations 属性并在客户端生成 jQuery 验证所需的 HTML 属性。*@
<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");}
}
五、使用数据库
5.1 SQL Server Express LocalDB
RazorPagesMovieContext 对象处理连接到数据库并将 Movie 对象映射到数据库记录的任务。 向 中的依赖关系注入容器注册数据库上下文:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
//将服务添加到容器中。
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPagesMovieContext") ?? throw new InvalidOperationException("Connection string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
ASP.NET Core 配置系统会读取 键。 进行本地开发时,配置从 appsettings.json 文件获取连接字符串。
生成的连接字符串类似于以下 JSON:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
//将应用部署到测试或生产服务器时,可以使用环境变量将连接字符串设置为测试或生产数据库服务器。
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=(localdb)\mssqllocaldb;Database=RazorPagesMovieContext-bc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
LocalDB 是轻型版的 SQL Server Express 数据库引擎,以程序开发为目标。 LocalDB 作为按需启动并在用户模式下运行的轻量级数据库没有复杂的配置。 默认情况下,LocalDB 数据库在 C:Users<user> 目录下创建 *.mdf 文件。
- 从“视图”菜单中,打开“SQL Server 对象资源管理器”(SSOX) 。
- 右键单击 Movie 表,然后选择“视图设计器”:( 默认情况下,EF 为该主键创建一个名为 ID 的属性。)
- 右键单击 Movie 表,然后选择“查看数据”。
5.2 设定数据库种子
使用以下代码在 Models 文件夹中创建一个名为 SeedData 的新类:
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
namespace RazorPagesMovie.Models
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null RazorPagesMovieContext");
}
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded 数据库已接种
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
如果数据库中有任何电影,则会返回种子初始值设定项,并且不会添加任何电影。
if (context.Movie.Any())
{
return;
}
5.3 添加种子初始值设定项
使用下面突出显示的代码更新 Program.cs:
using RazorPagesMovie.Models;
...
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
SeedData.Initialize(services);
}
在上述代码中,修改了 Program.cs 来执行以下操作:
- 从依赖注入 (DI) 容器中获取数据库上下文实例。
- 调用 seedData.Initialize 方法,并向其传递数据库上下文实例。
- Seed 方法完成时释放上下文。 using 语句将确保释放上下文。
未运行 Update-Database 时出现以下异常:
SqlException: Cannot open database “RazorPagesMovieContext-” requested by the login. The login failed. Login failed for user ‘user name’.