Blazor Web Assembly 3.2 Add/Edit/Delete Fully Functional Application - Part 2

Introduction

 
This is part 2 of the article. It focuses on consuming the .NET Core API created in part 1, and binding the data in our Blazor app. By the end of the article, we will have a fully functional Blazor site with Add/Edit and Delete features.
 
Prerequisites 
 
This article assumes you have a basic working knowledge of Blazor web assembly and .NET Core.
 
We will be consuming the .NET Core API created in the first part to bind in our Blazor client app.
 
Through this article, we will cover the following topics:
  • Create and register data service in Blazor to consume a .NET Core API.
  • Creating Blazor razor pages and data binding.
  • Creating a custom Blazor component for Add Employee.
Output
 
Employee Home Page
 
 
Employee Detail Page
 
 
Implementation
 
Step 1 - Create and register data service in Blazor to consume .NET Core API
 
Create a new folder service in EmployeePortal.Client and add IEmployeeDataService.
 
This will contain a definition of all the methods to be used in EmployeeDataService.
 
IEmployeeDataService
  1. public interface IEmployeeDataService {  
  2.     Task < IEnumerable < Employee >> GetAllEmployees();  
  3.     Task < Employee > AddEmployee(Employee employee);  
  4.     Task < Employee > GetEmployeeDetails(int employeeId);  
  5.     Task UpdateEmployee(Employee employee);  
  6.     Task DeleteEmployee(int employeeId);  
  7. }  
 EmployeeDataService
  1. public class EmployeeDataService: IEmployeeDataService {  
  2.     private readonly HttpClient _httpClient;  
  3.     public EmployeeDataService(HttpClient httpClient) {  
  4.         _httpClient = httpClient;  
  5.     }  
  6.     public async Task < IEnumerable < Employee >> GetAllEmployees() {  
  7.         return await JsonSerializer.DeserializeAsync < IEnumerable < Employee >> (await _httpClient.GetStreamAsync($ "api/employee"), new JsonSerializerOptions() {  
  8.             PropertyNameCaseInsensitive = true  
  9.         });  
  10.     }  
  11.     public async Task < Employee > AddEmployee(Employee employee) {  
  12.         var employeeJson = new StringContent(JsonSerializer.Serialize(employee), Encoding.UTF8, "application/json");  
  13.         var response = await _httpClient.PostAsync($ "api/employee", employeeJson);  
  14.         if (response.IsSuccessStatusCode) {  
  15.             return await JsonSerializer.DeserializeAsync < Employee > (await response.Content.ReadAsStreamAsync());  
  16.         }  
  17.         return null;  
  18.     }  
  19.     public async Task < Employee > GetEmployeeDetails(int employeeId) {  
  20.         return await JsonSerializer.DeserializeAsync < Employee > (await _httpClient.GetStreamAsync($ "api/employee/{employeeId}"), new JsonSerializerOptions() {  
  21.             PropertyNameCaseInsensitive = true  
  22.         });  
  23.     }  
  24.     public async Task UpdateEmployee(Employee employee) {  
  25.         var employeeJson = new StringContent(JsonSerializer.Serialize(employee), Encoding.UTF8, "application/json");  
  26.         await _httpClient.PutAsync("api/employee", employeeJson);  
  27.     }  
  28.     public async Task DeleteEmployee(int employeeId) {  
  29.         await _httpClient.DeleteAsync($ "api/employee/{employeeId}");  
  30.     }  
  31. }  
 Register EmployeeDataService and IEmployeeDataService in program.cs of cllient app
 builder.Services.AddHttpClient<IEmployeeDataService, EmployeeDataService>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
 
Step 2 - Creating Blazor razor pages and data binding
 
Add a new Razor component and EmployeePage class inside the pages folder.
 
 
EmployeePage.cs 
  1. public partial class EmployeePage: ComponentBase {  
  2.     public IEnumerable < EmployeePortal.Shared.Employee > Employees {  
  3.             get;  
  4.             set;  
  5.         }  
  6.         [Inject]  
  7.     public IEmployeeDataService EmployeeDataService {  
  8.         get;  
  9.         set;  
  10.     }  
  11.     public AddEmployeeDialog AddEmployeeDialog {  
  12.         get;  
  13.         set;  
  14.     }  
  15.     protected async override Task OnInitializedAsync() {  
  16.         Employees = (await EmployeeDataService.GetAllEmployees()).ToList();  
  17.     }  
  18.     protected void QuickAddEmployee() {  
  19.         AddEmployeeDialog.Show();  
  20.     }  
  21.     public async void AddEmployeeDialog_OnDialogClose() {  
  22.         Employees = (await EmployeeDataService.GetAllEmployees()).ToList();  
  23.         StateHasChanged();  
  24.     }  
  25. }  
Employee page class will inherit from the Component base.
 
OnInitializedAsync will be called when the componenet is been Initialized and we would call GetAllEmployees at this point in time.
 
IEmployeeDataService needs to be injected to call the GetAllEmployees method.
 
Employeerazor
  1. @page "/"  
  2. @using EmployeePortal.Client.Components;  
  3. <h1 class="page-title">All employees</h1>  
  4. @if (Employees == null)  
  5. {  
  6. <p>  
  7.     <em>Loading...</em>  
  8. </p>  
  9. }  
  10. else  
  11. {  
  12. <table class="table">  
  13.     <thead>  
  14.         <tr>  
  15.             <th>Employee ID</th>  
  16.             <th>First name</th>  
  17.             <th>Last name</th>  
  18.             <th></th>  
  19.         </tr>  
  20.     </thead>  
  21.     <tbody>  
  22. @foreach (var employee in Employees)  
  23. {  
  24.         <tr>  
  25.             <td>@employee.EmployeeId</td>  
  26.             <td>@employee.FirstName</td>  
  27.             <td>@employee.LastName</td>  
  28.             <td>  
  29.                 <a href="@($"detail/{employee.EmployeeId}")" class="btn btn-primary-details table-btn">  
  30.                     <i class="fas fa-info-circle"></i>  
  31. Details  
  32.   
  33.                 </a>  
  34.                 <a Edit href="@($"edit/{employee.EmployeeId}")" class="btn btn-primary-edit table-btn">  
  35.                     <i class="fas fa-edit"></i>  
  36. Edit  
  37.   
  38.                 </a>  
  39.             </td>  
  40.         </tr>  
  41. }  
  42.     </tbody>  
  43. </table>  
  44. }  
  45. <button @onclick="QuickAddEmployee" class="btn btn-dark table-btn quick-add-btn">  +  </button>  
  46. <AddEmployeeDialog @ref="AddEmployeeDialog" CloseEventCallback="@AddEmployeeDialog_OnDialogClose"></AddEmployeeDialog>  
 @page "/" directive specifies this is the default page when blazor application is loaded.
 
 Import Employee models from shared EmployeePortal.Shared 
 
 Since we are using aync call to load employee data, we need to check if the Employees object is null before binding.
 
 Add a new class for Detail.
  1. public partial class Detail {  
  2.     [Parameter]  
  3.     public string EmployeeId {  
  4.         get;  
  5.         set;  
  6.     }  
  7.     public EmployeePortal.Shared.Employee Employee {  
  8.             get;  
  9.             set;  
  10.         }  
  11.         [Inject]  
  12.     public NavigationManager NavigationManager {  
  13.         get;  
  14.         set;  
  15.     }  
  16.     [Inject]  
  17.     public IEmployeeDataService EmployeeDataService {  
  18.         get;  
  19.         set;  
  20.     }  
  21.     protected async override Task OnInitializedAsync() {  
  22.         Employee = await EmployeeDataService.GetEmployeeDetails(int.Parse(EmployeeId));  
  23.     }  
  24.     protected void NavigateToOverview() {  
  25.         NavigationManager.NavigateTo("/");  
  26.     }  
  27. }  
Add a new razor component for Detail.razor.
  1. @page "/detail/{EmployeeId}"  
  2. @if (@Employee == null)  
  3. {  
  4. <p>  
  5.     <em>Loading...</em>  
  6. </p>  
  7. }  
  8. else  
  9. {  
  10. <section class="employee-detail">  
  11.     <h1 class="page-title">Details for @Employee.FirstName @Employee.LastName</h1>  
  12.     <div class="col-12 row">  
  13.         <div class="col-10 row">  
  14.             <div class="col-xs-12 col-sm-8">  
  15.                 <div class="form-group row">  
  16.                     <label class="col-sm-4 col-form-label">Employee ID</label>  
  17.                     <div class="col-sm-8">  
  18.                         <label type="text" class="form-control-plaintext">@Employee.EmployeeId</label>  
  19.                     </div>  
  20.                 </div>  
  21.                 <div class="form-group row">  
  22.                     <label class="col-sm-4 col-form-label">First name</label>  
  23.                     <div class="col-sm-8">  
  24.                         <label type="text" readonly class="form-control-plaintext">@Employee.FirstName</label>  
  25.                     </div>  
  26.                 </div>  
  27.                 <div class="form-group row">  
  28.                     <label class="col-sm-4 col-form-label">Last name</label>  
  29.                     <div class="col-sm-8">  
  30.                         <label type="text" readonly class="form-control-plaintext">@Employee.LastName</label>  
  31.                     </div>  
  32.                 </div>  
  33.                 <div class="form-group row">  
  34.                     <label class="col-sm-4 col-form-label">Email</label>  
  35.                     <div class="col-sm-8">  
  36.                         <label type="text" readonly class="form-control-plaintext">@Employee.Email</label>  
  37.                     </div>  
  38.                 </div>  
  39.                 <div class="form-group row">  
  40.                     <label class="col-sm-4 col-form-label">Street</label>  
  41.                     <div class="col-sm-8">  
  42.                         <label type="text" readonly class="form-control-plaintext">@Employee.Street</label>  
  43.                     </div>  
  44.                 </div>  
  45.                 <div class="form-group row">  
  46.                     <label class="col-sm-4 col-form-label">Zip</label>  
  47.                     <div class="col-sm-8">  
  48.                         <label type="text" readonly class="form-control-plaintext">@Employee.Zip</label>  
  49.                     </div>  
  50.                 </div>  
  51.                 <div class="form-group row">  
  52.                     <label class="col-sm-4 col-form-label">City</label>  
  53.                     <div class="col-sm-8">  
  54.                         <label type="text" readonly class="form-control-plaintext">@Employee.City</label>  
  55.                     </div>  
  56.                 </div>  
  57.                 <div class="form-group row">  
  58.                     <label class="col-sm-4 col-form-label">Phone number</label>  
  59.                     <div class="col-sm-8">  
  60.                         <label type="text" readonly class="form-control-plaintext">@Employee.PhoneNumber</label>  
  61.                     </div>  
  62.                 </div>  
  63.             </div>  
  64.         </div>  
  65.     </div>  
  66.     <a class="btn btn-outline-primary" @onclick="@NavigateToOverview">Back to overview</a>  
  67. </section>  
  68. }  
 Create a new Edit class.
  1. public partial class Edit {  
  2.     [Inject]  
  3.     public IEmployeeDataService EmployeeDataService {  
  4.         get;  
  5.         set;  
  6.     }  
  7.     [Parameter]  
  8.     public string EmployeeId {  
  9.         get;  
  10.         set;  
  11.     }  
  12.     [Inject]  
  13.     public NavigationManager NavigationManager {  
  14.         get;  
  15.         set;  
  16.     }  
  17.     public EmployeePortal.Shared.Employee Employee {  
  18.         get;  
  19.         set;  
  20.     }  
  21.     //used to store state of screen  
  22.     protected string Message = string.Empty;  
  23.     protected string StatusClass = string.Empty;  
  24.     protected bool Saved;  
  25.     protected override async Task OnInitializedAsync() {  
  26.         Saved = false;  
  27.         int.TryParse(EmployeeId, out  
  28.             var employeeId);  
  29.         if (employeeId == 0) //new employee is being created  
  30.         {  
  31.             Employee = new Employee {};  
  32.         } else {  
  33.             Employee = await EmployeeDataService.GetEmployeeDetails(int.Parse(EmployeeId));  
  34.         }  
  35.     }  
  36.     protected async Task HandleValidSubmit() {  
  37.         Saved = false;  
  38.         if (Employee.EmployeeId == 0) {  
  39.             var addedEmployee = await EmployeeDataService.AddEmployee(Employee);  
  40.             if (addedEmployee != null) {  
  41.                 StatusClass = "alert-success";  
  42.                 Message = "New employee added successfully.";  
  43.                 Saved = true;  
  44.             } else {  
  45.                 StatusClass = "alert-danger";  
  46.                 Message = "Something went wrong adding the new employee. Please try again.";  
  47.                 Saved = false;  
  48.             }  
  49.         } else {  
  50.             await EmployeeDataService.UpdateEmployee(Employee);  
  51.             StatusClass = "alert-success";  
  52.             Message = "Employee updated successfully.";  
  53.             Saved = true;  
  54.         }  
  55.     }  
  56.     protected void HandleInvalidSubmit() {  
  57.         StatusClass = "alert-danger";  
  58.         Message = "There are some validation errors. Please try again.";  
  59.     }  
  60.     protected async Task DeleteEmployee() {  
  61.         await EmployeeDataService.DeleteEmployee(Employee.EmployeeId);  
  62.         StatusClass = "alert-success";  
  63.         Message = "Deleted successfully";  
  64.         Saved = true;  
  65.     }  
  66.     protected void NavigateToOverview() {  
  67.         NavigationManager.NavigateTo("/");  
  68.     }  
  69. }  
Add a new razor component Edit.razor
  1. @page "/edit/{EmployeeId}"  
  2. @using EmployeePortal.Shared;  
  3. @if (Employee == null)  
  4. {  
  5.   
  6. <p>  
  7.     <em>Loading...</em>  
  8. </p>  
  9. }  
  10. else  
  11. {  
  12. @if (!Saved)  
  13. {  
  14.   
  15. <section class="employee-edit">  
  16.     <h1 class="page-title">Details for @Employee.FirstName @Employee.LastName</h1>  
  17.     <EditForm Model="@Employee" OnValidSubmit="@HandleValidSubmit"  
  18. OnInvalidSubmit="@HandleInvalidSubmit">  
  19.         <DataAnnotationsValidator />  
  20.         <ValidationSummary></ValidationSummary>  
  21.         <div class="form-group row">  
  22.             <label for="firstName" class="col-sm-3">First name: </label>  
  23.             <InputText id="firstName" class="form-control col-sm-8" @bind-Value="@Employee.FirstName" placeholder="Enter first name"></InputText>  
  24.             <ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Employee.FirstName)" />  
  25.         </div>  
  26.         <div class="form-group row">  
  27.             <label for="lastName" class="col-sm-3">Last name: </label>  
  28.             <InputText id="lastName" class="form-control col-sm-8" @bind-Value="@Employee.LastName" placeholder="Enter last name"></InputText>  
  29.             <ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Employee.LastName)" />  
  30.         </div>  
  31.         <div class="form-group row">  
  32.             <label for="email" class="col-sm-3">Email: </label>  
  33.             <InputText id="email" class="form-control col-sm-8" @bind-Value="@Employee.Email" placeholder="Enter email"></InputText>  
  34.             <ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Employee.Email)" />  
  35.         </div>  
  36.         <div class="form-group row">  
  37.             <label for="street" class="col-sm-3">Street: </label>  
  38.             <InputText id="street" class="form-control col-sm-8" @bind-Value="@Employee.Street" placeholder="Enter street"></InputText>  
  39.         </div>  
  40.         <div class="form-group row">  
  41.             <label for="zip" class="col-sm-3">Zip code: </label>  
  42.             <InputText id="zip" class="form-control col-sm-8" @bind-Value="@Employee.Zip" placeholder="Enter zip code"></InputText>  
  43.         </div>  
  44.         <div class="form-group row">  
  45.             <label for="city" class="col-sm-3">City: </label>  
  46.             <InputText id="city" class="form-control col-sm-8" @bind-Value="@Employee.City" placeholder="Enter city"></InputText>  
  47.         </div>  
  48.         <div class="form-group row">  
  49.             <label for="phonenumber" class="col-sm-3">Phone number: </label>  
  50.             <InputText id="phonenumber" class="form-control col-sm-8" @bind-Value="@Employee.PhoneNumber" placeholder="Enter phone number"></InputText>  
  51.         </div>  
  52.         <div class="form-group row">  
  53.             <label for="comment" class="col-sm-3">Comment: </label>  
  54.             <InputTextArea id="comment" class="form-control col-sm-8" @bind-Value="@Employee.Comment" placeholder="Enter comment"></InputTextArea>  
  55.             <ValidationMessage class="offset-sm-3 col-sm-8" For="@(() => Employee.Comment)" />  
  56.         </div>  
  57.         <button type="submit" class="btn btn-primary edit-btn">Save employee</button>  
  58.         <a class="btn btn-danger" @onclick="@DeleteEmployee">  
  59. Delete  
  60. </a>  
  61.         <a class="btn btn-outline-primary" @onclick="@NavigateToOverview">Back to overview</a>  
  62.     </EditForm>  
  63. </section>  
  64. }  
  65. else  
  66. {  
  67. <div class="alert @StatusClass">@Message</div>  
  68. }  
  69. }  
  70.    
Run the application on your local machine.
 
You will able to see a list of employees and also able to Edit and Delete using our UI. 
 
Step 3 - Creating a custom Blazor component to Add Employee.
 
We will be using a Modal pop up to allow users to add a new Employee.
 
This Modal pop up will be a custom Blazor component. It will have also basic validations for user input.
 
I had posted a dedicated article for creating and using Custom Add Employee Modal in our project.
 
Once the AddEmployee Component is integrated on EmployeePage we would able to add new Employee and the Employee list is refreshed with new data.
 

Summary

 
In this article, we learned how to create a fully functional Blazor web assembly app. In our next article, we will deploy this app to Azure using the PAAS model and Azure SQL to store user data.
 
Thanks a lot for reading. I hope you liked this article. Please share your valuable suggestions and feedback. Write in the comment box in case you have any questions. Have a good day!