Click here to Skip to main content
Click here to Skip to main content

ASP.NET AJAX support in custom controls

, 15 Jan 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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.

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:

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)

Share

About the Author

Sergiy Korzh
Founder Korzh.com
Ukraine Ukraine
Software developer and entrepreneur.
 
Main projects:
* EasyQuery - ad-hoc data filtering UI for .NET applications;
* Localizer - localization tool kit for Delphi projects;
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralNice control! PinmemberSandeep Mewara27-Mar-10 6:09 
GeneralThank you!!! So much!!! PinmemberSilvioDelgado24-Feb-10 5:10 
GeneralThanks PinmemberMember 424231220-May-09 23:23 
QuestionHow about ScriptManager.GetCurrent Pinmemberdjsolid16-Jan-09 23:38 
AnswerRe: How about ScriptManager.GetCurrent PinmemberSergiy Korzh18-Jan-09 22:13 
Hello,
 
We have tried to build the class which will work whether ASP.NET AJAX installed on the server or not. If you use ScriptManager.GetCurrent then you will need to add System.Web.Extensions assembly into the References list of your own assembly that is not good in case if you want your control should work correctly without ASP.NET AJAX.
 
EasyQuery - user friendly ad-hoc query builder for your application or web-site.

QuestionIt's great and all... Pinmemberomnidragoon14-Jan-09 6:23 
AnswerRe: It's great and all... PinmemberSergiy Korzh15-Jan-09 0:28 
GeneralRegisterExpandoAttribute Pinmemberabellix12-Nov-08 1:53 
GeneralPerfect PinmemberMember 22453801-Sep-08 2:37 
Generala really good article. PinmemberRajib Ahmed22-Jun-08 7:31 
GeneralFormatting PinmemberJim Crafton20-Jun-08 4:13 
GeneralRe: Formatting PinmemberSergiy Korzh20-Jun-08 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141216.1 | Last Updated 15 Jan 2009
Article Copyright 2008 by Sergiy Korzh
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid