How to Dependency Inject in Blazor Applications

What is Dependency Injection, you ask?

Let's get into one of the most powerful design patterns - Dependency Injection (DI), where dependencies (like services) are provided to an object rather than the object creating them. This approach makes our code more modular, testable, and decoupled.

Before we dive deeper, let’s quickly highlight why you should fall in love with DI:

  • Loose coupling: With DI, your components won’t be tightly bound to each other. You can swap out services easily.
  • Better testability: It’s way easier to mock services for testing when they’re injected.
  • Scalable architecture: Want to scale your app? DI makes this a breeze because you can manage the lifecycle of services effectively.

Types of Dependency Injection in Blazor

Blazor gives you three types of dependency lifetimes to choose from. Each one has its use case, depending on how often you want your services created and disposed of.

Lifetime When Created When Disposed Example
Transient Every time a service is requested (new instance each time) Immediately after the service is no longer needed. (when the request is complete) A service that generates a unique discount code every time a user requests it.
Scoped Once per user session (circuit in Blazor Server, same for each request in Blazor WebAssembly) When the user disconnects (Blazor Server) or when the app refreshes (Blazor WebAssembly) A service storing user session data (e.g., username) during their visit.
Singleton A single instance is created once and shared across the entire app The instance lives for the entire application’s lifetime A global logging service that persists logs throughout the entire app’s usage.

Here are two more services: one is a weather service that showcases a real-time example, and the other is a greeting service that demonstrates how to pass parameters to a service.

Service Architecture Breakdown

Here’s how the architecture of your Blazor project might look with these services:

│── Services/
│   │── TransientService.cs
│   │── ScopedService.cs
│   │── SingletonService.cs
│   │── GreetingService.cs
│   │── WeatherService.cs
│── Interfaces/  (or Abstractions/)
│   │── ITransientService.cs
│   │── IScopedService.cs
│   │── ISingletonService.cs
│   │── IGreetingService.cs
│   │── IWeatherService.cs

Defining Services and Interfaces


1. Transient Service

public interface ITransientService
{
    string GenerateUniqueId();
}

Implementation

public class TransientService : ITransientService
{
    public string GenerateUniqueId()
    {
        return Guid.NewGuid().ToString();
    }
}

2. Scoped Service

public interface IScopedService
{
    int GetRequestCount();
}

Implementation

public class ScopedService : IScopedService
{
    private int _requestCount = 0;
    
    public int GetRequestCount()
    {
        return ++_requestCount;
    }
}

3. Singleton Service

public interface ISingletonService
{
    DateTime GetApplicationStartTime();
}

Implementation

public class SingletonService : ISingletonService
{
    private readonly DateTime _startTime;
    
    public SingletonService()
    {
        _startTime = DateTime.Now;
    }
    
    public DateTime GetApplicationStartTime()
    {
        return _startTime;
    }
}

Registering Services in Blazor

Services are registered in the Program.cs file. Below is how different DI lifetimes are registered:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();

Injecting Dependencies in Blazor Components

Let’s add two more components to display the service outputs on the UI.

│── Services/
│   │── ...
│── Interfaces/
│   │── ....
│── Pages/
│   │── InjectAttributeDemo.razor
│   │── InjectDirectiveDemo.razor

Now this is where fun begins, since I need to showcase the use of multiple ways to inject dependencies.

1. InjectDirectiveDemo.razor

Using @inject Directive (Recommended for .razor Files)

  • The simplest approach is best for use in .razor files.
  • You inject services directly into the component markup using @inject.
@page "/inject-directive-demo"
@inject ITransientService TransientService
@inject IScopedService ScopedService
@inject ISingletonService SingletonService

<h3>Inject Directive Example</h3>
<p>Unique ID (Transient): @TransientService.GenerateUniqueId()</p>
<p>Request Count (Scoped): @ScopedService.GetRequestCount()</p>
<p>Application Start Time (Singleton): @SingletonService.GetApplicationStartTime()</p>

 InjectDirectiveDemo.razor

Inject Directive Example

Image 1. Services output via the @inject directive.

2. InjectAttributeDemo

Using [Inject] Attribute (Recommended for Complex Components)

Works in code sections (@code { }) of .razor files or in .razor.cs files.

@page "/inject-attribute-demo"
<h3>Inject Attribute Example</h3>
<p>Unique ID (Transient): @TransientService.GenerateUniqueId()</p>
<p>Request Count (Scoped): @ScopedService.GetRequestCount()</p>
<p>Application Start Time (Singleton): @SingletonService.GetApplicationStartTime()</p>

@code {
    [Inject] private ITransientService TransientService { get; set; }
    [Inject] private IScopedService ScopedService { get; set; }
    [Inject] private ISingletonService SingletonService { get; set; }

    protected override void OnInitialized()
    {
        var id = TransientService.GenerateUniqueId();
        var count = ScopedService.GetRequestCount();
        var time = SingletonService.GetApplicationStartTime();
    }
}

InjectAttributeDemo.razor

Dependency Injection Demo

Image 2. Services output via the @inject attribute.

Real-Time Example. Weather Forecast Service

Now, let’s spice things up with a real-world example! Imagine you want to build a weather app. You can create a Weather Forecast Service to fetch data from an API and inject it into your components. Here is where new files would go.

│── Services/
│   │── WeatherService
│   │── ...
│── Interfaces/
│   │── IWeatherService
│   │── ....
│── Pages/
│   │── Weather.razor
│   │── ....

Step 1. Create the Service

public interface IWeatherService
{
    Task<WeatherForecast[]> GetWeatherForecastsAsync();
}

Step 2. Register the Service

builder.Services.AddScoped<IWeatherService, WeatherService>();

Step 3. Implement Service

public class WeatherService(HttpClient httpClient) : IWeatherService
{
    private readonly HttpClient _httpClient = httpClient;

    public async Task<WeatherForecast[]> GetWeatherForecastsAsync()
    {
        return await _httpClient.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }
}

public class WeatherForecast
{
     public DateOnly Date { get; set; }
     public int TemperatureC { get; set; }
     public string? Summary { get; set; }
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Step 4. Inject the Service into a Blazor Component 

@page "/weather"
@inject IWeatherService WeatherService

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await WeatherService.GetWeatherForecastsAsync();
    }

}
@page "/weather"
@inject IWeatherService WeatherService

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await WeatherService.GetWeatherForecastsAsync();
    }

}

Weather

Image 3. Weather service output

Now, for the last one, we are going to create a greeting service that accepts a parameter.

Step 1. Create a Configurable Service

Let's define an interface and a service that accepts a configuration parameter.

Interface

    public interface IGreetingService
    {
        string GetGreeting(string name);
    }

Implementation

    public class GreetingService(string greetingTemplate) : IGreetingService
    {
        private readonly string _greetingTemplate = greetingTemplate;

        public string GetGreeting(string name)
        {
            return string.Format(_greetingTemplate, name);
        }
    }

The fun part is to know how to pass arguments:

Step 2. Register the Service in the Program.cs

Since ConfigurableService requires a parameter, we register it using a factory function:

builder.Services.AddSingleton<IGreetingService>(sp =>
{
    return new GreetingService("Hello, {0}! Welcome to Blazor.");
});

Step 3. Inject and Use the Service in a Blazor Component

Now, use it in a component:

@page "/greeting"
@inject IGreetingService GreetingService

<h3>Greeting Service Example</h3>

<p>Enter your name:</p>
<input @bind="userName" placeholder="Your Name" />

<p><strong>Greeting:</strong> @greetingMessage</p>

<button @onclick="GenerateGreeting">Generate Greeting</button>

@code {
    private string userName = "";
    private string greetingMessage = "";

    private void GenerateGreeting()
    {
        greetingMessage = GreetingService.GetGreeting(userName);
    }
}

Dependency Injection Demo Gif

Image 4. Greeting service output

Conclusion

Dependency Injection (DI) is a powerful design pattern that enhances modularity and scalability in Blazor applications. By injecting dependencies rather than creating them within components, we achieve loose coupling and better service management.

Blazor supports three DI lifetimes, Transient, Scoped, and Singleton, each serving different use cases based on how often services need to be instantiated. We explored various ways to inject dependencies, such as the @inject directive and [Inject] attribute, making service consumption seamless in both markup and code.

Through real-world examples like a Weather Forecast Service and a Configurable Greeting Service, we demonstrated the practical implementation of DI, showcasing how services can be registered, injected, and used efficiently.

Up Next
    Ebook Download
    View all
    Learn
    View all