Tutorial - Employee Management System - Part 4 | Consuming the REST API from Blazor Web App
Part 4: Consuming the REST API from ASP.NET Core Blazor
We'll now connect our Blazor Web App to the REST API we just built. We'll go through the process of creating services to encapsulate API calls, registering them for dependency injection, and then consuming them in Blazor components, including handling route parameters for detail views.
Step-by-Step Tutorial Guide (Part 4)
Part 4.0: Blazor Web App Project Setup (Pre-requisites)
Before we start, ensure your EmployeeManagement.Web project is set up correctly:
- Project Creation: You should have an
EmployeeManagement.Webproject, ideally created as an ASP.NET Core Blazor Web App (or Blazor Server App if you followed an older guide) in your solution. - Project Reference: Ensure
EmployeeManagement.Webhas a project reference toEmployeeManagement.Models.- In Solution Explorer, right-click
EmployeeManagement.Web->Add->Project Reference.... - Check
EmployeeManagement.Modelsand clickOK.
- In Solution Explorer, right-click
- NuGet Package: You'll need
System.Net.Http.JsonforGetFromJsonAsync. This is typically included by default in modern Blazor project templates, but ensure it's there.- In Package Manager Console (Tools -> NuGet Package Manager -> Package Manager Console):
Install-Package System.Net.Http.Json -ProjectName EmployeeManagement.Web
Part 4.1: Create a Service Layer for API Calls
For better separation of concerns and testability, we'll create a dedicated service that handles all interactions with our Employee REST API.
Step 4.1.1: Create Services Folder
- In your
EmployeeManagement.Webproject, right-click on the project ->Add->New Folder. - Name the folder
Services.
Step 4.1.2: Define IEmployeeService.cs Interface
- In the
Servicesfolder, right-click ->Add->New Item...->Interface. - Name it
IEmployeeService.cs. - Replace its content with the following:
// EmployeeManagement.Web/Services/IEmployeeService.cs using EmployeeManagement.Models; // For the Employee model using System.Collections.Generic; using System.Threading.Tasks; namespace EmployeeManagement.Web.Services { public interface IEmployeeService { // Method to get all employees Task<IEnumerable<Employee>> GetEmployees(); // Method to get a single employee by ID Task<Employee> GetEmployee(int id); // Added for detail view } }
Step 4.1.3: Implement EmployeeService.cs
This class will use HttpClient to make actual HTTP requests to your REST API.
- In the
Servicesfolder, right-click ->Add->Class. - Name it
EmployeeService.cs. - Replace its content with the following:
// EmployeeManagement.Web/Services/EmployeeService.cs using EmployeeManagement.Models; using System.Collections.Generic; using System.Net.Http; // For HttpClient using System.Net.Http.Json; // For GetFromJsonAsync using System.Threading.Tasks; namespace EmployeeManagement.Web.Services { public class EmployeeService : IEmployeeService { private readonly HttpClient httpClient; // HttpClient is injected via constructor public EmployeeService(HttpClient httpClient) { this.httpClient = httpClient; } public async Task<Employee> GetEmployee(int id) { // Call the API endpoint: api/employees/{id} return await httpClient.GetFromJsonAsync<Employee>($"api/employees/{id}"); } public async Task<IEnumerable<Employee>> GetEmployees() { // Call the API endpoint: api/employees return await httpClient.GetFromJsonAsync<Employee[]>("api/employees"); } } }
- Important Note on
HttpClient.GetJsonAsyncvsHttpClient.GetFromJsonAsync: The prompt mentionedGetJsonAsync. This method was part of an olderMicrosoft.AspNetCore.Blazor.HttpClientNuGet package primarily used with the initial Blazor WebAssembly preview. In modern ASP.NET Core Blazor, the recommended way is to useGetFromJsonAsync(andPostAsJsonAsync, etc.) from theSystem.Net.Http.Jsonnamespace, which is more robust and part of the standard library. I've updated the code to reflect this modern approach.
Part 4.2: Register HttpClient and EmployeeService for Dependency Injection
For our Blazor components to use IEmployeeService, we need to register it with the Dependency Injection container. This is done in the Program.cs file of the Blazor Web App project.
- Modify
EmployeeManagement.Web/Program.cs:- Open
Program.cs. - Add necessary
usingstatements at the top:
- Open
- Register the services. Find the
builder.Servicessection and add the following: - Crucial Note on
BaseAddress: TheBaseAddressmust match the URL where yourEmployeeManagement.Apiproject is running.- To find this: In your
EmployeeManagement.Apiproject, openProperties/launchSettings.json. Look for theapplicationUrlunder the "https" profile (or "IIS Express" if you're using that). It will be something likehttps://localhost:44379/orhttps://localhost:7001/. Copy that exact URL.
- To find this: In your
// Add these using statements at the top of Program.cs using EmployeeManagement.Web.Services; // For IEmployeeService, EmployeeService
// EmployeeManagement.Web/Program.cs (relevant section) var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); // If it's a Blazor Server project // Or AddWebAssemblyApp() if it's a Blazor WebAssembly project // In Blazor Web Apps (new template), it handles both. // Register HttpClient and EmployeeService builder.Services.AddHttpClient<IEmployeeService, EmployeeService>(client => { // IMPORTANT: Replace with the actual URL of your EmployeeManagement.Api project // You can find this in the EmployeeManagement.Api project's launchSettings.json (e.g., "applicationUrl") // Ensure it uses HTTPS if your API uses HTTPS. client.BaseAddress = new Uri("https://localhost:44379/"); // Example URL }); var app = builder.Build(); // ... rest of Program.cs
Part 4.3: Displaying Employee List in a Blazor Component
Now, we'll create a Blazor component to fetch and display all employees using our EmployeeService.
Step 4.3.1: Create EmployeeList.razor Component
- In the
EmployeeManagement.Webproject, right-click on thePagesfolder ->Add->Razor Component. - Name it
EmployeeList.razor. - Replace its content with the following markup:
@page "/" // This component will be the default page at the root URL @inherits EmployeeListBase <h3>Employee List</h3> @if (Employees == null) { <p><em>Loading employees...</em></p> } @else { <div class="card-deck"> @foreach (var employee in Employees) { <div class="card m-3" style="min-width: 18rem; max-width:30.5%;"> <div class="card-header"> <h3>@employee.FirstName @employee.LastName</h3> </div> <img class="card-img-top imageThumbnail" src="@employee.PhotoPath" /> <div class="card-footer text-center"> <a href="@($"employeedetails/{employee.EmployeeId}")" class="btn btn-primary m-1">View</a> <a href="#" class="btn btn-primary m-1">Edit</a> <a href="#" class="btn btn-danger m-1">Delete</a> </div> </div> } </div> }
@page "/": This makesEmployeeList.razorthe default component for the root URL of your Blazor app.@inherits EmployeeListBase: This tells the component to inherit from a code-behind class (EmployeeListBase.cs) for its logic.imageThumbnailclass: You might need to add this to yourwwwroot/css/app.css(or equivalent) for proper styling:
/* wwwroot/css/app.css or a new custom CSS file */ .imageThumbnail { height: 150px; /* Adjust as needed */ width: auto; border-radius: 5px; margin: 5px; } .card-deck { display: flex; flex-wrap: wrap; justify-content: center; } .card { box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); transition: 0.3s; } .card:hover { box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); }
Step 4.3.2: Create EmployeeListBase.cs Code-Behind
- In the
Pagesfolder, right-click ->Add->Class. - Name it
EmployeeListBase.cs. - Replace its content with the following:
// EmployeeManagement.Web/Pages/EmployeeListBase.cs using EmployeeManagement.Models; using EmployeeManagement.Web.Services; using Microsoft.AspNetCore.Components; // Required for [Inject], ComponentBase using System.Collections.Generic; using System.Linq; // For .ToList() using System.Threading.Tasks; namespace EmployeeManagement.Web.Pages { public class EmployeeListBase : ComponentBase { // [Inject] attribute automatically provides an instance of IEmployeeService [Inject] public IEmployeeService EmployeeService { get; set; } // Property to hold the list of employees fetched from the API public IEnumerable<Employee> Employees { get; set; } // OnInitializedAsync is a lifecycle method called when the component is initialized protected override async Task OnInitializedAsync() { // Call the service to get all employees and convert to List Employees = (await EmployeeService.GetEmployees()).ToList(); } } }
Step 4.3.3: Build and Test (Employee List)
- Ensure both
EmployeeManagement.ApiandEmployeeManagement.Webare running.- You can set both as startup projects: Right-click solution ->
Properties->Startup Project->Multiple startup projects. Select "Start" for both.
- You can set both as startup projects: Right-click solution ->
- Run the application (F5).
- Your browser should open to the Blazor app. You should see the list of employees fetched from your API. If you don't, check the browser's developer console for errors and ensure your
BaseAddressinProgram.csis correct and your API is running.
Part 4.4: Displaying Employee Details with Route Parameters
Now, we'll create a component to display details of a single employee, using a route parameter to pass the employee's ID.
Step 4.4.1: Create EmployeeDetails.razor Component
- In the
Pagesfolder, right-click ->Add->Razor Component. - Name it
EmployeeDetails.razor. - Replace its content with the following markup:
@page "/employeedetails/{Id:int}" // Route with an integer parameter named 'Id' @page "/employeedetails" // Optional route without parameter (for demonstrating optionality) @inherits EmployeeDetailsBase <div class="row justify-content-center m-3"> <div class="col-sm-8"> <div class="card"> <div class="card-header"> <h1>@Employee.FirstName @Employee.LastName</h1> </div> <div class="card-body text-center"> @if (!string.IsNullOrEmpty(Employee.PhotoPath)) { <img class="card-img-top" src="@Employee.PhotoPath" style="max-width: 200px; border-radius: 5px; margin-bottom: 10px;" /> } @else { <p>No photo available</p> } <hr /> <h4>Employee ID : @Employee.EmployeeId</h4> <h4>Email : @Employee.Email</h4> <h4>Department : @(Employee.Department?.DepartmentName ?? "N/A")</h4> @* Safely access Department Name *@ <h4>Date of Birth : @Employee.DateOfBrith.ToShortDateString()</h4> <h4>Gender : @Employee.Gender</h4> </div> <div class="card-footer text-center"> <a href="/" class="btn btn-primary">Back to List</a> <a href="#" class="btn btn-primary">Edit</a> <a href="#" class="btn btn-danger">Delete</a> </div> </div> </div> </div>
@page "/employeedetails/{Id:int}": This defines a route for this component.{Id:int}means it expects an integer parameter namedId.@page "/employeedetails": This second@pagedirective makes theIdparameter optional. If no ID is provided, it will navigate to this route.@(Employee.Department?.DepartmentName ?? "N/A"): This uses the null-conditional operator (?.) to safely accessDepartmentName. IfEmployee.Departmentis null (e.g., if department wasn't eagerly loaded or somehow missing), it won't throw an error and will display "N/A". (We already ensured eager loading inEmployeeRepository).
Step 4.4.2: Create EmployeeDetailsBase.cs Code-Behind
- In the
Pagesfolder, right-click ->Add->Class. - Name it
EmployeeDetailsBase.cs. - Replace its content with the following:
// EmployeeManagement.Web/Pages/EmployeeDetailsBase.cs using EmployeeManagement.Models; using EmployeeManagement.Web.Services; using Microsoft.AspNetCore.Components; // Required for [Inject], ComponentBase, [Parameter] using System.Threading.Tasks; namespace EmployeeManagement.Web.Pages { public class EmployeeDetailsBase : ComponentBase { // Public property to hold the employee details. Initialized to prevent null reference errors. public Employee Employee { get; set; } = new Employee(); // Inject the IEmployeeService to fetch employee data [Inject] public IEmployeeService EmployeeService { get; set; } // [Parameter] attribute maps the route parameter (e.g., /{Id}) to this property. // The name must match the route parameter name (case-insensitive in routing, but good to match case). [Parameter] public int Id { get; set; } // Changed to int type to match route constraint :int // OnInitializedAsync is called when the component is first initialized protected override async Task OnInitializedAsync() { // If Id is 0 (default for int) or not provided in the URL, fetch employee with ID 1 as a default // This covers the optional parameter route: /employeedetails if (Id == 0) { Id = 1; } // Call the service to get the specific employee by ID Employee = await EmployeeService.GetEmployee(Id); } } }
[Parameter] public int Id { get; set; }: This is crucial. Blazor's router will automatically populate thisIdproperty with the value from the URL's route parameter.Id = Id ?? "1";(from prompt) vsif (Id == 0) { Id = 1; }(my code): Since we use{Id:int}in the route template, theIdparameter will be anint. If it's not provided in the URL (e.g., navigating to/employeedetailswithout an ID), its default value will be0. Thus,if (Id == 0)is the correct check for an optional integer parameter.
Step 4.4.3: Build and Test (Employee Details)
- Ensure both
EmployeeManagement.ApiandEmployeeManagement.Webare running. - Navigate to your Blazor app.
- On the employee list page, click the "View" button next to an employee.
- Expected Result: You should be redirected to a URL like
/employeedetails/1(if you clicked view for employee 1), and the details for that employee should be displayed.
- Expected Result: You should be redirected to a URL like
- Try manually navigating to
/employeedetails(without an ID) to see if it defaults to employee ID 1. - Try navigating to
/employeedetails/99(an invalid ID) to see how your component handles anullEmployeeobject (e.g., by displaying a loading message or error). Currently, it will just show empty data sinceEmployeeis initialized to a new object. You might want to add an@if (Employee == null)check inEmployeeDetails.razorto show "Employee not found" message.
Part 4.5: Final Code Reference
Here's the complete, final code for the Blazor Web App's service layer and components after implementing all features described above.
Final EmployeeManagement.Web/Services/IEmployeeService.cs:
using EmployeeManagement.Models; using System.Collections.Generic; using System.Threading.Tasks; namespace EmployeeManagement.Web.Services { public interface IEmployeeService { Task<IEnumerable<Employee>> GetEmployees(); Task<Employee> GetEmployee(int id); } }
Final EmployeeManagement.Web/Services/EmployeeService.cs:
using EmployeeManagement.Models; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; // For GetFromJsonAsync using System.Threading.Tasks; namespace EmployeeManagement.Web.Services { public class EmployeeService : IEmployeeService { private readonly HttpClient httpClient; public EmployeeService(HttpClient httpClient) { this.httpClient = httpClient; } public async Task<Employee> GetEmployee(int id) { return await httpClient.GetFromJsonAsync<Employee>($"api/employees/{id}"); } public async Task<IEnumerable<Employee>> GetEmployees() { return await httpClient.GetFromJsonAsync<Employee[]>("api/employees"); } } }
Final EmployeeManagement.Web/Program.cs (relevant section):
// ... (existing using statements) using EmployeeManagement.Web.Services; // Add this line var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); // Or whatever Blazor hosting model you are using // Register HttpClient and EmployeeService builder.Services.AddHttpClient<IEmployeeService, EmployeeService>(client => { // IMPORTANT: Replace with the actual URL of your EmployeeManagement.Api project // You can find this in the EmployeeManagement.Api project's launchSettings.json (e.g., "applicationUrl") // Ensure it uses HTTPS if your API uses HTTPS. client.BaseAddress = new Uri("https://localhost:44379/"); // Example URL }); var app = builder.Build(); // ... (rest of Program.cs)
Final EmployeeManagement.Web/Pages/EmployeeList.razor:
@page "/" @inherits EmployeeListBase <h3>Employee List</h3> @if (Employees == null) { <p><em>Loading employees...</em></p> } @else { <div class="card-deck"> @foreach (var employee in Employees) { <div class="card m-3" style="min-width: 18rem; max-width:30.5%;"> <div class="card-header"> <h3>@employee.FirstName @employee.LastName</h3> </div> <img class="card-img-top imageThumbnail" src="@employee.PhotoPath" /> <div class="card-footer text-center"> <a href="@($"employeedetails/{employee.EmployeeId}")" class="btn btn-primary m-1">View</a> <a href="#" class="btn btn-primary m-1">Edit</a> <a href="#" class="btn btn-danger m-1">Delete</a> </div> </div> } </div> }
Final EmployeeManagement.Web/Pages/EmployeeListBase.cs:
using EmployeeManagement.Models; using EmployeeManagement.Web.Services; using Microsoft.AspNetCore.Components; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace EmployeeManagement.Web.Pages { public class EmployeeListBase : ComponentBase { [Inject] public IEmployeeService EmployeeService { get; set; } public IEnumerable<Employee> Employees { get; set; } protected override async Task OnInitializedAsync() { Employees = (await EmployeeService.GetEmployees()).ToList(); } } }
Final EmployeeManagement.Web/Pages/EmployeeDetails.razor:
@page "/employeedetails/{Id:int}" @page "/employeedetails" @inherits EmployeeDetailsBase <div class="row justify-content-center m-3"> <div class="col-sm-8"> <div class="card"> <div class="card-header"> <h1>@Employee.FirstName @Employee.LastName</h1> </div> <div class="card-body text-center"> @if (!string.IsNullOrEmpty(Employee.PhotoPath)) { <img class="card-img-top" src="@Employee.PhotoPath" style="max-width: 200px; border-radius: 5px; margin-bottom: 10px;" /> } @else { <p>No photo available</p> } <hr /> <h4>Employee ID : @Employee.EmployeeId</h4> <h4>Email : @Employee.Email</h4> <h4>Department : @(Employee.Department?.DepartmentName ?? "N/A")</h4> <h4>Date of Birth : @Employee.DateOfBrith.ToShortDateString()</h4> <h4>Gender : @Employee.Gender</h4> </div> <div class="card-footer text-center"> <a href="/" class="btn btn-primary">Back to List</a> <a href="#" class="btn btn-primary">Edit</a> <a href="#" class="btn btn-danger">Delete</a> </div> </div> </div> </div>
Final EmployeeManagement.Web/Pages/EmployeeDetailsBase.cs:
using EmployeeManagement.Models; using EmployeeManagement.Web.Services; using Microsoft.AspNetCore.Components; using System.Threading.Tasks; namespace EmployeeManagement.Web.Pages { public class EmployeeDetailsBase : ComponentBase { public Employee Employee { get; set; } = new Employee(); [Inject] public IEmployeeService EmployeeService { get; set; } [Parameter] public int Id { get; set; } protected override async Task OnInitializedAsync() { // If Id is 0 (default for int) or not provided in the URL, use 1 as a default // This handles navigation to /employeedetails without an explicit ID. if (Id == 0) { Id = 1; } Employee = await EmployeeService.GetEmployee(Id); } } }
This completes the initial setup for consuming your REST API in a Blazor Web App. You now have a working list view and detail view for employees!
Comments
Post a Comment