Tutorial - EMS - Part 4 (continue) | Displaying Related Data in Blazor

Employee Management System - Part 4.6: Displaying Related Data in Blazor

Part 4.6: Displaying Data from Two or More Tables in Blazor

We've already laid the groundwork for this in previous steps:

EmployeeManagement.Models/Employee.cs:

It should contain both the `DepartmentId` foreign key and the `Department` navigation property. (Let's ensure this is explicitly stated again to avoid confusion).

Here's the definitive Employee.cs that supports eager loading of the `Department` object:

// EmployeeManagement.Models/Employee.cs
using System;
using System.ComponentModel.DataAnnotations; // For Required, StringLength etc.

namespace EmployeeManagement.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }

        [Required(ErrorMessage = "First Name is required.")]
        [StringLength(100, MinimumLength = 2, ErrorMessage = "First Name must be between 2 and 100 characters.")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is required.")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Email is required.")]
        [EmailAddress(ErrorMessage = "Invalid Email Format.")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Date of Birth is required.")]
        public DateTime DateOfBrith { get; set; }

        [Required(ErrorMessage = "Gender is required.")]
        public Gender Gender { get; set; }

        public int DepartmentId { get; set; } // Foreign Key
        public Department Department { get; set; } // Navigation Property

        public string PhotoPath { get; set; }
    }
}

EmployeeManagement.Api/Models/EmployeeRepository.cs:

The GetEmployee (and GetEmployees) method already uses .Include(e => e.Department) to ensure that when an Employee is fetched, its associated `Department` data is also loaded from the database. This is critical for the Blazor component to access Employee.Department.DepartmentName.

Here's a snippet to confirm GetEmployee (and GetEmployees should be similar):

// EmployeeManagement.Api/Models/EmployeeRepository.cs (snippet)
public async Task<Employee> GetEmployee(int employeeId)
{
    return await appDbContext.Employees
        .Include(e => e.Department) // This line fetches the related Department data
        .FirstOrDefaultAsync(e => e.EmployeeId == employeeId);
}

With these two foundational pieces in place, the Blazor component simply needs to access the data correctly and handle potential loading states.

Part 4.6: Displaying Data from Two or More Tables in Blazor

As the prompt suggests, we want to display the `DepartmentName` which comes from the `Departments` table, alongside `Id`, `Name`, and `Email` from the `Employees` table. Since we've already ensured the `Department` navigation property is loaded in our API, the Blazor component can directly access it.

The key aspects are:

  • The `Employee` model having the `Department` navigation property.
  • The API's repository using `.Include()` to eager load the `Department` data.
  • The Blazor component safely accessing the `Department.DepartmentName`.
  • Adding a loading state to prevent `NullReferenceException` if data isn't ready.

Step 4.6.1: Update `EmployeeDetails.razor` for Department Name and Loading State

We will modify the `EmployeeDetails.razor` to explicitly display `Employee.Department.DepartmentName` and add a loading spinner.

Modify EmployeeManagement.Web/Pages/EmployeeDetails.razor:

  • Open `EmployeeDetails.razor`.
  • Replace its content with the following:
@page "/employeedetails/{Id:int}"
@page "/employeedetails"
@inherits EmployeeDetailsBase

@* Add a null check for Employee and Employee.Department *@
@if (Employee == null)
{
    <div class="spinner-container">
        <div class="spinner"></div>
        <p>Loading employee details...</p>
    </div>
}
@else
{
    <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> @* Direct access now *@
                    <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>
}
Key Changes:
  • Null Check: @if (Employee == null): This guard ensures that the component only attempts to render the employee details once the `Employee` object have been successfully loaded from the API.
  • Loading Spinner: If data is null, it displays a `div` with `spinner` and `spinner-container` classes.
  • Direct Access:

    Department : @(Employee.Department?.DepartmentName ?? "N/A")

    : we can now directly access `Employee.Department.DepartmentName` with the null-conditional `?.` operator, as `Employee.Department` is not guaranteed to be not null at this point. As it is async processing.

Step 4.6.2: Add Basic CSS for the Spinner (Optional but Recommended)

For the spinner to be visible, add some basic CSS to your wwwroot/css/app.css or a dedicated CSS file.

Modify wwwroot/css/app.css:

  • Add the following CSS rules at the end of the file:
/* Basic spinner styles */
.spinner-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 200px; /* Adjust as needed */
}
.spinner {
    border: 4px solid rgba(0, 0, 0, 0.1);
    border-left-color: #337ab7; /* Bootstrap primary blue */
    border-radius: 50%;
    width: 50px;
    height: 50px;
    animation: spin 1s linear infinite;
    margin-bottom: 10px;
}
@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

Step 4.6.3: Build and Test

  • Ensure both EmployeeManagement.Api and EmployeeManagement.Web are running.
  • Navigate to your Blazor app's home page (the Employee List).
  • Click the "View" button for any employee.
  • Expected Result: You should briefly see "Loading employee details..." and a spinner, then the full employee details including the correct Department Name (e.g., "IT", "HR", "Payroll", "Admin") should appear.

Final Code Reference (Employee Details Component)

For completeness, here's the final version of the EmployeeDetails.razor and EmployeeDetailsBase.cs files after incorporating these changes. The EmployeeManagement.Models/Employee.cs and EmployeeManagement.Api/Models/EmployeeRepository.cs remain as they were defined in the comprehensive sections for their respective projects.

Final `EmployeeManagement.Web/Pages/EmployeeDetails.razor`:

@page "/employeedetails/{Id:int}"
@page "/employeedetails"
@inherits EmployeeDetailsBase

@if (Employee == null || Employee.Department == null)
{
    <div class="spinner-container">
        <div class="spinner"></div>
        <p>Loading employee details...</p>
    </div>
}
@else
{
    <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</h4> @* Displaying 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>
}

Final `EmployeeManagement.Web/Pages/EmployeeDetailsBase.cs` (No change needed from previous step):

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 == 0)
            {
                Id = 1;
            }
            Employee = await EmployeeService.GetEmployee(Id);
        }
    }
}

This completes the functionality for displaying related data in your Blazor application!

Comments

Popular posts from this blog

Blazor WebAssembly Hosted App Tutorial: Envelope Tracker System

Authentication and Authorization in Blazor

Securing MVC-based Applications Using Blazor