Blazor 是一个免费的开源单页应用程序 (SPA) 开发框架,它使开发人员能够使用 HTML、CSS 和 C# 而不是 JavaScript 来构建交互式 Web 应用程序。我们可以使用这个流行的框架构建 Blazor WebAssembly 或 Blazor 服务器应用程序,这两种托管模型各有优缺点。 Blazor 服务器应用在服务器上运行,在那里它们可以享受 .NET 运行时的完全支持并使用任何 .NET 库。我已经写了几篇关于 Blazor 的教程,很多人让我写一篇关于使用 Entity Framework Core 在 Blazor 服务器应用程序中实现 CRUD 操作的教程。在本教程中,我将实现一个演示应用程序,该应用程序将向您展示如何使用 EF Core 和 SQL Server 作为后端数据库执行 CRUD 操作。
Blazor 服务器应用程序中实施 CRUD 操作
为 CRUD 操作设置数据库
在本教程中,我将使用带有以下两个表的 SQL Server 2016 数据库。
- 球员 - 此表将存储有关足球运动员的数据。它具有诸如 ShirtNo、Name、PositionId(FK)、Appearances、Goals 等列。
- Positions – 该表将存储不同的位置,例如守门员、后卫、中场等。
我还在 Players 表中添加了一些玩家的信息,我们很快就会在这个表上实现 CRUD 操作,Positions 表将用于绑定玩家创建或更新表单的下拉列表。
Blazor 服务器应用程序入门
在 Visual Studio 2019 中创建 Blazor 服务器应用程序并删除默认项目模板生成的以下文件。
- Data/WeatherForecast.cs
- Data/WeatherForecastService.cs
- Pages/Counter.razor
- Pages/FetchData.razor
从 Shared 文件夹打开 NavMenu.razor 文件,并确保导航菜单 HTML 类似于以下代码片段。
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="/players">
<span class="oi oi-arrow-right" aria-hidden="true"></span> Players
</NavLink>
</li>
</ul>
</div>
配置实体框架核心(数据库优先)
我们需要安装 Entity Framework Core 和其他使用 EF Core(数据库优先)方法所需的相关包。打开 NuGet 包管理器,在项目中搜索并安装以下包。
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
我们正在使用现有的 SQL Server 数据库,这就是为什么我们要使用 EF Core(数据库优先)方法对实体模型和 DbContext 进行逆向工程。为此,我们可以使用具有许多选项的 Scaffold-DbContext 命令来自定义生成的代码。您可以在我的另一篇文章中阅读有关 EF Core(数据库优先)的更多信息。
打开项目的包管理器控制台并复制/粘贴以下命令并按 Enter。以下命令将在 Models 文件夹中生成实体类,在 Data 文件夹中生成 FootballDbContext 类。
Scaffold-DbContext -Connection "Server=DB_SERVER; Database=FootballDb; Trusted_Connection=True; MultipleActiveResultSets=true;" -Provider Microsoft.EntityFrameworkCore.SqlServer -OutputDir "Models" -ContextDir "Data" -Context "FootballDbContext" –NoOnConfiguring
在 appsettings.json 文件中指定连接字符串如下
{
"ConnectionStrings": {
"DefaultConnection": "Server=DB_SERVER; Database=FootballDb; Trusted_Connection=True; MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
最后,可以在 Startup.cs 文件的 ConfigureServices 方法中配置实体框架提供程序,如下所示:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContext<FootballDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
为 CRUD 操作实施应用服务
我们将要构建的应用程序将允许我们从 Blazor 服务器应用程序执行 CRUD 操作。 Blazor 服务器应用程序将调用后端应用程序服务与数据库进行通信。根据我们的应用需求,我们需要创建以下两个服务。
- PlayersService – 实现与 Players 相关的方法
- PositionsService – 实现与 Positions相关的方法
在项目中创建一个Services文件夹,创建如下IPlayersService接口。该接口有五种标准方法可以在 Players 上执行 CRUD 操作
IPlayersService.cs
public interface IPlayersService
{
Task<IEnumerable<Player>> GetPlayersList();
Task<Player> GetPlayerById(int id);
Task<Player> CreatePlayer(Player player);
Task UpdatePlayer(Player player);
Task DeletePlayer(Player player);
}
在 Services 文件夹中创建一个 PlayersService 类,并在该类上实现 IPlayersService 接口。您需要在服务类中注入 FootballDbContext 来执行数据库操作。 PlayersService 的方法非常简单,因为它们在 Players 表上执行标准的创建、更新、删除、列表等操作。
PlayersService.cs
public class PlayersService : IPlayersService
{
private readonly FootballDbContext _context;
public PlayersService(FootballDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Player>> GetPlayersList()
{
return await _context.Players
.Include(x => x.Position)
.ToListAsync();
}
public async Task<Player> GetPlayerById(int id)
{
return await _context.Players
.Include(x => x.Position)
.FirstOrDefaultAsync(x => x.Id == id);
}
public async Task<Player> CreatePlayer(Player player)
{
_context.Players.Add(player);
await _context.SaveChangesAsync();
return player;
}
public async Task UpdatePlayer(Player player)
{
_context.Players.Update(player);
await _context.SaveChangesAsync();
}
public async Task DeletePlayer(Player player)
{
_context.Players.Remove(player);
await _context.SaveChangesAsync();
}
}
接下来,在 Services 文件夹中创建以下 IPositionsService 接口。我们可以在 IPositionsService 中声明所有与 Positions 表相关的方法,但对于本教程,我们只需要一个 GetPositionsList 方法。
IPositionsService.cs
public interface IPositionsService
{
Task<IEnumerable<Position>> GetPositionsList();
}
创建一个新类 PositionsService 并实现 IPositionsService 接口,如下所示:
public class PositionsService : IPositionsService
{
private readonly FootballDbContext _context;
public PositionsService(FootballDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Position>> GetPositionsList()
{
return await _context.Positions
.OrderBy(x => x.DisplayOrder)
.ToListAsync();
}
}
准备好服务后,我们需要通过在 Startup.cs 文件的 ConfigureServices 方法中添加以下两行来向 .NET 依赖项注入容器注册它们。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContext<FootballDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IPlayersService, PlayersService>();
services.AddScoped<IPositionsService, PositionsService>();
}
实现Players列表组件
我们要创建的第一个 razor 组件是玩家列表组件。该组件将显示后端数据库中所有玩家的列表。让我们在 Pages 文件夹中创建一个 Players 文件夹,并在新创建的 Players 文件夹中添加一个新的剃刀组件 List.razor 及其代码隐藏文件 List.razor.cs。
List.razor
@page "/players"
@page "/players/list"
<div class="row">
<div class="col-lg-9">
<h1>Players</h1>
</div>
<div class="col-lg-3 text-right">
<a class="btn btn-success btn-sm" href="/players/create" role="button">Create New</a>
</div>
</div>
@if (Players != null && Players.Count > 0)
{
<table class="table table-bordered table-striped table-sm">
<thead>
<tr>
<th>Shirt No</th>
<th>Name</th>
<th>Position</th>
<th>Appearances</th>
<th>Goals</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Players)
{
<tr>
<td>@item.ShirtNo</td>
<td>@item.Name</td>
<td>@item.Position?.Name</td>
<td>@item.Appearances</td>
<td>@item.Goals</td>
<td class="text-right">
<a class="btn btn-primary btn-sm" href="/players/detail/@item.Id" role="button">Details</a>
<a class="btn btn-secondary btn-sm" href="/players/edit/@item.Id" role="button">Edit</a>
<button class="btn btn-danger btn-sm" @onclick="@(e => DeletePlayer(item.Id))" role="button">Delete</button>
</td>
</tr>
}
</tbody>
</table>
}
上面的 HTML 标记是不言自明的。我们只是遍历 Players 属性中可用的玩家列表,我们将从代码隐藏文件中公开这些玩家。
在代码隐藏文件中,我们首先需要注入 IPlayersService,以便我们可以在 Blazor 组件中调用它的方法。
[Inject]
protected IPlayersService PlayersService { get; set; }
我们还需要声明 Players 属性,该属性将保存从后端服务返回的玩家对象。
private List<Player> Players { get; set; }
最后,我们需要调用 OnInitializedAsync 方法中的 GetPlayersList 方法来初始化 Players 属性。 OnInitializedAsync 是 Blazor 应用程序生命周期方法之一,用于初始化 Blazor 组件。
protected override async Task OnInitializedAsync()
{
Players = await PlayersService.GetPlayersList();
}
List.razor.cs 代码隐藏文件的完整代码如下所示:
List.razor.cs
public partial class List
{
[Inject]
protected IPlayersService PlayersService { get; set; }
private List<Player> Players { get; set; }
protected override async Task OnInitializedAsync()
{
Players = await PlayersService.GetPlayersList();
}
}
按 F5 运行项目,您将看到类似于以下屏幕截图的输出。
实现Player(玩家)详细信息组件
我们要创建的下一个 razor 组件是玩家详细信息组件。当我们单击玩家列表组件中的详细信息按钮时,此组件将显示特定玩家的详细信息。让我们在 Players 文件夹中添加一个新组件 Detail.razor 并在其中添加以下标记。
我们需要做的第一件事是使用**@page** 指令定义页面路由。我们还可以在页面 URL 的末尾定义一个 int 类型的路由参数 id。
@page "/players/detail/{id:int}"
现在,我们只需在 URL 中传递玩家 ID 即可查看任何玩家详细信息,如下所示:
/players/detail/1
如果您想了解有关 Blazor 路由的更多信息,请阅读文章 Blazor 路由和导航开发人员指南。
剩下的 HTML 标记只是显示玩家对象的属性,例如 ShirtNo、Name 等。
Detail.razor
@page "/players/detail/{id:int}"
@if (Player != null)
{
<div class="row">
<div class="col-lg-9">
<h1>@Player.Name</h1>
</div>
<div class="col-lg-3 text-right">
<a class="btn btn-secondary btn-sm" href="/players" role="button">Go Back To List</a>
</div>
</div>
<table class="table">
<tbody>
<tr>
<th scope="row">Shirt No</th>
<td>@Player.ShirtNo</td>
</tr>
<tr>
<th scope="row">Position</th>
<td>@Player.Position?.Name</td>
</tr>
<tr>
<th scope="row">Appearances</th>
<td>@Player.Appearances</td>
</tr>
<tr>
<th scope="row">Goals</th>
<td>@Player.Goals</td>
</tr>
</tbody>
</table>
}
为了完成这个页面,让我们在 Players 文件夹中添加 Detail.razor.cs 代码隐藏文件。
Detail.razor.cs 代码隐藏文件将与文件后面的列表页面非常相似。我们需要以我们在上面列表页面中注入的类似方式注入 IPlayersService。
[Inject]
protected IPlayersService PlayersService { get; set; }
我们还需要声明一个 Player 属性来存储所选玩家的详细信息。
public Player Player { get; set; }
最后,我们将调用 OnInitializedAsync 方法中的 GetPlayerById 方法来初始化 Player 属性。
protected override async Task OnInitializedAsync()
{
Player = await PlayersService.GetPlayerById(Id);
}
Detail.razor.cs 代码隐藏文件的完整代码如下所示:
public partial class Detail
{
[Inject]
protected IPlayersService PlayersService { get; set; }
[Parameter]
public int Id { get; set; }
public Player Player { get; set; }
protected override async Task OnInitializedAsync()
{
Player = await PlayersService.GetPlayerById(Id);
}
}
按 F5 运行项目并单击任何Player(玩家)的详细信息按钮。您将看到类似于以下屏幕截图的输出。
实现创建Player(玩家)组件
该组件将允许我们在数据库中创建新玩家。 HTML 标记将使用多个 Blazor 表单组件(例如 EditForm、InputNumber 等)来创建一个允许用户输入玩家详细信息的简单表单。
<InputNumber class="form-control" @bind-Value="Player.ShirtNo" />
我们还需要使用 Blazor InputSelect 组件生成一个位置下拉列表。此下拉菜单将允许用户选择从数据库加载的球员位置之一。
<InputSelect @bind-Value="Player.PositionId" class="form-control">
@if (Positions != null)
{
@foreach (var position in Positions)
{
<option value="@position.Id">@position.Name</option>
}
}
</InputSelect>
当用户提交表单时,我们将调用由 EditForm 组件的 OnValidSubmit 属性定义的 SubmitPlayer 方法。
<EditForm Model="@Player" OnValidSubmit="@SubmitPlayer">
Create.razor 文件的完整 HTML 标记如下所示:
Create.razor
@page "/players/create"
<div class="row">
<div class="col-lg-9">
<h1>Create</h1>
</div>
<div class="col-lg-3 text-right">
<a class="btn btn-secondary btn-sm" href="/players" role="button">Go Back To List</a>
</div>
</div>
@if (Player != null)
{
<EditForm Model="@Player" OnValidSubmit="@SubmitPlayer">
<div class="form-group">
<label>Shirt No</label>
<InputNumber class="form-control" @bind-Value="Player.ShirtNo" />
</div>
<div class="form-group">
<label>Name</label>
<InputText class="form-control" @bind-Value="Player.Name" />
</div>
<div class="form-group">
<label>Position</label>
<InputSelect @bind-Value="Player.PositionId" class="form-control">
@if (Positions != null)
{
@foreach (var position in Positions)
{
<option value="@position.Id">@position.Name</option>
}
}
</InputSelect>
</div>
<div class="form-group">
<label>Appearances</label>
<InputNumber class="form-control" @bind-Value="Player.Appearances" />
</div>
<div class="form-group">
<label>Goals</label>
<InputNumber class="form-control" @bind-Value="Player.Goals" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</EditForm>
}
接下来,我们将在 Players 文件夹中创建一个代码隐藏文件 Create.razor.cs,这次我们将同时注入 IPlayersService 和 IPositionsService 到组件中。我们还注入了 Blazor 内置服务 NavigationManager,它允许我们在创建玩家后以编程方式将用户导航到列表页面。
[Inject]
protected IPlayersService PlayersService { get; set; }
[Inject]
protected IPositionsService PositionsService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
接下来,我们需要创建 Player 和 Positions 属性。 Player 属性将保存新创建的玩家信息,而 Positions 属性将保存从数据库加载的玩家位置。
public Player Player { get; set; }
private List<Position> Positions { get; set; }
我们需要调用 PositionsService 类的 GetPositionsList 方法来填充 Positions 属性,并且可以使用新的 Player 对象初始化 Player 属性。
protected override async Task OnInitializedAsync()
{
Player = new Player();
Positions = await PositionsService.GetPositionsList();
}
最后,我们需要调用 PlayersService 的 CreatePlayer 方法将新玩家保存到数据库中。创建玩家后,我们使用 NavigationManager 类的 NavigateTo 方法将用户重定向回列表页面。
private async void SubmitPlayer()
{
await PlayersService.CreatePlayer(Player);
NavigationManager.NavigateTo("/players");
}
Create.razor.cs 代码隐藏文件的完整代码如下所示:
Create.razor.cs
public partial class Create
{
[Inject]
protected IPlayersService PlayersService { get; set; }
[Inject]
protected IPositionsService PositionsService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
public Player Player { get; set; }
private List<Position> Positions { get; set; }
protected override async Task OnInitializedAsync()
{
Player = new Player();
Positions = await PositionsService.GetPositionsList();
}
private async void SubmitPlayer()
{
await PlayersService.CreatePlayer(Player);
NavigationManager.NavigateTo("/players");
}
}
运行项目并单击显示在玩家列表页面顶部的 Create New 按钮。您应该会看到类似于以下内容的表单。
提供新玩家详细信息,然后单击“创建”按钮在数据库中创建新玩家。
实现编辑Player(玩家)组件
这个组件是我们上面创建的 Create 和 Details 组件的组合。我们将使用在 Details 组件中使用的相同 GetPlayerById 方法初始化 Player 对象,然后将玩家对象与 Blazor 表单组件绑定。为了更新玩家,我们将调用 PlayersService 类的 UpdatePlayer 方法。
这是 Edit.razor.cs 代码隐藏文件的完整代码
Edit.razor.cs
public partial class Edit
{
[Inject]
protected IPlayersService PlayersService { get; set; }
[Inject]
protected IPositionsService PositionsService { get; set; }
[Inject]
public NavigationManager NavigationManager { get; set; }
[Parameter]
public int Id { get; set; }
public Player Player { get; set; }
private List<Position> Positions { get; set; }
protected override async Task OnInitializedAsync()
{
Player = await PlayersService.GetPlayerById(Id);
Positions = await PositionsService.GetPositionsList();
}
private async void SubmitPlayer()
{
await PlayersService.UpdatePlayer(Player);
NavigationManager.NavigateTo("/players");
}
}
以下是 Edit.razor 页面的 HTML 标记,它看起来类似于我们的 Create.razor 页面。
Edit.razor
@page "/players/edit/{id:int}"
@if (Player != null)
{
<div class="row">
<div class="col-lg-9">
<h1>@Player.Name</h1>
</div>
<div class="col-lg-3 text-right">
<a class="btn btn-secondary btn-sm" href="/players" role="button">Go Back To List</a>
</div>
</div>
<EditForm Model="@Player" OnValidSubmit="@SubmitPlayer">
<div class="form-group">
<label>Shirt No</label>
<InputNumber class="form-control" @bind-Value="Player.ShirtNo" />
</div>
<div class="form-group">
<label>Name</label>
<InputText class="form-control" @bind-Value="Player.Name" />
</div>
<div class="form-group">
<label>Position</label>
<InputSelect @bind-Value="Player.PositionId" class="form-control">
@if (Positions != null)
{
@foreach (var position in Positions)
{
<option value="@position.Id">@position.Name</option>
}
}
</InputSelect>
</div>
<div class="form-group">
<label>Appearances</label>
<InputNumber class="form-control" @bind-Value="Player.Appearances" />
</div>
<div class="form-group">
<label>Goals</label>
<InputNumber class="form-control" @bind-Value="Player.Goals" />
</div>
<button type="submit" class="btn btn-primary">Update</button>
</EditForm>
}
再次运行该项目,然后单击您要编辑的任何播放器旁边显示的“编辑”按钮。您应该会看到类似于以下内容的表单。
尝试更新玩家信息并单击更新按钮。您应该会看到玩家列表中显示的更新信息。
实现删除Player(玩家)功能
要删除Player(玩家),我们不需要创建新页面或组件。用户只需单击列表中每个玩家显示的删除按钮,删除按钮的 onclick 事件将通过将当前玩家 ID 作为参数传递来调用 DeletePlayer 方法。
<button class="btn btn-danger btn-sm" @onclick="@(e => DeletePlayer(item.Id))" role="button">Delete</button>
在 List.razor.cs 文件中添加以下 DeletePlayer 方法,只需调用 PlayersService 类的 DeletePlayer 方法即可。删除玩家后,我们还可以从当前显示在玩家列表页面上的玩家集合中删除该玩家。
public async Task DeletePlayer(int playerId)
{
var player = await PlayersService.GetPlayerById(playerId);
if (player != null)
{
await PlayersService.DeletePlayer(player);
Players.RemoveAll(x => x.Id == playerId);
}
}
总结概括
在本教程中,我们学习了如何在 Blazor 服务器应用中实现 CRUD 操作。我们使用实体框架(数据库优先)方法来执行后端数据库操作。如果您想了解有关 Blazor 的更多信息,可以在此处查看有关 Blazor 教程的完整列表。