Guaranteeing Disposal Of UserControls In ASP.NET
Posted by Davy Brion on April 9th, 2009
As i mentioned recently, one of our applications suffered from a memory leak because one of ASP.NET’s UserControls (in this case, the Repeater) created instances of one of our own UserControl type without disposing them afterward. In most cases, this isn’t really a big issue, but if your UserControl really requires explicit Disposal this can obviously be a pretty big problem.
In order to prevent this situation from ever happening again, i came up with an approach which guarantees that all instances of UserControls that require explicit disposal are indeed properly disposed at the end of the request in which they were created. I don’t really like this approach as i consider it a hack. But then again, when ASP.NET controls fail to dispose the controls they create in some occasions, all bets are off.
And so the Disposer class was born:
public static class Disposer
{
private const string DisposalEnabledKey = "_disposeTrackedObjects";
private const string DisposableObjectsKey = "_disposableObjects";
public static void EnableDisposalOfTrackedObjectsForCurrentRequest()
{
HttpContext.Current.Items[DisposalEnabledKey] = true;
HttpContext.Current.Items[DisposableObjectsKey] = new List<WeakReference>();
}
public static void RegisterForGuaranteedDisposal(IDisposable disposable)
{
if (GuaranteedDisposalIsEnabled())
{
var disposables = GetTrackedDisposables();
disposables.Add(new WeakReference(disposable));
}
}
public static void DisposeTrackedReferences()
{
var disposables = GetTrackedDisposables();
foreach (var reference in disposables)
{
if (reference.IsAlive)
{
var disposable = reference.Target as IDisposable;
if (disposable != null) disposable.Dispose();
}
}
}
private static bool GuaranteedDisposalIsEnabled()
{
var value = HttpContext.Current.Items[DisposalEnabledKey];
if (value == null)
{
return false;
}
return (bool)value;
}
private static List<WeakReference> GetTrackedDisposables()
{
return HttpContext.Current.Items[DisposableObjectsKey] as List<WeakReference> ?? new List<WeakReference>();
}
}
Ugly stuff, right? It gets worse.
In the constructor of the UserControl(s) that really need(s) to be disposed, add the following line:
Disposer.RegisterForGuaranteedDisposal(this);
Then, we have our own custom HttpModule to complete this little hack-fest:
public class OurKickAssHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += context_BeginRequest;
context.EndRequest += context_EndRequest;
}
private void context_BeginRequest(object sender, EventArgs e)
{
Disposer.EnableDisposalOfTrackedObjectsForCurrentRequest();
}
private void context_EndRequest(object sender, EventArgs e)
{
Disposer.DisposeTrackedReferences();
}
// not really needed here but it's required by IHttpModule
public void Dispose() {}
}
All in all, pretty horrible stuff if you ask me. But at least we’re sure now that all instances of the UserControl are always properly disposed.
April 9th, 2009 at 7:15 pm
[...] Davy explains how he solved our problems in his blog post about the topic: Guaranteeing Disposal Of UserControls In ASP.NET [...]