Nowadays, as a developer, if we want to develop any web-based applications, we can develop them in many ways. Now, we have several options to build any Web-based Application like MVC framework, API-based structure along with any client-side framework like Angular or ReactJs. Similarly, now we have another option for developing Web applications, i.e. Blazor Framework. As we know, the Blazor framework provides different hosting models like Blazor Server or Blazor WebAssembly. Now, in this article, we will demonstrate how we can perform the CRUD-based operation using Blazor WebAssembly with the help of Entity Framework core.
So, in this, we demonstrate how to develop complete UI-based components for Employees. After this demonstration, we can perform the below operations –
- Can view the list of the employee.
- Can Add new Employee Details
- Can Update/Edit any Existing Employee Details
- Can Delete any Employee Records
- Pass Id Field Value to the child page as Route Params
As we know, the Blazor Web Assembly framework supports the component-based concept, which is one the best approach for the code reusability concept. For this reason, in our demonstration, we will create a once single component called EmployeeInfo which will be used for Employee Add, Edit, or View-based Operations. We will discuss in detail related this in the related section.
Github Repo Links :- debasis-saha/blazor_wasm_employee_crud_ops (github.com)
Pre-requisite for the Demo
Before starting the coding work, first, we need to know the pre-requisite related to this application. The main pre-requisites are –
- .Net Framework 7.0
- Code editor which supports Blazor ( we will use Visual Studio 2022, but we can also Visual Studio code)
- Database Application (we will use SQL Server).
Create a Database Structure for the Demo
Before starting working with the Blazor WebAssembly projects, first, we need to create the database table structure for our example. For that purpose, we are using an SQL Server database. So, we need to create a table called “Employees” in the database as below.
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]
)
Now, for our testing purpose, we will insert one record into the table –
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'[email protected]', N'9830198301', CAST(N'2021-10-05T00:00:00.000' AS DateTime))
GO
SET IDENTITY_INSERT [dbo].[Employees] OFF
GO
Create the Employee Listing Page
Step 1. First, open Microsoft Visual Studio 2022 and then click Create a New Project.
Step 2. Now select the Blazor WebAssembly App option and the click on Next Button.
Step 3. In the Next window, provide the Project name and then click on the Next Button.
Step 4. Now, select the Framework version.Net7 and then click on the create button.
Step 5. Now open the NavMenu.razor page under the shared folder of client projects and the below code to create the link of the Employee.
<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>
Step 6. Now, select the Shared Project and add the below-mentioned Nuget Packages to that project.
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
Step 7. Now, in the Shared Project, add a new folder called Models and add the new file called Employee.cs as below.
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; }
}
Step 8. Now, add another new folder called DBContexts under the shared folder and create a new file called SQLDBContext.cs and add the below code there.
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)
{
}
}
Step 9. Select the controller folder under the Server Projects and add a new controller called EmployeeController.cs.
Step 10. Now, add the below code in the Employee Controller to fetch the Employee List data.
[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();
}
}
Step 11. Our Employee GetAll API End Point is ready to retrieve all employee list data.
Step 12. Now, return to the Client Project and Add a new Folder Called Employee under the Pages folder.
Step 13. Now, add a new Razor Component called EmployeeList as below.
Step 14. Now, add the below code in the EmployeeList.razor component.
@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");
}
}
Step 15. Now, run the application and then check the Employee Link.
Create Add / Edit Related Components
Now, in this section, we will demonstrate how to perform Add or Edit operation based on the Employee Records. For this purpose, we will first create an EmployeeInfo Component, which will be used as a common component for both operations, i.e. Add and edit. This component will not perform any API call. It only accepts value as an input parameter and binds the data into the control. Also, when the user clicks on the save button, it raises an EventCallback method so related components can trap that request and perform as per their logic.
Step 1. Now, add another new component file called EmployeeInfo.razor under the Employee Folder of the Client project and then add the below code.
@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);
}
}
Step 2. In the above component, we have defined four different input parameters.
- EmployeeId: It is mainly used to change the Button caption for either Save or Update method.
- HeaderText: will be used to provide the page heading purpose.
- Employee: Objects of the Employee Model class. It carries the existing data at the time of the Update.
- ReadOnlyMode: will be used only for view purposes.
Step 3. Now, add another component called AddEmployee.razor and add the below code there –
@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");
}
}
}
Step 4. The above component will also be used at the time of editing existing records. That is why we using two different route indexes for this component.
Step 5. Now, run the application and then click on Add Employee link button in the Employee List View.
Step 6. Now, for the edit button, add the below code in the EmployeeList table view.
<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>
Step 7. Now, add the below method in the code section of EmployeeList.
private string GetEditEmployeeViewUrl(Employee employee)
{
return $"employee/edit/{employee.Id}";
}
Step 8. Now, run the application to check the edit functionality.
Perform Delete Operation
In this section, we will demonstrate how we can perform the delete operation from the list Page. For the delete operation, we need to implement one confirmation from the user when the user clicks on the Delete button. But In Blazor, we can not directly use any JavaScript-based modal window. If we want to use that, we can use the same with the help of JS Engine injection. For this, first, open the EmployeeList.razor component and add the below line of code in the beginning parts.
@page "/employee/index"
@using employee_crud_ops.Shared.Models
@inject HttpClient Http
@inject IJSRuntime JsRuntime
Now, add the code below in the table section to generate the delete button and Edit Button.
<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>
Now, add the below code in the code section to call the Delete method.
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();
}
}
}
Now, run the application to check the delete operations.
Conclusion
So, in this article, we discuss the basic CRUD operation using the Blazor Web Assembly along with Entity Framework Core 7.0. Any queries, suggestions, or feedback related to this article are always welcome.