100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Dependency Injection in Blazor

Learn how Blazor's built-in DI container registers and resolves services, and how lifetimes affect component behavior.

State & ServicesIntermediate9 min readJul 10, 2026
Analogies

What Is Dependency Injection in Blazor?

Dependency Injection (DI) in Blazor is the mechanism by which components request the services they depend on—like an HttpClient, a state container, or a repository—from a central container rather than constructing them directly. Blazor's built-in container implements Microsoft.Extensions.DependencyInjection, the same abstraction ASP.NET Core uses, so registrations added in Program.cs via services.AddScoped, AddSingleton, or AddTransient become resolvable inside any component through the @inject directive or the [Inject] attribute.

🏏

Cricket analogy: Like a captain not manufacturing bats himself but requesting one from the team's kit manager before Virat Kohli walks out to bat, a Blazor component doesn't build its own HttpClient—it asks the DI container to hand one over.

Service Lifetimes: Singleton, Scoped, and Transient

A service registered with AddSingleton is created once for the entire application process and that same instance is shared across every user's circuit or browser tab. This makes singletons ideal for stateless, thread-safe utilities like a caching layer or configuration reader, but dangerous for anything holding per-user state, because Blazor Server hosts many circuits inside one process, and unintentionally shared mutable state can leak data between unrelated users.

🏏

Cricket analogy: The single scoreboard operator at Eden Gardens tracks every match that day for all spectators—one shared resource, not one per fan—just as a singleton service is one instance shared across every user's Blazor circuit.

Scoped services are created once per client connection—once per circuit in Blazor Server, or effectively once per app instance in WebAssembly since there's no server-side scope—making them the right choice for a DbContext or a per-user shopping cart that must persist across component navigation but not leak to other users. Transient services, by contrast, are instantiated fresh every single time they're requested, suiting lightweight, stateless helpers like a GUID generator or a validation formatter.

🏏

Cricket analogy: A player is assigned one specific bat for the entire IPL season (scoped) versus grabbing a fresh throwdown ball from the bucket for every single practice delivery (transient), the same split between DbContext and a formatter.

Registering Services in Program.cs

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<ShoppingCartService>();
builder.Services.AddSingleton<ProductCatalogCache>();
builder.Services.AddTransient<PriceFormatter>();
builder.Services.AddHttpClient<ProductApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.shop.example/");
});

var app = builder.Build();

Injecting Services into Components

Inside a .razor component, the @inject directive is compiler sugar that generates a property decorated with [Inject], resolving the named service from the container when the component is instantiated. Because injected properties are set after the constructor runs, any logic that depends on an injected service, such as fetching data from an injected HttpClient, belongs in OnInitializedAsync rather than in the component's field initializers, which execute too early to see the resolved value.

🏏

Cricket analogy: A player only receives their kit bag from the team manager after boarding the team bus, not before—so any prep work needing that gear must wait, just as OnInitializedAsync must wait for @inject to resolve first.

csharp
@page "/cart"
@inject ShoppingCartService CartService
@inject PriceFormatter Formatter

<h3>Your Cart</h3>
<ul>
    @foreach (var item in cartItems)
    {
        <li>@item.Name - @Formatter.Format(item.Price)</li>
    }
</ul>

@code {
    private List<CartItem> cartItems = new();

    protected override async Task OnInitializedAsync()
    {
        cartItems = await CartService.GetItemsAsync();
    }
}

The @inject directive can be used multiple times in a single component to pull in as many services as needed, and each becomes a distinct property visible to that component's markup and @code block.

Registering a Scoped service and then injecting it into a Singleton creates a captive dependency: the Singleton holds onto that first-resolved Scoped instance for the lifetime of the app process, silently sharing what you intended to be per-user state across every circuit.

  • DI in Blazor uses Microsoft.Extensions.DependencyInjection, same as ASP.NET Core
  • Three lifetimes: Singleton (one instance per app), Scoped (one per circuit/session), Transient (new every request)
  • @inject is compiler sugar for [Inject] property injection
  • Blazor Server circuits share a process, so singleton state leaks across all users if not designed carefully
  • Blazor WebAssembly has only one user per app instance, so Scoped behaves like Singleton there
  • Avoid capturing scoped services inside singleton services (captive dependency) — it silently pins scoped state for the app's lifetime
  • Injected properties are only guaranteed set after the constructor, so use OnInitializedAsync for dependent logic

Practice what you learned

Was this page helpful?

Topics covered

#BlazorStudyNotes#MicrosoftTechnologies#DependencyInjectionInBlazor#Dependency#Injection#Blazor#Service#StudyNotes#SkillVeris#ExamPrep