在自定义控件中支持 ASP.NET AJAX






4.72/5 (21投票s)
如何更新您的 ASP.NET 自定义控件以使其与 ASP.NET AJAX 正确配合使用。
引言
本文对于任何希望更新其控件以使其与 ASP.NET AJAX 正确配合使用的自定义组件开发人员都将非常有用。首先,我们将描述在进行此类自定义时最常见的问题,然后提出针对这些问题的解决方案。
问题描述
假设您为 ASP.NET 开发了一些可视化控件(例如,具有一些改进的编辑框)。 假设您的控件使用一些客户端脚本来实现其行为中的这些“改进”。
问题是:您的控件是否能很好地与 ASP.NET AJAX 增强功能一起使用? 或者更确切地说:如果有人将您的控件放置在 UpdatePanel
中以启用部分页面更新,一切是否会正常? 总体答案是:否。 我将告诉您原因。 如果您的控件使用一些客户端脚本,则应在 PreRender
阶段使用 ClientScriptManager
类的 RegisterClientScriptBlock
方法注册这些脚本。 通常,它看起来类似于以下代码段
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);
}
此代码将运行良好,并且您的脚本将在正常页面呈现时包含在页面上。 它甚至可以在 UpdatePanel
内部工作,但前提是您的组件是静态的 - 我的意思是,如果它像往常一样放置在页面上,并在首次页面呈现时创建。 但是,如果您的组件放置在 UpdatePanel
内部并且是响应某些异步更新而动态创建的,则此类代码段将不起作用。
解决方案
为了解决这个问题,我们需要具备以下能力
- 确定我们的控件是否在
UpdatePanel
内部的能力; - 确定当前回发是在部分呈现模式下执行还是普通同步回发的能力;
- 在异步回发期间注册我们的客户端脚本的方法。
此外,实现所有描述功能的代码不得对 ASP.NET AJAX 程序集(System.Web.Extensions.dll)使用静态链接,否则我们的控件将无法在未安装 ASP.NET AJAX 的网站上运行。
前两个功能很容易实现。 我们只需要查找控件父级并检查其中是否有任何一个控件是 UpdatePanel
控件。 对于第二个任务,我们还需要检查找到的 UpdatePanel
的 IsInPartialRendering
属性。 请记住我们关于静态链接的限制:因此我们可以以通常的方式访问该属性,但需要使用 System.Reflection
命名空间中的类提供的功能。
至于第三个问题(在部分更新时注册客户端脚本):我们应该使用 ScriptManager
对象的 RegisterClientScriptXXX
方法,该方法应包含在每个支持 ASP.NET AJAX 的页面上。 因此,我们需要首先在我们的页面上找到该对象,然后调用必要的方法。 唯一的问题(再次):我们不能直接这样做。 相反,我们应该首先尝试加载 ASP.NET AJAX 程序集,然后找到 ScriptManager
对象并使用反射调用该对象的方法。
因此,这是实现所有描述任务的代码(AJAX 类)。 此类的所有方法都定义为 static
,因此我们不需要创建此类实例来调用它们。
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);
}
}
}
}
}
现在,使用此类,我们可以修改代码中第一章的 OnPreRender
方法
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)
}