Exercise: ASP.NET Core Blazor Dependency Injection (DI)

Exercise Set 2: ASP.NET Core Blazor Dependency Injection (DI)

Exercise Set 2: ASP.NET Core Blazor Dependency Injection (DI)

Goal: Understand how to register services for DI and consume them in Blazor components, specifically using the ProductDataService from the previous exercise.

Scenario: Refactor the "Product Catalog" to use DI for the ProductDataService instead of directly instantiating it.

Instructions:

Part 1: Registering and Injecting a Singleton Service

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

1. Modify ProductDataService to be an Interface:

  • Rename Data/ProductDataService.cs to Data/IProductDataService.cs.
  • Change class ProductDataService to interface IProductDataService.
  • Remove the constructor and the private _products field.
  • Your IProductDataService.cs should look like this:
Data/IProductDataService.cs:
namespace BlazorRoutingExercise.Data
{
    public interface IProductDataService
    {
        List<Product> GetAllProducts();
        Product? GetProductById(int id);
    }
}

2. Create the Concrete Implementation:

  • In the Data folder, create a new class file named ProductDataService.cs. (Yes, same name as the original file, but this is the implementation).
  • Make this class implement IProductDataService and move the _products list and methods into it.
  • Your ProductDataService.cs should look like this:
Data/ProductDataService.cs:
using BlazorRoutingExercise.Models;
using System.Collections.Generic;
using System.Linq;

namespace BlazorRoutingExercise.Data
{
    // This class implements the interface
    public class ProductDataService : IProductDataService
    {
        private List<Product> _products = new List<Product>
        {
            new Product { Id = 1, Name = "Laptop Pro", Description = "Powerful laptop for professionals.", Price = 1200.00M },
            new Product { Id = 2, Name = "Gaming Mouse", Description = "Ergonomic mouse for serious gamers.", Price = 75.50M },
            new Product { Id = 3, Name = "Wireless Keyboard", Description = "Full-size, quiet wireless keyboard.", Price = 99.99M },
            new Product { Id = 4, Name = "Monitor UltraWide", Description = "Immersive ultrawide display for productivity.", Price = 450.00M }
        };

        public List<Product> GetAllProducts() => _products;
        public Product? GetProductById(int id) => _products.FirstOrDefault(p => p.Id == id);
    }
}

3. Register the Service in Program.cs:

  • Open Program.cs.
  • Add a using statement for BlazorRoutingExercise.Data.
  • Register IProductDataService with its implementation ProductDataService as a Singleton.
Program.cs (relevant section):
// In Program.cs, within the 'Add services to the container.' section:
builder.Services.AddSingleton<BlazorRoutingExercise.Data.IProductDataService, BlazorRoutingExercise.Data.ProductDataService>();

Why Singleton? For this simple data service, a Singleton means all components (across all user connections) will share the same instance of the product list. If a product were added/removed, all users would see the change (not applicable in this read-only demo, but good to know).

4. Inject the Service into ProductList.razor:

  • Open Pages/ProductList.razor.
  • Remove the line private ProductDataService _productService = new ProductDataService();.
  • Add the @inject directive at the top, specifying the interface:
Pages/ProductList.razor (relevant section):
@inject BlazorRoutingExercise.Data.IProductDataService ProductService // Injected via DI
  • In the OnInitialized method, change _productService.GetAllProducts(); to ProductService.GetAllProducts();.

5. Inject the Service into ProductDetails.razor:

  • Open Pages/ProductDetails.razor.
  • Remove the line private ProductDataService _productService = new ProductDataService();.
  • Add the @inject directive at the top, specifying the interface:
Pages/ProductDetails.razor (relevant section):
@inject BlazorRoutingExercise.Data.IProductDataService ProductService // Injected via DI
  • In the OnParametersSet method, change _productService.GetProductById(ProductId); to ProductService.GetProductById(ProductId);.

6. Test Your DI:

  • Run the application.
  • Verify that the "Products" list and "Product Details" pages still load correctly. If they do, it means your service is being correctly injected and used!

Part 2: Challenge - Explore Service Lifetimes

1. Experiment with Lifetimes:

  • In Program.cs, change the service registration from AddSingleton to AddScoped:
Program.cs (relevant section):
builder.Services.AddScoped<BlazorRoutingExercise.Data.IProductDataService, BlazorRoutingExercise.Data.ProductDataService>();
  • Run the application. Open two different browser tabs.
  • Question: For this read-only ProductDataService, do you observe any difference between Singleton and Scoped behavior when viewing products in different tabs? (Hint: No, because the data isn't changing. The difference matters more when services hold state that can be modified per user/request).
  • Optional: Change it to AddTransient. Again, for a read-only service, the visual difference is minimal.

2. Challenge (Conceptual):

If you had a ShoppingCartService that stored items for a single user's shopping cart, which lifetime (Singleton, Scoped, or Transient) would be most appropriate? Why?

Self-check answer: Scoped would be most appropriate. Each user (connection/request) would get their own unique shopping cart instance, but that instance would be reused for all component interactions within that same user's session.

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