如今,作为一名开发人员,如果我们想开发任何基于 Web 的应用程序,我们可以通过多种方式开发它们。现在,我们有几种选项来构建任何基于 Web 的应用程序,例如 MVC 框架、基于 API 的结构以及任何客户端框架,例如 Angular 或 ReactJs。同样,现在我们还有另一种开发 Web 应用程序的选项,即 Blazor 框架。众所周知,Blazor 框架提供了不同的托管模型,例如 Blazor Server 或 Blazor WebAssembly。现在,在本文中,我们将演示如何在 Entity Framework 核心的帮助下使用 Blazor WebAssembly 执行基于 CRUD 的操作。
因此,在此,我们演示了如何为员工开发完整的基于 UI 的组件。 演示之后,我们可以执行以下操作 -
- 可以查看员工名单。
- 可以添加新员工详细信息
- 可以更新/编辑任何现有员工详细信息
- 可以删除任何员工记录
- 将 Id 字段值作为路由参数传递给子页面
众所周知,Blazor Web Assembly 框架支持基于组件的概念,这是代码可重用性概念的最佳方法之一。因此,在我们的演示中,我们将创建一个名为 EmployeeInfo 的单一组件,用于员工添加、编辑或基于视图的操作。我们将在相关部分详细讨论此问题。
演示的先决条件
在开始编码工作之前,首先,我们需要了解与此应用程序相关的先决条件。主要先决条件是 -
- .Net Framework 7.0
- 支持 Blazor 的代码编辑器(我们将使用 Visual Studio 2022,但我们也可以使用 Visual Studio 代码)
- 数据库应用程序(我们将使用 SQL Server)。
为演示创建数据库结构
在开始使用 Blazor WebAssembly 项目之前,首先,我们需要为示例创建数据库表结构。为此,我们使用 SQL Server 数据库。因此,我们需要在数据库中创建一个名为“Employees”的表,如下所示。
CREATE TABLE [dbo].[Employees](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Code] [nvarchar](20) NOT NULL,
[FullName] [nvarchar](100) NULL,
[DOB] [datetime] NOT NULL,
[Address] [nvarchar](200) NULL,
[City] [nvarchar](100) NULL,
[State] [nvarchar](100) NULL,
[Country] [nvarchar](100) NULL,
[PostalCode] [nvarchar](15) NULL,
[EmailId] [nvarchar](100) NULL,
[PhoneNo] [nvarchar](50) NULL,
[JoiningDate] [datetime] NULL,
CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
)
现在,出于测试目的,我们将在表中插入一条记录 -
SET IDENTITY_INSERT [dbo].[Employees] ON
GO
INSERT [dbo].[Employees] ([Id], [Code], [FullName], [DOB], [Address], [City], [State], [Country], [PostalCode], [EmailId], [PhoneNo], [JoiningDate]) VALUES (1, N'E0001', N'NILAY SHARMA', CAST(N'1998-05-05T00:00:00.000' AS DateTime), N'CENTRAL ROAD', N'KOLKATA', N'WB', N'INDIA', N'700001', N'NILAY@YAHOO.COM', N'9830198301', CAST(N'2021-10-05T00:00:00.000' AS DateTime))
GO
SET IDENTITY_INSERT [dbo].[Employees] OFF
GO
创建员工列表页面
步骤1. 首先,打开 Microsoft Visual Studio 2022,然后单击创建新项目。
步骤2. 现在选择 Blazor WebAssembly App 选项并单击下一步按钮。
步骤3. 在下一个窗口中,提供项目名称,然后单击下一步按钮。
步骤4. 现在,选择框架版本.Net7,然后单击创建按钮。
步骤5. 现在打开客户端项目共享文件夹下的NavMenu.razor页面和下面的代码来创建员工的链接。
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="employee/index">
<span class="oi oi-plus" aria-hidden="true"></span> Employee List
</NavLink>
</div>
</nav>
</div>
步骤6. 现在,选择共享项目并将下面提到的 Nuget 包添加到该项目。
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
步骤7. 现在,在共享项目中,添加一个名为 Models 的新文件夹,并添加名为 Employee.cs 的新文件,如下所示。
public class Employee
{
public int Id { get; set; }
[Required]
public string Code { get; set; }
[Required]
public string FullName { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
public string? Address { get; set; }
[Required]
public string City { get; set; }
[Required]
public string State { get; set; }
[Required]
public string Country { get; set; }
[Required]
public string PostalCode { get; set; }
[Required]
public string EmailId { get; set; }
[Required]
public string PhoneNo { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime JoiningDate { get; set; }
}
步骤8. 现在,在共享文件夹下添加另一个名为 DBContexts 的新文件夹,并创建一个名为 SQLDBContext.cs 的新文件并在其中添加以下代码。
public partial class SQLDBContext: DbContext
{
public SQLDBContext()
{
}
public SQLDBContext(DbContextOptions<SQLDBContext> options)
: base(options)
{
}
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=DEB-HP-NOTEBOOK;Database=PracticeDB;user id=debas; Trusted_Connection=True; MultipleActiveResultSets=true; Encrypt=False;");
}
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
步骤9. 选择服务器项目下的控制器文件夹并添加一个名为 EmployeeController.cs 的新控制器。
步骤10. 现在,在员工控制器中添加以下代码来获取员工列表数据。
[Route("api/[controller]")]
[ApiController]
public class EmployeeController: ControllerBase
{
private readonly SQLDBContext _dbContext;
public EmployeeController(SQLDBContext context)
{
_dbContext = context;
}
[Route("GetEmployees")]
[HttpGet]
public async Task<IList<Employee>> GetEmployees()
{
try
{
var _data = await _dbContext.Employees.ToListAsync();
return _data;
}
catch (Exception ex)
{
throw (ex);
}
}
[Route("GetEmployee/{id}")]
[HttpGet]
public async Task<Employee> GetEmployee(int id)
{
try
{
var _data = await _dbContext.Employees.FindAsync(id);
return _data;
}
catch (Exception ex)
{
throw (ex);
}
}
[Route("SaveEmployee")]
[HttpPost]
public async Task<IActionResult> SaveEmployee(Employee employee)
{
try
{
if (_dbContext.Employees == null)
{
return Problem("Entity set 'AppDbContext.Employee' is null.");
}
if (employee != null)
{
_dbContext.Add(employee);
await _dbContext.SaveChangesAsync();
return Ok("Save Successfully!!");
}
}
catch (Exception ex)
{
throw (ex);
}
return NoContent();
}
[Route("UpdateEmployee")]
[HttpPost]
public async Task<IActionResult> UpdateEmployee(Employee employee)
{
_dbContext.Entry(employee).State = EntityState.Modified;
try
{
await _dbContext.SaveChangesAsync();
return Ok("Update Successfully!!");
}
catch (DbUpdateConcurrencyException)
{
if (!EmployeeExists(employee.Id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
[HttpDelete("DeleteEmployee/{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
if (_dbContext.Employees == null)
{
return NotFound();
}
var product = await _dbContext.Employees.FindAsync(id);
if (product == null)
{
return NotFound();
}
_dbContext.Employees.Remove(product);
await _dbContext.SaveChangesAsync();
return NoContent();
}
private bool EmployeeExists(int id)
{
return (_dbContext.Employees?.Any(e => e.Id == id)).GetValueOrDefault();
}
}
步骤11. 我们的员工 GetAll API 端点已准备好检索所有员工列表数据。
步骤12. 现在,返回到客户端项目并在Pages文件夹下添加一个名为Employee的新文件夹。
步骤13. 现在,添加一个名为EmployeeList的新Razor组件,如下所示。
步骤14. 步骤14. 现在,在EmployeeList.razor组件中添加以下代码。
@page "/employee/index"
@using employee_crud_ops.Shared.Models
@inject HttpClient Http
<h3>Employee List</h3>
<h4 style="color:blue;">
Wants to Add New Employee? Click on <a href="/employee/create">Add Employee</a>
</h4>
<table class="table">
<thead>
<tr>
<th>Code</th>
<th>Full Name</th>
<th>Date of Birth</th>
<th>State</th>
<th>City</th>
<th>Joining Date</th>
</tr>
</thead>
<tbody>
@if (employees == null)
{
<tr>
<td colspan="8" align="center">No Data Found</td>
</tr>
}
else
{
@foreach (var employee in employees)
{
<tr>
<td width="10%">@employee.Code</td>
<td width="25%">employee.FullName</td>
<td width="10%">@employee.DOB.ToShortDateString()</td>
<td width="15%">@employee.State</td>
<td width="15%">@employee.City</td>
<td width="10%">@employee.JoiningDate.ToShortDateString()</td>
</tr>
}
}
</tbody>
</table>
@code {
private Employee[]? employees;
protected override async Task OnInitializedAsync()
{
await this.FetEmployees();
}
private async Task FetEmployees()
{
employees = await Http.GetFromJsonAsync<Employee[]>("/api/employee/getemployees");
}
}
步骤15. 现在,运行应用程序,然后检查员工链接。
创建添加/编辑相关组件
现在,在本节中,我们将演示如何根据员工记录执行添加或编辑操作。为此,我们将首先创建一个 EmployeeInfo 组件,该组件将用作两个操作(即添加和编辑)的通用组件。此组件不会执行任何 API 调用。它仅接受值作为输入参数并将数据绑定到控件中。此外,当用户单击保存按钮时,它会引发一个 EventCallback 方法,以便相关组件可以捕获该请求并按照其逻辑执行。
步骤1. 现在,在客户端项目的员工文件夹下添加另一个名为EmployeeInfo.razor的新组件文件,然后添加以下代码。
@using employee_crud_ops.Shared.Models
<h3>@HeaderText</h3>
<hr />
<div style="width:60%;">
<EditForm Model="Employee" OnValidSubmit="OnValidSubmit">
<DataAnnotationsValidator />
<div class="row mb-3">
<label for="inputCode" class="col-sm-2 col-form-label">Employee Code</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empcode" @bind-Value="@Employee.Code" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.Code)" />
</div>
</div>
<div class="row mb-3">
<label for="inputName" class="col-sm-2 col-form-label">Employee Name</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empname" @bind-Value="@Employee.FullName" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.FullName)" />
</div>
</div>
<div class="row mb-3">
<label for="inputDOB" class="col-sm-2 col-form-label">Date Of Birth</label>
<div class="col-sm-10">
<InputDate class="form-control" id="empdob" @bind-Value="@Employee.DOB" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.DOB)" />
</div>
</div>
<div class="row mb-3">
<label for="inputJoinDate" class="col-sm-2 col-form-label">Joining Date</label>
<div class="col-sm-10">
<InputDate class="form-control" id="empjoindate" @bind-Value="@Employee.JoiningDate" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.JoiningDate)" />
</div>
</div>
<div class="row mb-3">
<label for="inputAddress" class="col-sm-2 col-form-label">Address</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empaddr" @bind-Value="@Employee.Address" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.Address)" />
</div>
</div>
<div class="row mb-3">
<label for="inputCity" class="col-sm-2 col-form-label">City</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empcity" @bind-Value="@Employee.City" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.City)" />
</div>
</div>
<div class="row mb-3">
<label for="inputState" class="col-sm-2 col-form-label">State</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empstate" @bind-Value="@Employee.State" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.State)" />
</div>
</div>
<div class="row mb-3">
<label for="inputCountry" class="col-sm-2 col-form-label">Country</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empcountry" @bind-Value="@Employee.Country" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.Country)" />
</div>
</div>
<div class="row mb-3">
<label for="inputpin" class="col-sm-2 col-form-label">Postal Code</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="emppicode" @bind-Value="@Employee.PostalCode" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.PostalCode)" />
</div>
</div>
<div class="row mb-3">
<label for="inputEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<InputText type="email" class="form-control" id="empemail" @bind-Value="@Employee.EmailId" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.EmailId)" />
</div>
</div>
<div class="row mb-3">
<label for="inputPhone" class="col-sm-2 col-form-label">Phone No</label>
<div class="col-sm-10">
<InputText type="text" class="form-control" id="empphone" @bind-Value="@Employee.PhoneNo" disabled=@ReadOnlyMode />
<ValidationMessage For="@(() => Employee.PhoneNo)" />
</div>
</div>
<a class="btn btn-danger" tabindex="-1" role="button" aria-disabled="true" href="employee/index">Back To Employee List</a>
@if (ReadOnlyMode == false)
{
<button type="submit" class="btn btn-primary">@(EmployeeId >0 ? "Update" : "Save")</button>
}
</EditForm>
</div>
@code {
[Parameter] public int EmployeeId { get; set; }
[Parameter] public string HeaderText { get; set; }
[Parameter] public Employee Employee { get; set; }
[Parameter] public bool ReadOnlyMode { get; set; }
[Parameter] public EventCallback OnValidSubmit { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
Console.WriteLine(ReadOnlyMode);
}
}
步骤2. 在上面的组件中,我们定义了四个不同的输入参数。
- EmployeeId: 主要用于更改Save或Update方法的按钮标题。
- HeaderText: 将用于提供页面标题目的。
- Employee: 员工模型类的对象。它携带更新时的现有数据。
- ReadOnlyMode: 仅用于查看目的。
步骤3. 现在,添加另一个名为 AddEmployee.razor 的组件,并在那里添加以下代码 -
@page "/employee/create"
@page "/employee/edit/{employeeId:int}"
@using employee_crud_ops.Shared.Models
@inject HttpClient Http
@inject NavigationManager navigationManager
@if (employeeId > 0)
{
<EmployeeInfo EmployeeId="employeeId" HeaderText="Update Employee Details" Employee="employee" OnValidSubmit="UpdateEmployee"></EmployeeInfo>
}
else
{
<EmployeeInfo EmployeeId="0" HeaderText="Add Employee Details" Employee="employee" OnValidSubmit="CreateEmployee"></EmployeeInfo>
}
@code {
private Employee employee = new Employee();
[Parameter] public int employeeId { get; set; }
protected override async Task OnInitializedAsync()
{
if (employeeId > 0)
employee = await Http.GetFromJsonAsync<Employee>($"/api/employee/getemployee/{employeeId}");
}
private async Task CreateEmployee()
{
Console.WriteLine(employee.Code);
var result = await Http.PostAsJsonAsync("/api/employee/saveemployee", employee);
if (result != null && result.StatusCode == System.Net.HttpStatusCode.OK)
{
navigationManager.NavigateTo("employee/index");
}
}
private async Task UpdateEmployee()
{
Console.WriteLine(employee.Code);
var result = await Http.PostAsJsonAsync("/api/employee/updateemployee", employee);
if (result != null && result.StatusCode == System.Net.HttpStatusCode.OK)
{
navigationManager.NavigateTo("employee/index");
}
}
}
步骤4. 上述组件也将在编辑现有记录时使用。这就是为什么我们为该组件使用两个不同的路由索引。
步骤5. 现在,运行应用程序,然后单击员工列表视图中的添加员工链接按钮。
步骤6. 现在,对于编辑按钮,在 EmployeeList 表视图中添加以下代码。
<td width="7%">
<a href="@GetEditEmployeeViewUrl(@employee)" class="btn btn-primary" tabindex="-1" role="button" aria-disabled="true">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-fill" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z">.
</path>
</svg> Edit
</a>
</td>
步骤7. 现在,在EmployeeList的代码部分添加以下方法。
private string GetEditEmployeeViewUrl(Employee employee)
{
return $"employee/edit/{employee.Id}";
}
步骤8. 现在,运行应用程序以检查编辑功能。
执行删除操作
在本节中,我们将演示如何从列表页面执行删除操作。对于删除操作,当用户单击“删除”按钮时,我们需要实现用户的一次确认。但在 Blazor 中,我们不能直接使用任何基于 JavaScript 的模态窗口。如果我们想使用它,我们可以借助 JS 引擎注入来使用它。为此,首先,打开 EmployeeList.razor 组件并在开始部分添加下面的代码行。
@page "/employee/index"
@using employee_crud_ops.Shared.Models
@inject HttpClient Http
@inject IJSRuntime JsRuntime
现在,在表格部分添加下面的代码来生成删除按钮和编辑按钮。
<td width="8%">
<a class="btn btn-danger" tabindex="-1" role="button" aria-disabled="true" @onclick="_ => DeleteEmployee(employee)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-fill" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"></path>
</svg> Delete
</a>
</td>
现在,在代码部分添加下面的代码来调用 Delete 方法。
private async Task DeleteEmployee(Employee employee)
{
bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", $"Are you sure to Delete the Employee Record : {employee.Code}?"); // Confirm
if (confirmed)
{
var result = await Http.DeleteAsync($"/api/employee/deleteemployee/{employee.Id}");
if (result != null && result.StatusCode == System.Net.HttpStatusCode.OK)
{
await this.FetEmployees();
}
}
}
现在,运行应用程序来检查删除操作。
结论
因此,在本文中,我们讨论使用 Blazor Web Assembly 以及 Entity Framework Core 7.0 的基本 CRUD 操作。