Exercise: ASP.NET Core Blazor Logging

Exercise Set 3: ASP.NET Core Blazor Logging

Exercise Set 3: ASP.NET Core Blazor Logging

Goal: Understand how to add logging to Blazor components and services, and how to configure logging levels.

Scenario: Add logging to the "Product Catalog" application to track component initialization, product viewing, and any potential issues.

Instructions:

Part 1: Basic Logging in Components and Service

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

1. Configure Logging in Program.cs:

  • Open Program.cs.
  • Add using Microsoft.Extensions.Logging; at the top.
  • Add the following configuration within the var builder = WebApplication.CreateBuilder(args); block (before var app = builder.Build();):
Program.cs (additions/modifications):
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using BlazorRoutingExercise.Data; // Ensure this using is present for ProductDataService namespace
using Microsoft.Extensions.Logging; // Add this using statement for logging configuration

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>(); // Default Blazor service
builder.Services.AddSingleton<IProductDataService, ProductDataService>(); // From Exercise 2

// Configure Logging Providers and Filters:
// Analogy: Setting up our security cameras and deciding what they should record.
builder.Logging.ClearProviders(); // Clear default console provider
builder.Logging.AddConsole();   // Send logs to the console window where the server runs
builder.Logging.AddDebug();     // Send logs to the Debug output window in Visual Studio

// Set minimum log levels for different categories (namespaces).
// If a category isn't listed, it inherits from its parent or the Default.
builder.Logging.SetMinimumLevel(LogLevel.Information); // Default to Information and higher

// Log our specific application components/services at Debug or Information level
builder.Logging.AddFilter("BlazorRoutingExercise.Data.ProductDataService", LogLevel.Debug);
builder.Logging.AddFilter("BlazorRoutingExercise.Pages.ProductList", LogLevel.Information);
builder.AddFilter("BlazorRoutingExercise.Pages.ProductDetails", LogLevel.Information);

// Reduce noise from Microsoft/System framework logs
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter("System", LogLevel.Warning);

app = builder.Build();
// ... (rest of Program.cs remains unchanged) ...

2. Add ILogger to ProductDataService:

  • Open Data/ProductDataService.cs.
  • Add using Microsoft.Extensions.Logging; at the top.
  • Modify the constructor to accept ILogger<ProductDataService>:
Data/ProductDataService.cs (modified constructor):
using Microsoft.Extensions.Logging; // Add this
// ... other usings ...

namespace BlazorRoutingExercise.Data
{
    public class ProductDataService : IProductDataService
    {
        private List<Product> _products = new List<Product> { /* ... products ... */ };
        private readonly ILogger<ProductDataService> _logger; // Declare logger

        // Inject the logger via constructor
        public ProductDataService(ILogger<ProductDataService> logger)
        {
            _logger = logger;
            _logger.LogInformation("ProductDataService initialized with {ProductCount} products.", _products.Count);
        }
        // ... (rest of class remains unchanged) ...
    }
}
  • Add a log statement to GetProductById:
Data/ProductDataService.cs (modified GetProductById):
public Product? GetProductById(int id)
{
    _logger.LogDebug("Attempting to retrieve product with ID: {ProductId}", id);
    var product = _products.FirstOrDefault(p => p.Id == id);
    if (product == null)
    {
        _logger.LogWarning("Product with ID {ProductId} not found.", id);
    }
    return product;
}

Important: If ProductDataService was registered as a Singleton in Program.cs in the previous exercise, you're all set. If it was AddScoped or AddTransient, the DI container will still correctly provide the ILogger.

3. Add ILogger to ProductList.razor:

  • Open Pages/ProductList.razor.
  • Add @inject ILogger<ProductList> Logger at the top.
  • Add log statements:
Pages/ProductList.razor (relevant section):
@page "/products"
@using BlazorRoutingExercise.Data
@using BlazorRoutingExercise.Models
@inject NavigationManager NavManager
@inject IProductDataService ProductService // From Exercise 2
@inject ILogger<ProductList> Logger // Add this line

@* ... HTML markup ... *@

@code {
    // ... existing code ...

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

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

4. Add ILogger to ProductDetails.razor:

  • Open Pages/ProductDetails.razor.
  • Add @inject ILogger<ProductDetails> Logger at the top.
  • Add log statements:
Pages/ProductDetails.razor (relevant section):
@page "/productdetails/{ProductId:int}"
@using BlazorRoutingExercise.Data
@using BlazorRoutingExercise.Models
@inject NavigationManager NavManager
@inject IProductDataService ProductService // From Exercise 2
@inject ILogger<ProductDetails> Logger // Add this line

@* ... HTML markup ... *@

@code {
    [Parameter]
    public int ProductId { get; set; }

    // private ProductDataService _productService = new ProductDataService(); // Removed from Exercise 2
    private Product? product;

    protected override void OnParametersSet()
    {
        Logger.LogInformation("ProductDetails component parameters set. Looking for Product ID: {ProductId}", ProductId);
        product = ProductService.GetProductById(ProductId);
        if (product == null)
        {
            Logger.LogError("Product with ID {ProductId} was requested but not found in data service.", ProductId);
        }
    }

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

5. Test Your Logging:

  • Run the application.
  • Open Visual Studio's Output window (View > Output, then select "Web Server" or "ASP.NET Core Web Server" from the dropdown).
  • Navigate through the "Products" page and click "View Details" for a few products.
  • Observe:
    • You should see Information logs for component initialization and navigation actions.
    • You should see Debug logs from ProductDataService.GetProductById (because we set its filter to LogLevel.Debug).
    • If you manually type an invalid product ID in the URL (e.g., /productdetails/999), you should see Warning logs from ProductDataService and Error logs from ProductDetails.razor.

Part 2: Challenge - Log Levels and Custom Messages

1. Experiment with Log Levels:

  • In Program.cs, change the filter for BlazorRoutingExercise.Pages.ProductDetails from LogLevel.Information to LogLevel.Debug.
  • Program.cs (filter change example):
    // ... other filters ...
    builder.Logging.AddFilter("BlazorRoutingExercise.Pages.ProductDetails", LogLevel.Debug);
    // ...
    
  • Run the app. Does it generate more logs when you navigate to product details? (Yes, if you added Debug logs to that component).
  • Change the filter for BlazorRoutingExercise.Data.ProductDataService from LogLevel.Debug to LogLevel.Warning.
  • Program.cs (filter change example):
    // ... other filters ...
    builder.Logging.AddFilter("BlazorRoutingExercise.Data.ProductDataService", LogLevel.Warning);
    // ...
    
  • Run the app. Do you still see the LogDebug messages from GetProductById? (No, because the filter now only allows Warning and higher).
  • Change it back to LogLevel.Debug for the next steps.

2. Challenge:

  • In ProductList.razor, add a custom LogDebug message inside the @foreach loop, perhaps logging the name of each product as it's being rendered.
  • Verify that this log only appears when LogLevel.Debug is configured for ProductList.razor in Program.cs.
Pages/ProductList.razor (Challenge modification):
@* ... existing HTML and @code block ... *@

@code {
    // ... existing code ...

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

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

    // ... rest of code ...
}

This tutorial guide clearly shows how to integrate and utilize the powerful logging capabilities in Blazor applications for monitoring and debugging.

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