Tutorial - Employee Management System - Part 4 | Consuming the REST API from Blazor Web App

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:

  1. Project Creation: You should have an EmployeeManagement.Web project, ideally created as an ASP.NET Core Blazor Web App (or Blazor Server App if you followed an older guide) in your solution.
  2. Project Reference: Ensure EmployeeManagement.Web has a project reference to EmployeeManagement.Models.
    • In Solution Explorer, right-click EmployeeManagement.Web -> Add -> Project Reference....
    • Check EmployeeManagement.Models and click OK.
  3. NuGet Package: You'll need System.Net.Http.Json for GetFromJsonAsync. 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

  1. In your EmployeeManagement.Web project, right-click on the project -> Add -> New Folder.
  2. Name the folder Services.

Step 4.1.2: Define IEmployeeService.cs Interface

  1. In the Services folder, right-click -> Add -> New Item... -> Interface.
  2. Name it IEmployeeService.cs.
  3. 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.

  1. In the Services folder, right-click -> Add -> Class.
  2. Name it EmployeeService.cs.
  3. 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.GetJsonAsync vs HttpClient.GetFromJsonAsync: The prompt mentioned GetJsonAsync. This method was part of an older Microsoft.AspNetCore.Blazor.HttpClient NuGet package primarily used with the initial Blazor WebAssembly preview. In modern ASP.NET Core Blazor, the recommended way is to use GetFromJsonAsync (and PostAsJsonAsync, etc.) from the System.Net.Http.Json namespace, 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.

  1. Modify EmployeeManagement.Web/Program.cs:
    • Open Program.cs.
    • Add necessary using statements at the top:
  2. // Add these using statements at the top of Program.cs
    using EmployeeManagement.Web.Services; // For IEmployeeService, EmployeeService
    
    • Register the services. Find the builder.Services section and add the following:
    // 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
    
    • Crucial Note on BaseAddress: The BaseAddress must match the URL where your EmployeeManagement.Api project is running.
      • To find this: In your EmployeeManagement.Api project, open Properties/launchSettings.json. Look for the applicationUrl under the "https" profile (or "IIS Express" if you're using that). It will be something like https://localhost:44379/ or https://localhost:7001/. Copy that exact URL.

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

  1. In the EmployeeManagement.Web project, right-click on the Pages folder -> Add -> Razor Component.
  2. Name it EmployeeList.razor.
  3. 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 makes EmployeeList.razor the 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.
  • imageThumbnail class: You might need to add this to your wwwroot/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

  1. In the Pages folder, right-click -> Add -> Class.
  2. Name it EmployeeListBase.cs.
  3. 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)

  1. Ensure both EmployeeManagement.Api and EmployeeManagement.Web are running.
    • You can set both as startup projects: Right-click solution -> Properties -> Startup Project -> Multiple startup projects. Select "Start" for both.
  2. Run the application (F5).
  3. 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 BaseAddress in Program.cs is 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

  1. In the Pages folder, right-click -> Add -> Razor Component.
  2. Name it EmployeeDetails.razor.
  3. 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 named Id.
  • @page "/employeedetails": This second @page directive makes the Id parameter 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 access DepartmentName. If Employee.Department is 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 in EmployeeRepository).

Step 4.4.2: Create EmployeeDetailsBase.cs Code-Behind

  1. In the Pages folder, right-click -> Add -> Class.
  2. Name it EmployeeDetailsBase.cs.
  3. 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 this Id property with the value from the URL's route parameter.
  • Id = Id ?? "1"; (from prompt) vs if (Id == 0) { Id = 1; } (my code): Since we use {Id:int} in the route template, the Id parameter will be an int. If it's not provided in the URL (e.g., navigating to /employeedetails without an ID), its default value will be 0. Thus, if (Id == 0) is the correct check for an optional integer parameter.

Step 4.4.3: Build and Test (Employee Details)

  1. Ensure both EmployeeManagement.Api and EmployeeManagement.Web are running.
  2. Navigate to your Blazor app.
  3. 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.
  4. Try manually navigating to /employeedetails (without an ID) to see if it defaults to employee ID 1.
  5. Try navigating to /employeedetails/99 (an invalid ID) to see how your component handles a null Employee object (e.g., by displaying a loading message or error). Currently, it will just show empty data since Employee is initialized to a new object. You might want to add an @if (Employee == null) check in EmployeeDetails.razor to 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

Popular posts from this blog

Blazor: Building Web Apps with C# - Introduction

Blazor WebAssembly Hosted App Tutorial: Envelope Tracker System

Securing MVC-based Applications Using Blazor