Quantcast
Channel: Andrew Lock | .NET Escapades
Viewing all articles
Browse latest Browse all 743

Four ways to dispose IDisposables in ASP.NET Core

$
0
0

One of the most commonly implemented interfaces in .NET is the IDisposable interface. Classes implement IDisposable when they contain references to unmanaged resources, such as window handles, files or sockets. The garbage collector automatically releases memory for managed (i.e. .NET) objects, but it doesn't know about how to handle the unmanaged resources. Implementing IDisposable provides a hook, so you can properly clean up those resources when your class is disposed.

This post goes over some of the options available to you for disposing services in ASP.NET Core applications, especially when using the built-in dependency injection container.

For the purposes of this post, I'll use the following class that implements IDisposable in the examples. I'm just writing to the console instead of doing any actual cleanup, but it'll serve our purposes for this post.

public class MyDisposable : IDisposable
{
    public MyDisposable()
    {
        Console.WriteLine("+ {0} was created", this.GetType().Name);
    }

    public void Dispose()
    {
        Console.WriteLine("- {0} was disposed!", this.GetType().Name);
    }
}

Now let's look at our options.

The simple case - a using statement

The typical suggested approach when consuming an IDisposable in your code, is with a using block:

using(var myObject = new MyDisposable())
{
    // myObject.DoSomething();
}

Using IDisposables in this way ensures they are disposed correctly, whether or not they throw an exception. You could also use a try-finally block instead if necessary:

MyDisposable myObject;
try
{
    myObject = new MyDisposable();
    // myObject.DoSomething();
}
finally
{
    myObject?.Dispose();
}

You'll often find this pattern when working with files or streams - things that you only need transiently, and are finished with in the same scope. Unfortunately, sometimes this won't suit your situation, and you might need to dispose of the object from somewhere else. Depending on your exact situation, there are a number of other options available to you.

Note: Wherever possible, it's best practice to dispose of objects in the same scope they were created. This will help prevent memory leaks and unexpected file locks in your application, where objects go accidentally undisposed.

Disposing at the end of a request - using RegisterForDispose

When you're working in ASP.NET Core, or any web application, it's very common for your objects to be scoped to a single request. That is, anything you create to handle a request you want to dispose when the request finishes.

There are a number of ways to do this. The most common way is to leverage the DI container which I'll come to in a minute, but sometimes that's not possible, and you need to create the disposable in your own code.

If you are manually creating an instance of an IDisposable, then you can register that disposable with the HttpContext, so that when the request ends, the instance will be disposed automatically. Simply pass the instance to HttpContext.Response.RegisterForDispose:

public class HomeController : Controller
{
    readonly Disposable _disposable;

    public HomeController()
    {
        _disposable = new RegisteredForDispose();
    }

    public IActionResult Index()
    {
        // register the instance so that it is disposed when request ends
        HttpContext.Response.RegisterForDispose(_disposable);
        Console.Writeline("Running index...");
        return View();
    }
}

In this example, I'm creating the Disposable during the constructor of the HomeController, and then registering for its disposal in the action method. This is a little bit contrived, but it shows the mechanism at least.

If you execute this action method, you'll see the following:

$ dotnet run
Hosting environment: Development
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ MyDisposable was created
Running index...
- MyDisposable was disposed!

The HttpContext takes care of disposing our object for us!

Warning: I registered the instance in the action method, instead of the constructor method because HttpContext might be null in the constructor!

RegisterForDispose is useful when you are new-ing up services in your code. But given that Dispose is only required for classes using unmanaged resources, you'll probably find that more often than not, your IDisposable classes are encapsulated in services that are registered with the DI container.

As Mark Rendle pointed out, the Controller itself will also be disposed at the end of the request, so you can use that mechanism to dispose any objects you create.

Automatically disposing services - leveraging the built-in DI container

ASP.NET Core comes with a simple, built-in DI container that you can register your services with as either Transient, Scoped, or Singleton. You can read about it here, so I'll assume you already know how to use it to register your services.

Note, this article only discusses the built-in container - third-party containers might have other rules around automatic disposal of services.

Any service that the built-in container creates in order to fill a dependency, which also implements IDisposable, will be disposed by the container at the appropriate point. So Transient and Scoped instances will be disposed at the end of the request (or more accurately, at the end of a scope), and Singleton services will be disposed when the application is torn down and the ServiceProvider itself is disposed.

To clarify, that means the provider will dispose any service you register with it, as long as you don't provide a specific instance. For example, I'll create a number of disposable classes:

public class TransientCreatedByContainer: MyDisposable { }
public class ScopedCreatedByFactory : MyDisposable { }
public class SingletonCreatedByContainer: MyDisposable {}
public class SingletonAddedManually: MyDisposable {}

And register each of them in a different way in Startup.ConfigureServices. I am registering

  • TransientCreatedByContainer as a transient
  • ScopedCreatedByFactory as scoped, using a lambda function as a factory
  • SingletonCreatedByContainer as a singleton
  • SingletonAddedManually as a singleton by passing in a specific instance of the object.
public void ConfigureServices(IServiceCollection services)
{
    // other services

    // these will be disposed
    services.AddTransient<TransientCreatedByContainer>();
    services.AddScoped(ctx => new ScopedCreatedByFactory());
    services.AddSingleton<SingletonCreatedByContainer>();

    // this one won't be disposed
    services.AddSingleton(new SingletonAddedManually());
}

Finally, I'll inject an instance of each into the HomeController, so the DI container will create / inject instances as necessary:

public class HomeController : Controller
{
    public HomeController(
        TransientCreatedByContainer transient,
        ScopedCreatedByFactory scoped,
        SingletonCreatedByContainer createdByContainer,
        SingletonAddedManually manually)
    { }

    public IActionResult Index()
    {
        return View();
    }
}

When I run the application, hit the home page, and then stop the application, I get the following output:

$ dotnet run
+ SingletonAddedManually was created
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ TransientCreatedByContainer was created
+ ScopedCreatedByFactory was created
+ SingletonCreatedByContainer was created
- TransientCreatedByContainer was disposed!
- ScopedCreatedByFactory was disposed!
Application is shutting down...
- SingletonCreatedByContainer was disposed!

There's a few things to note here:

  • SingletonAddedManually was created before the web host had finished being set up, so it writes to the console before logging starts
  • SingletonCreatedByContainer is disposed after we have started shutting down the server
  • SingletonAddedManually is never disposed as it was provided a specific instance!

Note the behaviour whereby only objects created by the DI container are disposed applies to ASP.NET Core 1.1 and above. In ASP.NET Core 1.0, all objects registered with the container are disposed.

Letting the container handle your IDisposables for you is obviously convenient, especially as you are probably registering your services with it anyway! The only obvious hole here is if you need to dispose an object that you create yourself. As I said originally, if possible, you should favour a using statement, but that's not always possible. Luckily, ASP.NET Core provides hooks into the application lifetime, so you can do some clean up when the application is shutting down.

Disposing when the application ends - hooking into IApplicationLifetime events

ASP.NET Core exposes an interface called IApplicationLifetime that can be used to execute code when an application is starting up or shutting down:

public interface IApplicationLifetime
{
    CancellationToken ApplicationStarted { get; }
    CancellationToken ApplicationStopping { get; }
    CancellationToken ApplicationStopped { get; }
    void StopApplication();
}

You can inject this into your Startup class (or elsewhere) and register to the events you need. Extending the previous example, we can inject both the IApplicationLifetime and our singleton SingletonAddedManually instance into the Configure method of Startup.cs:

public void Configure(
    IApplicationBuilder app, 
    IApplicationLifetime applicationLifetime,
    SingletonAddedManually toDispose)
{
    applicationLifetime.ApplicationStopping.Register(OnShutdown, toDispose);
    
    // configure middleware etc
}

private void OnShutdown(object toDispose)
{
    ((IDisposable)toDispose).Dispose();
}

I've created a simple helper method that takes the state passed in (the SingletonAddedManually instance), casts it to an IDisposable, and disposes it. This helper method is registered with the CancellationToken called ApplicationStopping, which is fired when closing down the application.

If we run the application again, with this additional registration, you can see that the SingletonAddedManually instance is now disposed, just after the application shutting down trigger.

$ dotnet run
+ SingletonAddedManually was created
Content root path: C:\Users\Sock\Repos\RegisterForDispose
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
+ TransientCreatedByContainer was created
+ ScopedCreatedByFactory was created
+ SingletonCreatedByContainer was created
- TransientCreatedByContainer was disposed!
- ScopedCreatedByFactory was disposed!
Application is shutting down...
- SingletonAddedManually was disposed!
- SingletonCreatedByContainer was disposed!

Summary

So there you have it, four different ways to dispose of your IDisposable objects. Wherever possible, you should either use the using statement, or let the DI container handle disposing objects for you. For cases where that's not possible, ASP.NET Core provides two mechanisms you can hook in to: RegisterForDispose and IApplicationLifetime.

Oh, and apparently I missed one final way:


Viewing all articles
Browse latest Browse all 743

Trending Articles