在 Blazor 服务器应用程序中实施 CRUD 操作

在这里插入图片描述

Blazor 是一个免费的开源单页应用程序 (SPA) 开发框架,它使开发人员能够使用 HTML、CSS 和 C# 而不是 JavaScript 来构建交互式 Web 应用程序。我们可以使用这个流行的框架构建 Blazor WebAssembly 或 Blazor 服务器应用程序,这两种托管模型各有优缺点。 Blazor 服务器应用在服务器上运行,在那里它们可以享受 .NET 运行时的完全支持并使用任何 .NET 库。我已经写了几篇关于 Blazor 的教程,很多人让我写一篇关于使用 Entity Framework Core 在 Blazor 服务器应用程序中实现 CRUD 操作的教程。在本教程中,我将实现一个演示应用程序,该应用程序将向您展示如何使用 EF Core 和 SQL Server 作为后端数据库执行 CRUD 操作。

下载:Download Source Code

为 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 服务器应用程序将调用后端应用程序服务与数据库进行通信。根据我们的应用需求,我们需要创建以下两个服务。

  1. PlayersService – 实现与 Players 相关的方法
  2. 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 表单组件(例如 EditFormInputNumber 等)来创建一个允许用户输入玩家详细信息的简单表单。

<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,这次我们将同时注入 IPlayersServiceIPositionsService 到组件中。我们还注入了 Blazor 内置服务 NavigationManager,它允许我们在创建玩家后以编程方式将用户导航到列表页面。

[Inject]
protected IPlayersService PlayersService { get; set; }
 
[Inject]
protected IPositionsService PositionsService { get; set; }
 
[Inject]
public NavigationManager NavigationManager { get; set; }

接下来,我们需要创建 PlayerPositions 属性。 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();
}

最后,我们需要调用 PlayersServiceCreatePlayer 方法将新玩家保存到数据库中。创建玩家后,我们使用 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(玩家)组件

这个组件是我们上面创建的 CreateDetails 组件的组合。我们将使用在 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 教程的完整列表。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值