Tuesday, February 19, 2008

Implementing IDIsposable Interface In JavaScript Class Using ASP.NET AJAX Library.

Are you so happy to already know what memory leaks can do with a browser? Did you learn how to subscribe to document's events to release resources you used in javascript class? If you use ASP.NET AJAX, it gives a couple of good ways of handling scripts that need cleanup operation.

What is cleanup?

Ok, javascript is not ideal environment to develop large code bases. There is garbage collector for javascript objects, but it has some problem when dealing with some specific situations. Check out this MSDN article by Justin Rogers to learn more. Actually we are not having trouble only with IE. Any browser has specific leaks.

This became a big problem in my current project. The project has one main page. And that page is loaded only once ! No refresh, no postback - it stays opened for whole lifecycle of a user session. There are hundreds of controls that are loading later using AJAX. And using this architecture with a lot of client-side logic, memory leaks - if not detected on early stages - can become a real headache later. Another problem is third-party scripts (calendars, sliders, etc.) that are not optimized to be used in AJAX environment, so cleanup after each usage should be very high priority task in such a project.

To solve the problem you need to release resources in some stage of page lifecycle in the browser. Normally it is onunload event of the window object.

How to write disposable class

You don't have to know the name of event  in every browser, or how to subscribe to it. We will solve this implementing on client-side IDisposable interface that is defined in ASP.NET AJAX library.

Simplest javascript class implementing IDisposable interface looks like this:

/// <reference name="MicrosoftAjax.debug.js" />
/// <reference name="MicrosoftAjaxTimer.debug.js" />
/// <reference name="MicrosoftAjaxWebForms.debug.js" />

// Register namespace
Type.registerNamespace('MyNamespace');

//constructor
MyNamespace.MyClass = function() {
    
    // register the object as disposable, so the application will call it's dispose method when needed
    if(typeof(Sys) !== "undefined")Sys.Application.registerDisposableObject(this);
    
    // initialize class level variables from arguments
    this.MyVar1 = $get(arguments[0]);
    this.MyVar2 = $get(arguments[1]);
    
    // initialize other class level variables and constants
    this._myCssClassName = "someClass";
};

MyNamespace.MyClass.prototype = {

    dispose : function() {
        /// <summary>
        /// Implements dispose method
        /// of IDisposable interface,
        /// cleanup resources here.
        /// mainly detach events you attached earlier,
        /// assign nulls to elements that may leak etc...
        /// </summary>    
        this.MyVar1 = null;
        this.MyVar2 = null;
        alert("dispose executed!");
    },
    
    myMethod1 : function(param1, param2) {
        // TODO: implement logic
    },
    
    myMethod2 : function(param1, param2) {
        // TODO: implement logic
    },
    
    myMethod3 : function(param1, param2) {
        // TODO: implement logic
    }
};

// claim the class is implementing IDisposable
if(typeof(Sys) !== "undefined")MyNamespace.MyClass.registerClass('MyNamespace.MyClass', null, Sys.IDisposable);

// standard call for non-embedded scripts.
if(typeof(Sys) !== "undefined")Sys.Application.notifyScriptLoaded();

So the interesting part of the script is:

MyNamespace.MyClass.registerClass('MyNamespace.MyClass', null, Sys.IDisposable);

with this line we are saying - our object implements IDisposable interface.

Other important line is in the constructor of an object:

Sys.Application.registerDisposableObject(this);

this line registers our object(note - object is an instance of our class!) as disposable object. This way AJAX framework knows that it should call our dispose method when needed.

And don't forget to implement "dispose" function itself:

dispose : function() {this.MyVar1 = null; this.MyVar2 = null; alert("dispose executed!"); },

Here we made some dummy cleanup - in real application it depends on many factors. most often you will need to remove handlers from DOM elements and assign null-s to some specific variables that are the reason of memory leak.

You can read more about registerClass and registerDisposableObject on official ASP.NET AJAX documentation website.

That's it - now we can write some small test page to test the script and try to leave the page bypassing dispose method execution(you can do it only by killing a process in a task manager - but in this case you have nothing to cleanup :) )

<%@ 
    Page Language="C#" 
    AutoEventWireup="true" 
    CodeFile="Default.aspx.cs" 
    Inherits="_Default" 
%>

<!DOCTYPE html 
    PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html 
    xmlns="http://www.w3.org/1999/xhtml">
<head 
    runat="server">
    <title>Implementing IDisposable interface</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/MyScript.js" />
            </Scripts>
        </asp:ScriptManager>
        <div>
            <input 
                type="button" 
                value="Do Something" 
                onclick="myObj.myMethod1();" />
            <p>
                Before you try to leave (or refresh) this page, you should
                see alert saying "dispose executed!".
                This means AJAX framework called our object to let us 
                cleanup the resources
            </p>
        </div>
    </form>
    <script type="text/javascript">
        var myObj = new MyNamespace.MyClass();
    </script>
</body>
</html>

Now if we try to leave the page dispose method is called in our object:

image

You can download the source code, or test it live!

Technorati Tags: ,

No comments:

Post a Comment