Exercise: Split Razor Component (Partial Classes)

Exercise Set 4: Split Razor Component (Partial Classes)

Exercise Set 4: Split Razor Component (Partial Classes)

Goal: Understand how to separate the C# logic of a Razor component into a separate .cs file, known as a partial class.

Scenario: Refactor the ProductDetails.razor component by moving its @code block into a partial class.

Instructions:

Part 1: Splitting ProductDetails.razor

Continue from Exercise Set 3. If you closed it, open BlazorRoutingExercise.

1. Modify ProductDetails.razor:

  • Open Pages/ProductDetails.razor.
  • Remove the entire @code { ... } block. You should be left only with the HTML markup and the @page, @using, and @inject directives.
Pages/ProductDetails.razor (after modification):
@page "/productdetails/{ProductId:int}"
@using BlazorRoutingExercise.Data
@using BlazorRoutingExercise.Models
@inject NavigationManager NavManager
@inject IProductDataService ProductService
@inject ILogger<ProductDetails> Logger

<PageTitle>Product Details</PageTitle>

<h1>Product Details</h1>

@if (product == null)
{
    <p><em>Product not found or loading...</em></p>
}
@else
{
    <div>
        <h3>@product.Name</h3>
        <p><strong>ID:</strong> @product.Id</p>
        <p><strong>Price:</strong> @product.Price.ToString("C")</p>
        <p><strong>Description:</strong> @product.Description</p>
    </div>
    <button class="btn btn-secondary mt-3" @onclick="GoBack">Back to Products</button>
}

2. Create the Partial Class:

  • In the Pages folder, next to ProductDetails.razor, add a new class file named ProductDetails.razor.cs. (The .razor.cs naming convention is important for Visual Studio to recognize it as a partial class for the .razor file).
  • Add the following code to ProductDetails.razor.cs:
Pages/ProductDetails.razor.cs:
using Microsoft.AspNetCore.Components; // Essential for [Parameter]
using Microsoft.Extensions.Logging;    // Essential for ILogger
using BlazorRoutingExercise.Data;     // For IProductDataService
using BlazorRoutingExercise.Models;   // For Product model

namespace BlazorRoutingExercise.Pages
{
    // IMPORTANT: The class name must match the component name (ProductDetails)
    // and be declared as 'partial' to link it to ProductDetails.razor.
    public partial class ProductDetails : ComponentBase // Inherit from ComponentBase
    {
        // Injectables are now public properties with [Inject] attribute
        [Inject] public NavigationManager NavManager { get; set; } = default!;
        [Inject] public ILogger<ProductDetails> Logger { get; set; } = default!;
        [Inject] public IProductDataService ProductService { get; set; } = default!;


        // Route Parameter
        [Parameter]
        public int ProductId { get; set; }

        // Component State
        private Product? product;

        // Lifecycle methods and other logic
        protected override void OnParametersSet()
        {
            Logger.LogInformation("ProductDetails partial class: OnParametersSet for Product ID: {ProductId}", ProductId);
            product = ProductService.GetProductById(ProductId);
            if (product == null)
            {
                Logger.LogError("Product with ID {ProductId} not found in data service (from partial class).", ProductId);
            }
        }

        private void GoBack()
        {
            Logger.LogInformation("Navigating back to product list (from partial class).");
            NavManager.NavigateTo("/products");
        }
    }
}

Important Notes on Partial Classes:

  • The namespace (BlazorRoutingExercise.Pages) must match the namespace where ProductDetails.razor is compiled (which is BlazorRoutingExercise.Pages by default because it's in the Pages folder).
  • The class name (ProductDetails) must exactly match the component's name.
  • It must be public partial class.
  • It must inherit from ComponentBase (or another component type).
  • @inject directives from the .razor file are replaced by [Inject] attributes on public properties in the partial class.
  • [Parameter] attributes remain on public properties for route parameters.

3. Test the Split Component:

  • Run the application.
  • Navigate to the "Products" list and then to "Product Details".
  • Verify that everything functions exactly as before. The logs you added in the previous exercise should still appear, confirming the partial class is working.

Part 2: Challenge - Splitting Another Component

Challenge:

  • Repeat the process for the ProductList.razor component.
  • Create ProductList.razor.cs.
  • Move the @code block's content (state, OnInitialized, ViewProductDetails method) into the partial class.
  • Remember to replace @inject with [Inject] properties.
  • Test thoroughly to ensure the ProductList page still works after the refactoring.

Solution for ProductList.razor.cs:

Pages/ProductList.razor (after modification):
@page "/products"
@using BlazorRoutingExercise.Data
@using BlazorRoutingExercise.Models
@inject NavigationManager NavManager
@inject IProductDataService ProductService
@inject ILogger<ProductList> Logger

<PageTitle>Products</PageTitle>

<h1>Our Products</h1>

<p>Explore our range of high-quality products.</p>

@if (products == null)
{
    <p><em>Loading products...</em></p>
}
@else
{
    <table class="table table-striped">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in products)
            {
                <tr>
                    <td>@product.Id</td>
                    <td>@product.Name</td>
                    <td>@product.Price.ToString("C")</td>
                    <td>
                        <button class="btn btn-info btn-sm" @onclick="() => ViewProductDetails(product.Id)">View Details</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
Pages/ProductList.razor.cs (Solution):
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using BlazorRoutingExercise.Data;
using BlazorRoutingExercise.Models;
using System.Collections.Generic;

namespace BlazorRoutingExercise.Pages
{
    public partial class ProductList : ComponentBase
    {
        [Inject] public NavigationManager NavManager { get; set; } = default!;
        [Inject] public IProductDataService ProductService { get; set; } = default!;
        [Inject] public ILogger<ProductList> Logger { get; set; } = default!;

        private List<Product>? products;

        protected override void OnInitialized()
        {
            products = ProductService.GetAllProducts();
            Logger.LogInformation("ProductList component initialized. Displaying {ProductCount} products.", products?.Count ?? 0);

            // Challenge: Add a LogDebug message for each product being rendered
            if (products != null)
            {
                foreach (var product in products)
                {
                    Logger.LogDebug("Rendering product: {ProductName} (ID: {ProductId})", product.Name, product.Id);
                }
            }
        }

        private void ViewProductDetails(int productId)
        {
            Logger.LogInformation("Navigating to details for Product ID: {ProductId}", productId);
            NavManager.NavigateTo($"productdetails/{productId}");
        }
    }
}

This exercise demonstrates how partial classes can improve the organization and readability of your Blazor components by separating the C# logic from the HTML markup.

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