1 创建 Razor 页面 Web 应用
选择“ASP.NET Core Web 应用”模板
“项目名称”输入 RazorPagesMovie
选择“.NET 6.0 (长期支持)”
取消https
2 模板文件夹介绍
2.1 Pages 文件夹
包含 Razor 页面和支持文件。
每个 Razor 页面都是一对文件
支持文件的名称以下划线开头。 例如,_Layout.cshtml
文件可配置所有页面通用的 UI 元素。 此文件设置页面顶部的导航菜单和页面底部的版权声明。
2.2 wwwroot 文件夹
包含静态资产,如 HTML 文件、JavaScript 文件和 CSS 文件。
2.3 appsettings.json
包含配置数据,如连接字符串。
2.4 Program.cs
默认内容
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
3 添加数据模型
3.1 Movie模型
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; } = string.Empty;
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
public decimal Price { get; set; }
}
}
3.2 通过基架工具生成页面
先要添加文件夹Movies
nuget包: Microsoft.EntityFrameworkCore.Design
右键单击 Pages/Movies 文件夹 >“添加”>“已搭建基架的新项”
使用实体框架的 Razor Pages (CRUD)
“模型类”下拉列表中,选择“Movie (RazorPagesMovie.Models)”
“添加数据上下文”对话框中,生成类名 RazorPagesMovie.Data.RazorPagesMovieContext
安装 Microsoft.EntityFrameworkCore.SqlServer
包
会生成如下文件:
Pages/Movies:“创建”、“删除”、“详细信息”、“编辑”和“索引”。
Data/RazorPagesMovieContext.cs
并会生成数据库连接配置
会注册RazorPagesMovieContext到容器
3.3 数据库迁移
# 第一次迁移
Add-Migration init
# 第一次更新数据库会创建数据库
Update-Database
3.4 初始化数据种子
SeedData.cs
using Microsoft.EntityFrameworkCore;
using RazorPageTest1.Models.MovieSystem;
namespace RazorPageTest1.Data
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPageTest1DbContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPageTest1DbContext>>()))
{
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();
}
}
}
}
项目启动时初始化种子:
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
SeedData.Initialize(services);
}
4 默认生成的页面
修改Movie模型
[Display(Name = “Release Date”)]
和
[Column(TypeName = “decimal(18, 2)”)]
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
}
更新“编辑”、“详细信息”和“删除”Razor 页面以使用 {id:int}
路由模板。
@page 更改为 @page "{id:int}"
@page 更改为 @page "{id:int?}"
4.1 默认的并发异常处理
多个客户端的编辑按调用 SaveChanges
的顺序应用,后来应用的编辑可能会覆盖早期编辑的旧值。
如果编辑过程中,这条数据被删除了,会返回404
如果编辑过程中,这条数据被修改了,会保存最后修改的数据(所有字段)
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return (_context.Movie?.Any(e => e.ID == id)).GetValueOrDefault();
}
5 添加了按流派或名称搜索电影
Pages/Movies/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using RazorPageTest1.Models.MovieSystem;
namespace RazorPageTest1.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPageTest1.Data.RazorPageTest1DbContext _context;
public IndexModel(RazorPageTest1.Data.RazorPageTest1DbContext context)
{
_context = context;
}
//筛选电影标题
[BindProperty(SupportsGet =true)]
public string? SearchString { get; set; }
/// <summary>
/// 类别列表
/// </summary>
public SelectList? Genres { get; set; }
[BindProperty(SupportsGet = true)]
//筛选电影类别
public string? MovieGenre { get; set; }
//电影列表数据
public IList<Movie> Movie { get;set; } = default!;
public async Task OnGetAsync()
{
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
var movies=from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}
if (!string.IsNullOrEmpty(MovieGenre))
{
movies = movies.Where(x => x.Genre == MovieGenre);
}
Movie = await movies.ToListAsync();
}
}
}
增加筛选表单
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
6 向电影模型添加分级属性
public string Rating { get; set; } = string.Empty;
public class Movie
{
public int ID { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
6.1 修改视图
Pages/Movies/Index.cshtml
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
Pages/Movies/Create.cshtml。
Pages/Movies/Delete.cshtml。
Pages/Movies/Details.cshtml。
Pages/Movies/Edit.cshtml
同样修改
6.2 使用 Code First 迁移更新数据库架构
add-migration movie_rating_add
update-database
修改数据种子
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
Rating="R"
},
7 将验证规则添加到电影模型
System.ComponentModel.DataAnnotations 命名空间提供以下内容:
- 内置验证特性,可通过声明方式应用于类或属性。
[DataType]
等格式特性,这些特性可帮助进行格式设置,但不提供任何验证。
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; } = string.Empty;
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; } = string.Empty;
}
}
7.1 客户端验证
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Shared/_ValidationScriptsPartial.cshtml
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<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>
7.2 服务器端验证
浏览器中禁用 JavaScript
if (!ModelState.IsValid)
{
return Page();
}
7.3 验证特性和格式特性
验证特性
[Required] 和 [MinimumLength] 特性指示属性必须具有一个值。 不阻止用户输入空格来满足此验证。
[RegularExpression] 特性用于限制可输入的字符。 在上述代码中,Genre:
只能使用字母。
第一个字母必须为大写。 允许使用空格,但不允许使用数字和特殊字符。
RegularExpressionRating:
要求第一个字符为大写字母。
允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“Genre”无效。
[Range] 特性将值限制在指定的范围内。
[StringLength] 特性可以设置字符串属性的最大长度,以及可选的最小长度。
从本质上来说,需要值类型(如 decimal、int、float、DateTime),但不需要 [Required] 特性。
格式特性
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
7.4 数据库迁移
Add-Migration add_validate
Update-Database