Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

ASP.NET AJAX support in custom controls

4.72/5 (22 votes)
15 Jan 2009CPOL3 min read 1   383  
How to update your ASP.NET custom control to make it work with ASP.NET AJAX correctly.

Introduction

This article will be useful for any custom component developer who wants to update his/her controls so they will work correctly with ASP.NET AJAX. First, we will describe the most common problems that occur during such customizations, and then propose the solution for those issues.

Problem Description

Let's suppose you develop some visual control for ASP.NET (e.g., some kind of edit box with several improvements). Let's also suppose your control uses some client scripts to implement those "improvements" in its behaviour.

The question is: will your control work well with ASP.NET AJAX enhancements? Or more precisely: will everything be all right if somebody places your control inside an UpdatePanel to enable partial-page updates? The general answer is: No. And, I will tell you why. If your control uses some client-side scripts, then you should register those scripts on the PreRender stage using the RegisterClientScriptBlock method of the ClientScriptManager class. Usually, it looks similar to the following piece of code:

C#
protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);
    string scriptCode = "<script type=\"text/javascript\">\n<!--\n";
    . . . . . . . . 
    //script code definition
    . . . . . . . . 
    scriptCode += "// -->\n</script>";


    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScriptName",
        scriptCode);
}

This code will work well, and your script will be included on the page on normal page rendering. It will even work inside an UpdatePanel, but only if your component is static - I mean, if it is placed on the page as usual and is created at the first page rendering. But, such a piece of code will not work if your component is placed inside an UpdatePanel and is created dynamically in response to some asynchronous update.

The Solution

To solve the problem, we need the following abilities:

  • the ability to determine if our control is inside an UpdatePanel;
  • the ability to determine whether the current post-back is being executed in partial-rendering mode or is an usual synchronous post-back;
  • a way to register our client scripts during an asynchronous post-back.

Moreover, the code which implements all the described features must not use static linking for an ASP.NET AJAX assembly (System.Web.Extensions.dll) because otherwise our control will not work on websites which do not have ASP.NET AJAX installed.

The first two features are quite easy to implement. We just need to look for control parents and check if any of them is an UpdatePanel control. For the second task, we will also need to check the IsInPartialRendering property of the found UpdatePanel. Please remember our limitation about static linking: so we can just access the property in the usual way, but need to use the abilities provided by the classes from the System.Reflection namespace.

As for the third problem (client-script registering on partial updates): we should use the RegisterClientScriptXXX methods of the ScriptManager object which should be included on each page that supports ASP.NET AJAX. So, we need to find that object on our page first, and just call the necessary method. The only problem (again): we can not do that directly. Instead, we should try to load the ASP.NET AJAX assembly first, then find the ScriptManager object and call the necessary method of that object using Reflection.

So, here is the code (the AJAX class) which implements all the described tasks. All methods of this class are defined as static, so we will not need to create an instance of this class to call them.

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;

namespace Korzh.WebControls {
    /// <summary>
    /// Represents different procedures for ASP.NET AJAX extentions support
    /// </summary>
    public class Ajax {
        /// <summary>
        /// Determines whether the specified control is inside UpdatePanel
        /// </summary>
        /// <param name="control">The control.</param>
        /// <returns>
        /// <c>true</c> if the specified control is inside UpdatePanel; otherwise,
        /// <c>false</c>.
        /// </returns>
        public static bool IsControlInsideUpdatePanel(Control control) {
            Control parent = control.Parent;
            while (parent != null) {
                if (parent.GetType().FullName.Equals("System.Web.UI.UpdatePanel"))
                    return true;
                parent = parent.Parent;
            }
            return false;            
        }

        /// <summary>
        /// Determines whether the specified control is in partial rendering.
        /// </summary>
        /// <param name="control">The control.</param>
        /// <returns>
        /// <c>true</c> if the specified control is in partial rendering; otherwise,
        /// <c>false</c>.
        /// </returns>
        public static bool IsControlInPartialRendering(Control control) {
            Control parent = control.Parent;
            while (parent != null) {
                if (parent.GetType().FullName.Equals("System.Web.UI.UpdatePanel")) {
                    System.Reflection.PropertyInfo propInfo =
                        parent.GetType().GetProperty("IsInPartialRendering");
                    if (propInfo != null) 
                        return (bool)propInfo.GetValue(parent, null);
                    else
                        return false;
                }
                parent = parent.Parent;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the current postback is being executed in
        /// partial-rendering mode.
        /// </summary>
        /// <param name="page">The page object.</param>
        /// <returns>
        /// <c>true</c> if  the current postback is being executed in
        /// partial-rendering mode; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsInAsyncPostBack(Page page) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                System.Reflection.PropertyInfo propInfo = smClass.GetProperty(
                    "IsInAsyncPostBack");
                if (propInfo != null)
                    return (bool)propInfo.GetValue(scriptManager, null);
                else
                    return false;
            }
            return false;

        }

        private static bool ObjectIsInheritedFrom(Object obj, string fullTypeName) {
            Type type = obj.GetType();
            while (type != null) {
                if (type.FullName.Equals(fullTypeName)) return true;
                type = type.BaseType;
            }
            return false;
        }

        private static object FindScriptManager(Control parent) {
            foreach (Control control in parent.Controls) {
                if (ObjectIsInheritedFrom(control, "System.Web.UI.ScriptManager"))
                    return control;
                object result = FindScriptManager(control);
                if (result != null) return result;
            }
            return null;
        }

        private static System.Reflection.Assembly ajaxAssembly = null;

        private static void LoadAjaxAssembly() {
            if (ajaxAssembly == null) {
                ajaxAssembly = System.Reflection.Assembly.LoadFrom(
                    "System.Web.Extensions.dll");
            }
        }

        private static System.Type GetTypeFromAjaxAssembly(string className) {
            LoadAjaxAssembly();
            if (ajaxAssembly != null)
                return ajaxAssembly.GetType(className);
            else
                return null;
        }


        private static Type GetScriptManagerType(Object obj) {
            Type type = obj.GetType();
            while (type != null) {
                if (type.FullName.Equals("System.Web.UI.ScriptManager")) return type;
                type = type.BaseType;
            }
            return null;
        }

        /// <summary>
        /// Registers a client script block which will be rendered on each
        /// asynchronous postback.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The Page object that is registering the client
        /// script block.</param>
        /// <param name="type">The type of the client script block. </param>
        /// <param name="key">The string that uniquely identifies the
        /// script block.</param>
        /// <param name="script">A string that contains the script.</param>
        /// <param name="addScriptTags">
        ///  A Boolean value that indicates whether to enclose the script
        /// block in <script> tags.
        /// </param>
        public static void RegisterClientScriptBlock(Page page, Type type,
            string key, string script, bool addScriptTags) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, key, script,
                        addScriptTags };
                    smClass.InvokeMember("RegisterClientScriptBlock", 
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public | 
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }

        /// <summary>
        /// Registers a script file which to be rendered each time an asynchronous
        /// postback occurs.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The Page object that is registering the client
        /// script file.</param>
        /// <param name="type">The type of the client script file.</param>
        /// <param name="key">The string that uniquely identifies the script file.</param>
        /// <param name="url">The URL that points to the script file.</param>
        public static void RegisterClientScriptInclude(Page page, Type type,
            string key, string url) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, key, url };
                    smClass.InvokeMember("RegisterClientScriptInclude", 
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public | 
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }

        /// <summary>
        /// Registers a script file which to be rendered each time an asynchronous
        /// postback occurs.
        /// Works only if ScriptManager control is existed on the page. Otherwise
        /// does nothing.
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="type">The type of the client script file.</param>
        /// <param name="resourceName">The name of resource that contains the
        /// script file.</param>
        public static void RegisterClientScriptResource(Page page, Type type,
            string resourceName) {
            object scriptManager = FindScriptManager(page);
            if (scriptManager != null) {
                System.Type smClass = GetScriptManagerType(scriptManager);
                if (smClass != null) {
                    Object[] args = new Object[] { page, type, resourceName };
                    smClass.InvokeMember("RegisterClientScriptResource",
                            System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.Public |
                            System.Reflection.BindingFlags.InvokeMethod,
                            null, null, args);
                }
            }
        }
    }
}

Now, using this class, we can modify the OnPreRender method in our code from the first chapter:

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

    string scriptCode = "<script type=\"text/javascript\">\n<!--\n";
     . . . . . . . . 
    //script code definition
     . . . . . . . . 
    scriptCode += "// -->\n</script>";

    //register our client script for usual post-back
    //(will do nothing in case of partial udpate)
    Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScriptName",
        scriptCode);

    //register our script during asynchronous postback
    //this code will just do nothing if there is no ASP.NET AJAX installed
    if (Ajax.IsControlInUpdatePanel(this) && Ajax.IsInAsyncPostBack(Page))
        Ajax.RegisterClientScriptBlock(Page, this.GetType(), "MyScriptName",
        scriptCode, false)
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)