Reputation: 13
I am working on Blazor server application and I am blocked / confused about using transient type services inside Blazor server.
I have readed this topic on why I should not implement IDisposable inside transient service.
Transient services which implement IDisposable :
A reference to the service is maintained by the DI container so it can handle service disposal. This prevents garbage collection of the service upon component disposal. The service is kept in memory till the DI container itself can be garbage collected.
My question is:
Is it okey if I implement IDisposable inside my transient service, and call .Dispose()
inside my .razor
when I dont need it anymore?
Lets create example:
Example service registed as transient inside DI:
namespace testExample
{
public class MyService : IDisposable
{
public void DoSomething()
{
}
public void Dispose()
{
}
}
}
Example component:
@inject MyService _service
@implements IDisposable
<h3>MyComponent</h3>
@code {
protected override void OnInitialized()
{
_service.DoSomething();
}
public void Dispose()
{
_service.Dispose();
}
}
I tried this example up and by looking at memory and tracking how many instances are created, it seems that example is valid and can be used, but lack of my knowledge is still making me unsecured about this approach.
Upvotes: 1
Views: 655
Reputation: 30330
When you request a Transient service it's provided by the Scoped Service container. Under normal circumstances, that's it. You have the only reference to that object. The service container created it and then dropped it's reference. However. when you create a Transient service that implements IDisposable/IAsyncDisposable
, the service container keeps a reference to it in it's objects to dispose list.
So no matter what strategy you apply, the scoped service container maintains a reference to the service and the GC doesn't remove it until the scoped service container itself is disposed and removed from memory.
There is a solution. Create the instance outside the scoped service container using ActivatorUtilities
and dispose it manually.
First a simple interface and implementation.
public interface ITransientService { }
public class TransientService : ITransientService, IDisposable
{
public void Dispose() { }
}
Register it as a scoped service (you don't need to use it that way).
builder.Services.AddScoped<ITransientService, TransientService>();
Implemented in a component.
@page "/"
@inject IServiceProvider ServiceProvider
@using Microsoft.Extensions.DependencyInjection
@implements IDisposable
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
@code {
private ITransientService? _transientService;
private IDisposable? _disposable;
protected override void OnInitialized()
{
// Get the concrete service defined for the interface in the services container
var service = this.ServiceProvider.GetService<ITransientService>();
// and get the type
var type = service?.GetType();
if (type is not null)
{
// create a concreate instance from the service container
_transientService = ActivatorUtilities.CreateInstance(ServiceProvider, type) as ITransientService;
// assign if disposable
_disposable = _transientService as IDisposable;
}
}
public void Dispose()
{
_disposable?.Dispose();
}
}
Creating a special Scoped service container for your context seems like a good solution. You can dispose it when you've finished with it, and all the Transient services will be disposed properly.
There's a fundamental flaw to that solution. Many Transient services have dependencies on Scoped services. Consider how the Scoped service container obtains it's instance of Scoped services. It creates them: it is a Scoped container. It doesn't get them from the parent container. So if you're service needs say the AuthenticationService
or NavigationManager
, it gets a newly created instance, not the one for the SPA session.
Upvotes: 0