Scoped Dependency Injection in Server Side Blazor

Miya
4 min readSep 15, 2019

--

One of the more interesting aspects of the Blazor .NET framework is the ability to run Blazor both on the client through WebAssembly, and on the server with Blazor Server Side. And, for the most part, the programming model for the two is exactly the same. However, when it comes to dependency injection, there are some subtle differences that may cause issues in the server side model you may not be aware of if you are used to developing a SPA or even an ASP.NET MVC application.

Let’s jump into some code. First, let’s stub out a basic service that will act as our “data service” for our component.

public interface IDataService : IDisposable
{
Task<string> GetValueAsync();
Task<bool> SaveValueAsync(string value);
}
public class DataService : IDataService
{
public DataService()
=> Console.WriteLine("DataService constructed!");
// not a proper IDisposable implementation,
// but it'll do for demo purposes
public void Dispose()
{
Console.WriteLine("DataService disposed!");
}
public Task<string> GetValueAsync()
{
Console.WriteLine("TODO: Get value from database...");
return Task.FromResult("Hello World!");
}
public Task<bool> SaveValueAsync(string value)
{
Console.WriteLine($"TODO: Save value '{value}' to database...");
return Task.FromResult(true);
}
}

Since we are running in Blazor Server Side mode, you could imagine that this service would inject perhaps a DbContext instance into itself, whereas in the client side model, you would be making HTTP requests to a backend REST service. As we’ll see later, that difference is significant. But for now, let’s register our service as a transient service:

services.AddTransient<IDataService, DataService>();

And inject it into a simple component:

@page "/edit"
@inject IDataService MyDataService
<h1>Dependency Injection!</h1><input @bind-value="Value" /><button @onclick="Save">Save</button>@code {
string Value { get; set; } = "";
protected override async Task OnInitializedAsync()
{
Value = await MyDataService.GetValueAsync();
}
void Save()
{
MyDataService.SaveValueAsync(Value);
}
}

As you can see, pretty simple. If we run this, and navigate to the /edit page, navigate away and then back, then click the Save button, you’ll see this in the console window:

DataService constructed!
TODO: Get value from database...
DataService constructed!
TODO: Get value from database...
TODO: Save value 'Hello World!' to database...

So what have we noticed here? First, we see that although our service implemented IDisposable, its Dispose method is never called. That could become important if our service uses any shared resources that need to be freed, for example. Second, we see that the service is constructed every time we navigate to the component. However, while on the same page, between the time we call GetValueAsync and SetValueAsync, we are using the same instance.

Why is that last observation important? Well, let’s compare this to a traditional web app, or to a Blazor Client Side application. In this case, our service would be making one HTTP request in the GetValueAsync function and another HTTP request in the SaveValueAsync function. On the server side, assuming you’re using ASP.NET Core, the controller that implements those two requests would have its dependencies scoped to that request. A new instance of the controller, and any of its transient dependencies (such as our IDataService instance, and the DbContext it depends on), would be created for both requests.

However, in Blazor, our transient IDataService is scoped to the lifetime of the Blazor component. So, it shares the same instance between the GetValueAsync call and the SaveValueAsync call. Furthermore, if the Save button doesn’t automatically navigate you away from the page, further clicks to the Save button would also be using the same instance of IDataService.

This isn’t typically a problem in client-side Blazor, since you’re making stateless HTTP calls. However, this could become an issue in Server Side Blazor, because often the IDataService implementation wraps a transient DbContext instance as a dependency. That means all the entity tracking information is persisted between “requests” unless you manually handle and clean up entity tracking yourself. What we want instead is to have our IDataService injected on demand, and a new instance created and immediately disposed after use, for both the GetValueAsync call and the SaveValueAsync call. This means the lifetime of the transient IDataService needs to be less than the lifetime of the component. For that we need to create a new dependency injection scope.

In Blazor, there is a helper base class OwningComponentBase, which has a ScopedServices property of type IServiceProvider. You would use this as a base class for the component, then you can call ScopedServices.GetService to get a new transient service constructed on demand. These services are scoped to the lifetime of the component, and are disposed of when the component is cleaned up. However, every time you call GetService, the service instance is tracked until the component goes out of scope, which could become a memory leak for long-lived components, such as those in the global header or navigation components. What we need is to create a new dependency injection scope that only lives as long as the “Save” or “Get” operation.

To do this, we’ll need to inject a built-in service of type IServiceScopeFactory. This service allows us to create new dependency injection scopes. Let’s modify our component from before to use IServiceScopeFactory:

@page "/edit"
@using Microsoft.Extensions.DependencyInjection
@inject IServiceScopeFactory ScopeFactory
<h1>Dependency Injection!</h1><input @bind-value="Value" /><button @onclick="Save">Save</button>@code {
string Value { get; set; } = "";
protected override async Task OnInitializedAsync()
{
using(var serviceScope = ScopeFactory.CreateScope())
{
var myService = serviceScope.ServiceProvider.GetService<IDataService>();
Value = await myService.GetValueAsync();
}
}
void Save()
{
using(var serviceScope = ScopeFactory.CreateScope())
{
var myService = serviceScope.ServiceProvider.GetService<IDataService>();
myService.SaveValueAsync(Value);
}
}
}

Note: We’re not explicitly calling Dispose on the IDataService instance (although we could, since IDisposable.Dispose is meant to be idempotent), because logically the service is owned by the dependency injection scope, which calls Dispose for us.

Now let’s try it out. After running the project, navigating to the component page, navigating away, then back, clicking save a few times, and so on, we’ll see something similar to this in the console window:

DataService constructed!
TODO: Get value from database...
DataService disposed!
DataService constructed!
TODO: Save value 'Hello World!' to database...
DataService disposed!
DataService constructed!
TODO: Save value 'Hello World!' to database...
DataService disposed!
DataService constructed!
TODO: Get value from database...
DataService disposed!

Now, we see that the transient IDataService is constructed fresh for each call to LoadValueAsync and SaveValueAsync, and is immediately disposed.

Hope you found this useful!

--

--

Miya

VR, C#, .NET, JS, TypeScript, and anything else that interests me