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.