Course contentsShow
← C#
Lesson 1405 of 1,90812. Modern Frontend with Razor and BlazorFree lesson

Advanced Dependency Inversion

Use abstractions to allow components to interact with services without concrete coupling.

In this lesson, you will learn how to decouple your Razor components from infrastructure details by programmatically injecting abstractions rather than concrete implementations.

The Problem of Concrete Coupling

In many Blazor applications, developers often fall into the trap of injecting a concrete service directly into a component. For instance, if your UserDashboard.razor directly depends on SqlUserRepository, you have tightly coupled your UI to a specific database implementation. If you decide to switch to a Web API or a mock service for testing, you are forced to modify the component code itself. This violates the Dependency Inversion Principle, which states that high-level modules should depend on abstractions, not concretions.

Designing for Abstraction

To achieve true decoupling, define an interface that represents the intent of your service, not the mechanism of it.

Consider an interface IUserService that defines methods like GetUserNameAsync(). Your Blazor component should only know about this interface:

@inject IUserService UserService

<p>Welcome, @userName</p>

@code {
    private string userName;
    protected override async Task OnInitializedAsync() 
        => userName = await UserService.GetUserNameAsync();
}

Decoupling at the Service Container

The magic happens in your Program.cs file. By registering the interface against a specific implementation, you control the "inversion" from the outside. You can swap the implementation without touching a single line of your component logic:

// Easily switch between implementations here
builder.Services.AddScoped<IUserService, SqlUserRepository>();
// builder.Services.AddScoped<IUserService, MockUserRepository>(); // Simple toggle

By coding against the IUserService abstraction, your component becomes "infrastructure agnostic." This allows you to scale your application’s architecture, improve testability by injecting mock services, and ensure that your UI logic remains clean and focused on presentation rather than data retrieval details.

Key Takeaway: By injecting abstractions instead of concrete types, you insulate your Blazor components from implementation changes, making your codebase significantly more modular and easier to refactor.