HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

Rate It (4)

Last post 10-25-2007 4:46 PM by stock. 21 replies.

Sort Posts:

  • HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    10-29-2006, 6:50 PM
    • Loading...
    • Eilon
    • Joined on 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 716
    • AspNetTeam

    Buying Into Microsoft ASP.NET AJAX without Necessarily Paying For It

    By Eilon Lipton, Software Design Engineer on Microsoft ASP.NET AJAX

    Abstract

    Without the release of Microsoft ASP.NET AJAX just around the corner, taking advantage of the new AJAX features is all the buzz. However, what if some of your customers are not yet upgrading to ASP.NET AJAX? This article discusses a technique to allow your ASP.NET server control to function without ASP.NET AJAX installed, while taking advantages of new features such as the UpdatePanel control’s asynchronous postbacks when ASP.NET AJAX is available.

    Introduction

    The problem being solved here is how to access ASP.NET AJAX functionality when you can’t link against the Microsoft.Web.Extensions.dll assembly installed in the GAC. The primary technique we will use is the .NET Framework’s Reflection feature, which allows us to dynamically call functions without taking a compile-time dependency on them.

    The intent of the sample control is neither to show how to write an ASP.NET control, nor the best practices of doing such. The sample control is just to demonstrate patterns and practices for using Microsoft ASP.NET AJAX to write compatible controls.

    The techniques demonstrated here are typically only required for controls that do not take advantage of the Microsoft AJAX client libraries and the server interfaces, such as IScriptControl. Controls that use those features typically have to link against the Microsoft.Web.Extensions.dll assembly anyway. Rather, these techniques are intended for controls that were built against ASP.NET 2.0 and wish to merely “play nicely” with ASP.NET AJAX.

    The full source code is available here.

    Why Write an ASP.NET AJAX-Aware Control?

    The ASP.NET AJAX UpdatePanel control places restrictions on what the controls placed inside it are allowed to do. If the control is not intended to be placed inside an UpdatePanel control then are no restrictions on what it may do. The key restrictions for a control inside an UpdatePanel are:

    1. It must perform script registration through the ScriptManager’s registration APIs instead of through the Page.ClientScript APIs. There is a simple one-to-one mapping from the old APIs to the new APIs.
    2. If the control attaches event handlers it must implement dispose functionality. The “dispose” expando technique will be shown, although there are several other ways to do this.
    3. The scripts registered by the control need to be divided into two: 1. the script library code, which contains only function and class declarations, and is shared by all instances of the control; and 2. the initialization code for the control, which is unique to each control instance.

    If your customers want to use your control inside an UpdatePanel, you will have to abide by these restrictions.

    For more information on how UpdatePanels work, please see this forum post: http://forums.asp.net/thread/1445414.aspx.

    The Sample MathWidget Control

    The sample control used for demonstration purposes is a simple ASP.NET composite control that performs math operations. Rather than posting back to the server to do the calculations, it includes a client script library that does the work in the browser.

    Math Widget

    If you’re been following so far, you’re probably thinking to yourself, “oh no, it’s registering client script – that will never work inside an UpdatePanel!” Here’s an outline of the control and the code it uses to perform its script registration:

    namespace MathWizard {


    public class MathWidget : CompositeControl {
    protected override void CreateChildControls() {

    }

    protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);

    // Register script library
    Page.ClientScript.RegisterClientScriptResource(
    typeof(MathWidget),
    "MathWizard.MathWidget.js");

    // Register initialization
    Page.ClientScript.RegisterStartupScript(
    typeof(MathWidget),
    ClientID + "_KeyStuff",
    "MathWidget_Initialize('" + ClientID + "');",
    true);
    }
    }
    }
      

    Of the restrictions mentioned earlier only the third one is being followed: separation of script library and initialization code. Clearly this control will not work when placed inside an UpdatePanel since the async postback processing will be unaware of the script registrations performed during that postback. Only registrations performed through the ScriptManager APIs will work during an async postback.

    Here’s the initial version of the client-side library for the MathWidget control:

     
    function MathWidget_Add(widgetID) {
    // Perform add button operation
    var leftOperand = parseFloat(document.getElementById(widgetID + "_LeftOperandTextBox").value);
    var rightOperand = parseFloat(document.getElementById(widgetID + "_RightOperandTextBox").value);
    document.getElementById(widgetID + "_ResultTextBox").value = leftOperand + rightOperand;
    }

    function MathWidget_Multiply(widgetID) {
    // Perform multiply button operation
    var leftOperand = parseFloat(document.getElementById(widgetID + "_LeftOperandTextBox").value);
    var rightOperand = parseFloat(document.getElementById(widgetID + "_RightOperandTextBox").value);
    document.getElementById(widgetID + "_ResultTextBox").value = leftOperand * rightOperand;
    }

    function MathWidget_Initialize(widgetID) {
    // Initialize a client instance of the MathWidget control
    var leftOperand = document.getElementById(widgetID + "_LeftOperandTextBox");
    var rightOperand = document.getElementById(widgetID + "_RightOperandTextBox");
    leftOperand.attachEvent('onkeypress', MathWidget_KeyPressHandler);
    rightOperand.attachEvent('onkeypress', MathWidget_KeyPressHandler);
    }

    function MathWidget_KeyPressHandler() {
    // Allow only numeric keys to go through
    if (window.event.keyCode < 48 ||
    window.event.keyCode > 57) {
    window.event.returnValue = false;
    }
    }
     

    As you can tell, the MathWidget_Initialize() method hooks up event handlers to DOM elements. This will also break during an async post since we’ll be leaving some old event handlers attached to DOM events, as mentioned in the second restriction. Although this usually doesn’t produce disastrous results, it’s a great way to cause a memory leak. To avoid the memory leak we’ll need to implement dispose functionality.

    Making the Control Register Its Scripts

    The first step in getting the control to work with UpdatePanels is to write a compatibility layer that hides some of the implementation details. This is especially important since the compatibility layer will use the .NET Framework’s Reflection feature to do the method calls, and we’d rather hide that code from the control’s code.

    The compatibility layer’s job is to detect whether ASP.NET AJAX is available. When it is available, it uses the new ScriptManager APIs to perform the script registration. When it isn’t available, it calls the ASP.NET 2.0 Page.ClientScript APIs. This fulfils the first restriction. Here’s an outline of the compatibility layer:

     
    namespace MathWizard {


    internal static class ScriptManagerHelper {
    private static readonly object ReflectionLock = new object();
    private static bool MethodsInitialized;
    private static MethodInfo RegisterClientScriptResourceMethod;


    private static void InitializeReflection() {
    if (!MethodsInitialized) {
    lock (ReflectionLock) {
    if (!MethodsInitialized) {
    Type scriptManagerType = Type.GetType("Microsoft.Web.UI.ScriptManager, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
    if (scriptManagerType != null) {
    RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource");

    }
    MethodsInitialized = true;
    }
    }
    }
    }

    public static void RegisterClientScriptResource(Control control, Type type, string resourceName) {

    InitializeReflection();
    if (RegisterClientScriptResourceMethod != null) {
    // ASP.NET AJAX exists, so we use the ScriptManager
    RegisterClientScriptResourceMethod.Invoke(null, new object[] { control, type, resourceName });
    }
    else {
    // No ASP.NET AJAX, so we just call to the ASP.NET 2.0 method
    control.Page.ClientScript.RegisterClientScriptResource(type, resourceName);
    }
    }

    }
    }
     

    The basic technique is to try locating the ScriptManager type in the Microsoft.Web.Extensions.dll assembly. If it can’t be found, we know that ASP.NET AJAX is not available. If it was found then we locate specific methods of interest and hold on to them so that we can call them later.

    We then change the code in our control to call the new methods:

     
            protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    // Register script library
    ScriptManagerHelper.RegisterClientScriptResource(
    this,
    typeof(MathWidget),
    "MathWizard.MathWidget.js");

    // Register initialization
    ScriptManagerHelper.RegisterStartupScript(
    this,
    typeof(MathWidget),
    ClientID + "_KeyStuff",
    "MathWidget_Initialize('" + ClientID + "');",
    true);
    }
     

    That’s two restrictions down, and one to go.

    Implementing Dispose

    Fortunately, implementing dispose functionality is rather easy. Typically to implement dispose functionality I look at what my initialization functionality does and do it backwards. In the case of the MathWidget script library the initialize function attaches event handlers, so our dispose method must detach those handlers.

    Here’s what the client script dispose function looks like:

    function MathWidget_Dispose(widgetID) {
        // Initialize a client instance of the MathWidget control
        var leftOperand = document.getElementById(widgetID + "_LeftOperandTextBox");
        var rightOperand = document.getElementById(widgetID + "_RightOperandTextBox");
        leftOperand.detachEvent('onkeypress', MathWidget_KeyPressHandler);
        rightOperand.detachEvent('onkeypress', MathWidget_KeyPressHandler);
    }
    
     

    The next problem is how we get the ASP.NET AJAX async postback processing to call the dispose function when the UpdatePanel gets updated. As mentioned earlier, there are several techniques. This technique is the easiest to implement for existing controls, and it involves adding a “dispose” expando to one of our DOM elements, in this case the main <span> tag surrounding our control. When an async postback returns from the server and the UpdatePanels’ contents need to be updated, ASP.NET AJAX will search the contents of the UpdatePanel for DOM elements with a “dispose” expando. If it finds it and the expando is a function, it will call it. Here’s how we register the expando:

            protected override void OnPreRender(EventArgs e) {
                …
                // Register dispose if Microsoft ASP.NET AJAX is available
                if (ScriptManagerHelper.IsMicrosoftAjaxAvailable()) {
                    ScriptManagerHelper.RegisterStartupScript(
                        this,
                        typeof(MathWidget),
                        ClientID + "_Dispose",
                        @"
    document.getElementById('" + ClientID + @"').dispose = function() {{
        MathWidget_Dispose('" + ClientID + @"');
    }}
    ",
                        true);
                }
            }
      

    Note that we only bother registering the expando if ASP.NET AJAX is available. It doesn’t hurt to always register it, but there’s no point cluttering the page with script that won’t get used.

    Summary

    We fulfilled all three requirements to be fully compatible with UpdatePanels and we barely had to modify our control. You can also extend the compatibility layer to call other methods on ScriptManager, as necessary.

    All the source code here is free for any purposes you have. I’d love to hear your feedback on it too!

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    10-29-2006, 7:11 PM
    • Loading...
    • jodywbcb
    • Joined on 03-12-2003, 11:52 AM
    • West Seattle,WA
    • Posts 985

    Wow - thanks for providing such a substantial overview - informative - detailed and actually provides additional insight into issues I have been having.  Particularly the dispose...  I had had assumed at least when dealing with dynamic controls loaded in a placeholder that calling the [placeholder].Controls.Clear() and [placeholder].dispose() and calling [myupdatepanel].Update() would get rid of client-side scripts but I am assured it doesn't..Think I shall try to see if I can incorporate that as well....

     

    This would be a great article to post on your documentation site as well under tutorials (which would make it incredibly easy to find - along with the other documentation you and the team have written in the forums...)..

     

    Thanks again!

     

     

    -- jody
    My Blogs on .Net 2.0 and Ajax
    http://csk.wbcb.com
    http://ArtbyJody.com
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    10-30-2006, 7:40 PM
    • Loading...
    • Eilon
    • Joined on 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 716
    • AspNetTeam

    I'm glad you like the article.

    I forwarded the article as well as my previous posts to a bunch of people on the Atlas team, including our writers. Hopefully the content will be included in some form or another in the documentation.

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    10-31-2006, 3:20 AM
    • Loading...
    • infinitevs
    • Joined on 10-27-2006, 2:56 PM
    • Posts 21

    A really nice explaination of this, a great help thanks Big Smile

  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-01-2006, 12:57 PM
    • Loading...
    • Girijesh
    • Joined on 10-27-2005, 12:04 PM
    • India
    • Posts 638
    well done.
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-06-2006, 2:50 AM
    Thanks Eilon - great explanation and very easy to follow.
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-06-2006, 9:43 PM
    • Loading...
    • infinitevs
    • Joined on 10-27-2006, 2:56 PM
    • Posts 21
    does someone wanna explain the dispose expando part to me, in more detail, it doing my head in..?
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-15-2006, 6:34 PM
    Answer
    • Loading...
    • Eilon
    • Joined on 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 716
    • AspNetTeam

    The dispose expando is used to detach any resources that your control is using. If you don't implement dispose you could experience problems ranging from memory leaks to JavaScript errors to other broken behavior. The PageRequestManager (the client-side "brains" of UpdatePanel) will scan the DOM inside the UpdatePanel for elements supporting "dispose" functionality and invoke those methods to give them a chance to clean up their resources.

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-16-2006, 9:50 AM
    • Loading...
    • infinitevs
    • Joined on 10-27-2006, 2:56 PM
    • Posts 21

    Thanks eilon I appreciate the feedback, but can you please give me an example in code of what you mean? By expando do you mean, <element dispose="dispose" /> and if so how does one implement this, a simple example would be greatly appreciated..

    thanks in advance,

    kind regards,

    infin.

  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-16-2006, 12:06 PM
    • Loading...
    • Eilon
    • Joined on 06-26-2002, 6:14 PM
    • Redmond, WA
    • Posts 716
    • AspNetTeam

    In the article look under the section "Implementing Dispose". It dynamically adds an expando named "dispose" to the DOM element that points to a JavaScript function. There are two reasons you can't set the expando declaratively: 1. It's not valid XHTML, and 2. The value would be a string literal instead of an actual function.

    Thanks,

    Eilon

    Blog: http://weblogs.asp.net/LeftSlipper/
  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    11-27-2006, 11:37 PM
    • Loading...
    • yagimay
    • Joined on 12-10-2003, 12:04 AM
    • Posts 9

    Thanks for the great post Eilon.

    I was trying to clarify your post with ASP.NET AJAX Beta 2.
    Instead of MathWidget register its scripts via ScriptManager, I
    made it register via Page.ClientScript. I meant to ensure the control
    not work on UpdatePanel unless it register its scripts via ScriptManager.

    However it seems to work even when the scripts are registered via Page.ClientScript.
    Is the post still valid with Beta 2 or has there been any enhancement ?

    Here is what I do.

    1. Modify MathWidget.cs to register scripts via Page.ClientScript.
    ------------------------------------------
     MathWidget.cs
    ------------------------------------------
            protected override void OnPreRender(EventArgs e)
            {
                base.OnPreRender(e);
                // Register script library
                Page.ClientScript.RegisterClientScriptResource(
                    typeof(MathWidget),
                    "MathWizard.MathWidget.js");
                // Register initialization
                Page.ClientScript.RegisterStartupScript(
                    typeof(MathWidget),
                    ClientID + "_KeyStuff",
                    "MathWidget_Initialize('" + ClientID + "');",
                    true);
            }

    2. (a) Generate WebForm and add UpdatePanel.
        (b) Place MathWidget, Button and Label on UpdatePanel.
        (c) Add following code (Button1.Click returns current time).
    ------------------------------------------
     Default.aspx.cs
    ------------------------------------------
        protected void Button1_Click(object sender, EventArgs e)
        {
            Label1.Text = DateTime.Now.ToString();
        }

    3. Debug WebForm.
       (a) Make sure MathWidget works.
       (b) Click Button. --- Label displays current time.
       (c) MathWidget still works.

  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    12-16-2006, 12:51 AM
    • Loading...
    • cnelson1
    • Joined on 10-15-2006, 6:15 AM
    • Posts 31

    Thanks for the info, Eilon.

    Do I need to use the dispose function if I am not using attachevent? In my composite controls, I just use this.xxx.Attributes.Add() to attach my javascript to my controls. If there is some kind of dispose I need to use, can you give an example?

    Thanks,

    Carl

     

  • Re: HOWTO: Write controls compatible with UpdatePanel without linking to the ASP.NET AJAX DLL

    12-18-2006, 8:34 AM