Dependency Injection (DI) - Centralized Service Management

Dependency Injection (DI) - Centralized Service Management

Dependency Injection (DI) - Centralized Service Management

This demo will illustrate how Dependency Injection helps manage services and share data (or logic) across different components without tightly coupling them.

Concept: Imagine a house where different rooms (components) need access to a shared utility (a service), like a "weather report" machine. Instead of each room buying its own machine, a central "utility provider" (DI container) provides the same machine (or a new one, depending on configuration) when a room requests it.

Project Type: Blazor Server App (or WebAssembly, DI works similarly in both).

Step 1: Create the Blazor Server Project

  • Open Visual Studio.
  • Select "Create a new project".
  • Choose "Blazor Server App" and click "Next".
  • Name the project DISimpleDemo.
  • Click "Create".

Step 2: Server-Side: Define and Implement a Service

We'll create a simple service that holds a shared message.

2.1. Create Services Folder and IMessageService.cs (Interface):

In your DISimpleDemo project, create a new folder called Services. Inside this folder, add a new interface file named IMessageService.cs.

DISimpleDemo/Services/IMessageService.cs:

namespace DISimpleDemo.Services
{
    // Analogy: This is the contract for what a 'message provider' should do.
    // It says "you must be able to GetMessage() and SetMessage()".
    public interface IMessageService
    {
        string GetMessage();
        void SetMessage(string message);
    }
}

2.2. Create MessageService.cs (Implementation):

Now, create a class that implements the IMessageService interface.

DISimpleDemo/Services/MessageService.cs:

namespace DISimpleDemo.Services
{
    // Analogy: This is the actual 'message provider' machine.
    // It keeps track of the current message.
    public class MessageService : IMessageService
    {
        private string _currentMessage = "Default Shared Message";

        public string GetMessage()
        {
            return _currentMessage;
        }

        public void SetMessage(string message)
        {
            _currentMessage = message;
        }
    }
}

2.3. Register the Service in Program.cs:

This is where we tell Blazor's Dependency Injection container how to provide instances of our IMessageService.

Open Program.cs in the root of your DISimpleDemo project.

DISimpleDemo/Program.cs (addition highlighted):

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using DISimpleDemo.Data;
using DISimpleDemo.Services; // Add this using statement

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>(); // Default Blazor service

// Register our MessageService for Dependency Injection.
// Analogy: We're telling the central utility provider:
// "When someone asks for an 'IMessageService', give them a 'MessageService' instance."
// AddSingleton: Means there will be only ONE instance of MessageService for the entire app.
builder.Services.AddSingleton<IMessageService, MessageService>();

// Other common lifetimes:
// builder.Services.AddScoped(); // One instance per client connection/request
// builder.Services.AddTransient(); // New instance every time it's requested

var app = builder.Build();

// ... (rest of Program.cs remains unchanged) ...

Step 3: Client-Side: Consume the Service in Blazor Components

Now, we'll create two Blazor components that both use the IMessageService to display and modify the shared message.

3.1. Create Pages/MessageDisplay.razor:

This component will display the current message from the service.

DISimpleDemo/Pages/MessageDisplay.razor:

@page "/message-display"
@inject DISimpleDemo.Services.IMessageService MessageService // Inject the service

<h3>Message Display</h3>

<p>The current shared message is: <strong>@MessageService.GetMessage()</strong></p>

<p>This message is read directly from the centrally managed service.</p>

3.2. Create Pages/MessageEditor.razor:

This component will allow the user to change the message in the service.

DISimpleDemo/Pages/MessageEditor.razor:

@page "/message-editor"
@inject DISimpleDemo.Services.IMessageService MessageService // Inject the service

<h3>Message Editor</h3>

<p>Change the shared message here. This will affect all components that read from the service.</p>

<div class="form-group">
    <label for="newMessage">New Message:</label>
    <input id="newMessage" class="form-control" @bind="_newMessage" />
</div>
<button class="btn btn-primary mt-2" @onclick="UpdateMessage">Update Shared Message</button>

@code {
    private string _newMessage = "";

    protected override void OnInitialized()
    {
        // Get the current message from the service when the component initializes
        _newMessage = MessageService.GetMessage();
    }

    private void UpdateMessage()
    {
        MessageService.SetMessage(_newMessage);
        // We need to notify Blazor to re-render other components if they're not explicitly
        // refreshing. For this simple demo, navigating back or refreshing MessageDisplay
        // will show the change. In more complex apps, use StateHasChanged or events.
    }
}

3.3. Update NavMenu (Shared/NavMenu.razor):

Add links to your new pages.

Shared/NavMenu.razor:

@* ... existing code ... *@
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="message-display">
                    <span class="oi oi-eye" aria-hidden="true"></span> Message Display
                </NavLink>
            </li>
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="message-editor">
                    <span class="oi oi-pencil" aria-hidden="true"></span> Message Editor
                </NavLink>
            </li>
@* ... existing code ... *@

Step 4: Run the Demo and Observe

  • Set DISimpleDemo as the startup project.
  • Run the application (F5 or Ctrl+F5).
  • Navigate to the "Message Display" page. You'll see "Default Shared Message".
  • Navigate to the "Message Editor" page. Type a new message (e.g., "Hello from Blazor DI!") and click "Update Shared Message".
  • Now, go back to the "Message Display" page. You'll see the updated message.
  • Crucially: Open a second browser tab and navigate to the "Message Display" page. You will see the same updated message because the MessageService is a Singleton, meaning all users share that single instance.

Explanation of Concepts:

  • Service Definition (IMessageService): Defines what the service does, not how it does it. This promotes loose coupling. Components depend on the interface, not the concrete implementation.
  • Service Implementation (MessageService): Provides the actual logic and data storage for the service.
  • Service Registration (builder.Services.AddSingleton<IMessageService, MessageService>(); in Program.cs): This is the heart of DI. It tells the framework: "Whenever a component needs an IMessageService, create one MessageService instance and reuse it everywhere (Singleton lifetime)."
  • Service Consumption (@inject IMessageService MessageService in .razor files): Components simply declare that they need an IMessageService. Blazor's DI container automatically provides the registered instance without the component needing to know how it was created or where it comes from.

This tutorial guide effectively shows how DI allows you to centralize application logic and data, making it reusable, testable, and maintainable.

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