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";
. . . . . . . .
. . . . . . . .
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 {
public class Ajax {
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;
}
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;
}
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;
}
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);
}
}
}
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);
}
}
}
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";
. . . . . . . .
. . . . . . . .
scriptCode += "// -->\n</script>";
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "MyScriptName",
scriptCode);
if (Ajax.IsControlInUpdatePanel(this) && Ajax.IsInAsyncPostBack(Page))
Ajax.RegisterClientScriptBlock(Page, this.GetType(), "MyScriptName",
scriptCode, false)
}