本示例是根据官网文档创建一个管理电影数据库的 Razor Pages Web 应用

创建Razor Pages Web 应用

高性能asp.net网站 asp.net core razor pages_数据库


高性能asp.net网站 asp.net core razor pages_数据库_02


高性能asp.net网站 asp.net core razor pages_数据库_03


高性能asp.net网站 asp.net core razor pages_数据_04

运行应用

使用快捷键Ctrl+F5,或者菜单栏中运行

高性能asp.net网站 asp.net core razor pages_搜索_05

解决方案文件及文件夹简介

Pages 文件夹

包含 Razor 页面和支持文件。 每个 Razor 页面都是一对文件:

  • 一个 .cshtml 文件,其中包含使用 Razor 语法的 C# 代码的 HTML 标记。
  • 一个 .cshtml.cs 文件,其中包含处理页面事件的 C# 代码。
    支持文件的名称以下划线开头。 例如,_Layout.cshtml 文件可配置所有页面通用的 UI 元素。 _Layout.cshtml 设置页面顶部的导航菜单和页面底部的版权声明。

wwwroot 文件夹

包含静态资产,如 HTML 文件、JavaScript 文件和 CSS 文件。

appsettings.json

包含配置数据,如连接字符串。

Program.cs

应用入口,在此文件中会创建一个带有预配置默认值的 WebApplicationBuilder,向依赖关系注入 (DI) 容器添加 Razor Pages 支持。同时此文件中还包含大量的中间件。

添加数据模型

新增一个Models文件夹用于存放模型类。模型类称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系。 它们定义数据库中存储的数据属性。

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”模型的基架

基架工具将生成页面,用于对“Movie”模型执行创建、读取、更新和删除 (CRUD) 操作。新增一个在Pages文件夹下新增Movies文件夹,然后通过右键菜单选项,选择基架

高性能asp.net网站 asp.net core razor pages_数据_06


高性能asp.net网站 asp.net core razor pages_高性能asp.net网站_07


高性能asp.net网站 asp.net core razor pages_高性能asp.net网站_08

使用 EF 的迁移功能创建初始数据库架构

Entity Framework Core 中的迁移功能提供了一种方法来执行以下操作:

  • 创建初始数据库架构。
  • 以增量的方式更新数据库架构,使其与应用的数据模型保持同步。 保存数据库中的现有数据。

运行 migrations 命令以生成可创建初始数据库架构的代码

add-migration InitialCreate
update-database

高性能asp.net网站 asp.net core razor pages_数据_09

数据上下文

public class RazorPagesMovieContext : DbContext
{
    public RazorPagesMovieContext (DbContextOptions<RazorPagesMovieContext> options)
        : base(options) // 通过调用 DbContextOptions 对象中的一个方法将连接字符串名称传递到上下文
    {
    }

    public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } = default!; // 与数据表相对应
}

基架

Razor 可以从 HTML 转换为 C# 或 Razor 特定标记。 当 @ 符号后跟 Razor 保留关键字时,它会转换为 Razor 特定标记,否则会转换为 C#。

@page 指令

@pageRazor 指令将文件转换为一个 MVC 操作,这意味着它可以处理请求。 @page 必须是页面上的第一个 Razor 指令。 @page 和 @model 是转换为 Razor 特定标记的示例

@model 指令

@model 指令指定传递到 Razor 页面的模型类型。 在前面的示例中,@model 行使 PageModel 派生的类可用于 Razor 页面。 在页面上的 @Html.DisplayNameFor 和 @Html.DisplayForHTML 帮助程序中使用该模型。

ViewData字典

PageModel 基类包含 ViewData 字典属性,可用于将数据传递到某个视图。 可以使用键值模式将对象添加到 ViewData 字典。 在前面的示例中,Title 属性被添加到 ViewData 字典。

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

使用数据库

数据库连接

appsettings.json配置中数据库连接串

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "RazorPagesMovieContext": "Server=(localdb)\\mssqllocaldb;Database=RazorPagesMovie.Data;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

设定数据库种子

新建一个种子类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");
            }

            // 查看数据库中是否已经有种子数据,如果已经有就不添加任何数据
            if (context.Movie.Any())
            {
                return;  
            }

            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();
        }
    }
}

在Program类中添加种子数据初始化调用

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    SeedData.Initialize(services);
}

运行应用,可看到所设置的种子数据

高性能asp.net网站 asp.net core razor pages_数据_10

添加页面搜索功能

在IndexModel中添加按流派或名称搜索电影

public IList<Movie> Movie { get;set; } = default!;

[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }  // 包含用户在搜索文本框中输入的文本。 SearchString 也有 [BindProperty] 属性。 [BindProperty] 会绑定名称与属性相同的表单值和查询字符串。 在 HTTP GET 请求中进行绑定需要 

[BindProperty(SupportsGet = true)]。
public SelectList? Genres { get; set; } // 包含流派列表。 Genres 使用户能够从列表中选择一种流派。 SelectList 需要 using Microsoft.AspNetCore.Mvc.Rendering;

[BindProperty(SupportsGet = true)]
public string? MovieGenre { get; set; } // 包含用户选择的特定流派。

在OnGetAsync方法重处理搜索逻辑

public async Task OnGetAsync()
{
    // Use LINQ to get list of genres.
    IQueryable<string> genreQuery = from m in _context.Movie
                                    orderby m.Genre
                                    select m.Genre;

    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);
    }
    Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
    Movie = await movies.ToListAsync();
}

在Razor页面添加搜索框

<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="检索" />
    </p>
</form>

高性能asp.net网站 asp.net core razor pages_搜索_11

添加新的字段

依次向模型、Razor页面、种子数据(非必须)中追加新的字段Rating

public class Movie
    {
        public int Id { get; set; }
        [Display(Name = "标题")]
        public string? Title { get; set; }

        [Display(Name = "出版日期")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        [Display(Name = "流派")]
        public string? Genre { get; set; }

        [Display(Name = "价格")]
        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }

        public string Rating { get; set; } = string.Empty;
    }
@Html.DisplayNameFor(model => model.Movie[0].Rating)
// ...
 @Html.DisplayFor(modelItem => item.Rating)
new Movie
{
    Title = "When Harry Met Sally",
    ReleaseDate = DateTime.Parse("1989-2-12"),
    Genre = "Romantic Comedy",
    Price = 7.99M,
    Rating = "R"
},

Code First 迁移更新数据库架构

使用 Code First 迁移追加字段

Add-Migration Rating
Update-Database

可以将原表删除重新直接数据库初始化操作,不过这样会导致原数据丢失(生产环境切勿这样操作)。或者也可以通过数据库工具直接修改表,这样可以避免删除原数据。

添加数据验证

System.ComponentModel.DataAnnotations 命名空间提供以下内容:

  • 一组内置验证特性,可通过声明方式应用于类或属性。
  • [DataType] 等格式特性,这些特性可帮助进行格式设置,但不提供任何验证。

Movie 类使用了内置的 [Required]、[StringLength]、[RegularExpression] 和 [Range] 验证特性

public class Movie
{
    public int Id { get; set; }
    [Display(Name = "标题")]
    [StringLength(60, MinimumLength = 3)]
    [Required]
    public string? Title { get; set; }

    [Display(Name = "出版日期")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Display(Name = "流派")]
    [RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
    [Required]
    public string? Genre { get; set; }

    [Display(Name = "价格")]
    [Range(1, 100)]
    [DataType(DataType.Currency)]
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }

    [Display(Name = "评级")]
    [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
    [StringLength(5)]
    public string Rating { get; set; } = string.Empty;
}

存在客户端验证错误时,不会将表单数据发布到服务器。 请通过以下一种或多种方法验证是否未发布表单数据:

  • 在 OnPostAsync 方法中放置一个断点。 通过选择“创建”或“保存”来提交表单 。 从未命中断点。
  • 使用 Fiddler 工具。
  • 使用浏览器开发人员工具监视网络流量。

服务器端验证

在浏览器中禁用 JavaScript 后,提交出错表单将发布到服务器。
(可选)测试服务器端验证:

  1. 在浏览器中禁用 JavaScript。 可以使用浏览器的开发人员工具禁用 JavaScript。 如果无法在此浏览器中禁用 JavaScript,请尝试其他浏览器。
  2. 在“创建”或“编辑”页面的 OnPostAsync 方法中设置断点。
  3. 提交包含无效数据的表单。
  4. 验证模型状态是否无效:
if (!ModelState.IsValid)
 {
    return Page();
 }

验证消息回显

高性能asp.net网站 asp.net core razor pages_搜索_12

将验证特性迁移

迁移后使架构与模型保持一致

Add-Migration New_DataAnnotations
Update-Database

高性能asp.net网站 asp.net core razor pages_搜索_13

ASP.NET Core Razor Pages入门示例到此结束