Guaranteeing Disposal Of UserControls In ASP.NET

3 commentsWritten on April 9th, 2009 by
Categories: Software Development

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.

  • Pingback: Getting Your ASP.NET UserControl Disposed … At All Times | Kristof Rennen's Blog

  • http://dotnetzip.codeplex.com Dino Chiesa

    Davy, you sure you need all that? why not just register an Application_EndRequest() method in global.asax.cs, iterate through the HttpContext.Current.Items dictionary, and call Dispose as necessary (if the object is IDisposable). If you make the assumption that anything IDisposable needs to be Disposed when the request context goes out of scope, then the solution is much simpler than what you provided.
    You could also constrain the situation byb assuming “anything stored in request context that’s a DataContext should be Disposed.”

    Also, see this post from a while back from Keith Craig.
    http://blogs.vertigo.com/personal/keithc/Blog/archive/2007/06/28/linq-to-sql-and-the-quote-request-scoped-datacontext-quote-pattern.aspx

  • http://davybrion.com Davy Brion

    @Dino

    that would work as well, though you’re required to add each instance you want to see disposed to the HttpContext.Current.Items dictionary with a unique key to prevent overwriting other instances

    in a way it would indeed be simpler, yet it’s also more error prone i think