The quick answer implies that your AspNetCore aplication is stateless. Meaning that all state that is required for your application to function is stored outside (Redis, SQLServer, MongoDB, you name it).
The simplest option is to make sure that the respective IoC container manages the lifetime of all services.
In this case, the container will check whether your class implements IDisposable or IAsyncDisposable and clean up allocated resources at the end of each HTTP Request.
With this approach, you don’t even have to implement IDisposable.
Let’s take a look at the example.
We will have to register a factory that creates an SQLite connection to IServiceCollection. For visibility, our factory will log all created and subsequently disposed instances.
SQLite Connection Factory
Then we will have a simple Repository that references SqLiteConnection and allows us to execute a simple query against it.
Notice that Repository is also registered to our IoC container.
Repository without IDisposable
In our Repository, we are not taking any actions to clean up the SQLiteConnection instance. Finally, we define a controller that executes a call to Repository.
Test Controller with Repository call
FromServices attribute will take the instance of Repository from IServiceProvider. However, you can use any other way, including passing Repository to the constructor.
Let’s run this example and observe console output.
Logs for Test Controller request
Logs clearly show that Connection was created as request started and then disposed as request come to finish.
We can conclude that solution above is more than enough to start with. The main benefit is that services lifetimes are decoupled from each other, so we can easily play with different DI features without changing our implementation. For example, if many different Repositories are sequentially working with the same database inside a single HTTP Request — there is no need to have SqlConnection transient. We can easily make it Scoped and reduce Connection instances produced during the request to 1.
Okay, maybe your case is not that simple, and you really have to implement Dispose Pattern yourself. First, we have to learn more about managed/unmanaged resources.
But what is an unmanaged resource? Is there a way to identify it?
As any unmanaged resource lives outside of CLR (by definition), most likely, it will be represented by the corresponding IntPtr instance. This IntPtr will be either an OS handle representing some resource or a pointer to unmanaged memory.
So, if you are questioning yourself about which resources are in fact unmanaged, consider these two questions:
Do you or one of the referenced packages directly use OS methods to work with hardware or OS? (COM port, sound card, graphics card, P/Invoke, etc.)
Did some of these methods return IntPrt?
If true, you would have to wrap the IntPtr in a class that correctly implements Dispose Pattern with Finalizer. Also, you would have to refer to the documentation and learn about proper resources cleanup for your particular case. We will not dive into unmanaged resources in this article, as most AspNetCore APIs are not using them directly. Dotnet ‘wraps’ most of the unmanaged resources needed for the AspNetCore application to function in safe, managed classes. SafeHandles hierarchy is a good example.
In my experience, I’ve been faced directly with unmanaged resources only once. If you ever write a bot for Microsoft Teams that had to connect to calls and retrieve audio streams, you will receive the audio in form of unmanaged arrays produced by the sound card.
If your answer is no for both questions — congratulations, you are living a happy life with managed resources!
The implementation you’ll most likely need
To select a proper version of DisposePattern, we must define whether our class is planned to be inherited.
Let’s start overview from the sealed class. We will use the same SQLiteConnection and TestController. However, this time our Repository will implement an IDisposable interface.
As you can see, this version of DisposePattern is as simple as a call to Dispose for all IDisposable fields.
Logs for Test Controller and Disposable Repository
This example clearly shows us the redundancy of DisposePattern together with IoC. Everything seems to work perfectly. Except, SqLiteConnection was disposed twice: by Repository and by IoC container. This can result in problems when the Repository’s lifetime is different from Connection’s. But in our case, it’s not a big deal.
If you were not satisfied with Quick Answer, you are probably not using (or cannot use) any IoC container to manage lifetime, so you won’t face any double-dispose behavior.
Dispose Pattern and Inheritance
Now let’s take a look at the implementation of a Repository that can be inherited. In this case, we have no idea who will be responsible for derived class implementation, so we need to consider the possibility of unmanaged resources usage down the road.
DisposePattern for non-sealed classes
Our Dispose method contains a boolean parameter that indicates that an attempt to clean up resources was made from the Dispose method, not the Finalizer.
The idea is that managed resources need to be disposed only when we are calling Dispose method. On the other hand, unmanaged resources should always be disposed.
GC.SuppressFinalize(this) indicates that this the object doesn’t require the Finalizer to be called. This is necessary to ensure that possible Finalizers of subclasses are skipped if the instance is already disposed.
We’ve overviewed several scenarios of working with managed resources. I prefer to start with a Quick solution and increase complexity only in places that require that. Modern IoC frameworks, such as Grace supports lots of different lifestyles for services. So the need for manual IDisposable implementation slowly fades out.
Source: Medium - Oleksandr Redka
The Tech Platform