Today, let’s have a look at dealing with disposable objects in ASP.NET Core. Imagine you have an object that you’d like to reuse throughout the duration of the HTTP request, but at the end, it must clean up some of its open resources or handles. This is a common requirement for i.e. file writers, DB connections, other unmanaged resources or external processes - to mention just a few.
With ASP.NET Core DI π
Typically, if you rely on the ASP.NET Core Dependency Injection capabilities, you can achieve that by registering the dependency as “Scoped”, and then just resolve/inject it wherever needed.
public void ConfigureServices(IServiceCollection services)
{
// now a single instance of RestaurantService will be used
// for every HTTP request
services.AddScoped<IRestaurantService, RestaurantService>();
// ...
}
This also means that if the service is disposable, that is, needs to clean up some of the resources it opened, that will also happen for you automatically.
With RegisterForDispose π
A different way of handling this requirement, is to use an ASP.NET Core feature that allows you to explicitly register resources for disposal at the end of the HTTP request/response pipeline. This is done via HttpResponse object, which exposes a RegisterForDispose method. It can be very useful when you are creating disposable objects on the fly, rather than wrapping them into services or factories that are managed by the DI container.
You can pass any IDisposable to the RegisterForDispose method, and ASP.NET Core would guarantee it is going to be disposed.
It is worth mentioning, that this is a similar functionality to the one that existed in the past in ASP.NET Web API.
The example of using RegisterForDispose is shown below, in this case, in a middleware component.
app.Use(async (context, next) =>
{
var writer = File.CreateText(Path.GetTempFileName());
context.Response.RegisterForDispose(writer);
context.Items["filewriter"] = writer;
await writer.WriteLineAsync("some important information");
await writer.FlushAsync();
await next();
});
The snippet above, creates a temporary file inside a middleware component, and writes something to it. The writer is then not disposed straight away (notice the lack of using block), but rather registered for disposal and saved into the HttpContext.Items so that it can be used in other places of the execution pipeline - i.e. inside an MVC filter, MVC controller or other middleware components.
For example, a controller can now unpackage the disposable writer and continue using it:
public class FooController : ControllerBase
{
[HttpGet("foo")]
public async Task<string> Get()
{
var writer = HttpContext.Items["filewriter"] as StreamWriter;
if (writer != null)
{
await writer.WriteLineAsync("more important information");
await writer.FlushAsync();
}
return "hello";
}
}
We could even simplify the code a bit, and add an extension method that would chain the creation of the IDisposable and its registration for disposal.
public static class HttpContextExtensions
{
public static T RegisterForDispose<T>(this T disposable, HttpContext context) where T : IDisposable
{
context.Response.RegisterForDispose(disposable);
return disposable;
}
}
This way, we can now write code the following way:
var writer = File.CreateText(filePath).RegisterForDispose(context);
Which, in my view, is a little more expressive of the intent - clearly stating that the object created here, will be disposed.
This technique (RegisterForDispose) is used internally by ASP.NET Core for a number of things - for example, to support buffered file streams on file uploads, or to ensure the WindowsIdentity gets properly disposed in case of IIS integration.