自从 MS Ajax Beta 推出以来的过去几周,我收到了大量有关 wwHoverPanel 控件在与 MS Ajax 结合运行时遇到一些问题的报告。控件本身不会直接干扰 MS AJAX,但如果您将控件粘贴在 AJAX UpdatePanel() 内,则会出现问题,因为控件吐出的脚本代码不会正确生成到回调生成的更新中。由于缺少脚本代码,控件仍然可以工作,但会表现出一些意外的行为。例如,放置到更新面板中的悬停面板在许多情况下会丢失其位置,并且不会在当前鼠标光标位置弹出,而是会在其所在的容器控件的边框处弹出。
问题在于,Microosft 在 MS AJAX Beta 中决定采用完全独立的脚本生成引擎,该引擎通过 ScriptManager 控件驱动。 MS Ajax ScriptManager 模仿许多 ClientScript 对象的方法,但将它们作为静态方法提供(谢天谢地!如果没有这些方法,我们就真的完蛋了)。
因此,像 RegisterClientScriptBlock、ResgisterClientScriptResources 这样的方法——任何涉及将脚本代码放入页面的方法都在 ScriptManager 中具有相关的静态方法。 ScriptManager 方法将 Control 作为附加的第一个参数传递,但在其他方面模仿现有的 ClientScriptManager。
不过,这种新行为将现有控件放入绑定中 - 如果代码使用 ClientScriptManager,则 UpdatePanels 将无法看到脚本代码(如果需要在回调中更新)。但同时控件开发人员不能假设 MS Ajax ScriptManager 确实存在。
所有这一切的最终结果是,处理这种不匹配并不完全直接,需要创建一个包装对象来决定使用哪个控件。包装器需要决定 MS Ajax 在应用程序中是否可用,如果可用,则使用 Reflection 访问 ScriptManager 以写出任何脚本代码。
不过,我不能将此归功于:Eilon Lipton 不久前发布了有关此问题的文章,他的代码确实是我需要的,我只是将其包装到我使用过的 ClientScriptProxy 对象中的控制。我基本上添加了一些在应用程序中使用的 ClientScript 方法。这是课程:
[*** 代码更新:2006 年 12 月 12 日来自评论 *** ]
/// <summary>
/// 这是 Page.ClientScript 和 MS Ajax ScriptManager
/// 对象的代理对象,可以在 MS Ajax 不存在时运行。因为 MS Ajax
/// 可能不可用,所以无法直接访问方法
/// 并且我们需要通过
/// 此类间接引用客户端脚本方法。
///
/// 此类应在控件启动时调用,并用于
/// 替换所有调用 Page.ClientScript。
/// 通过反射
进行 Scriptmanager 调用
/// </summary>
public class ClientScriptProxy
{
private static Type scriptManagerType = null;
// *** 注册 ScriptManager 的代理方法
private static MethodInfo RegisterClientScriptBlockMethod;
私有静态MethodInfo RegisterStartupScriptMethod;
私有静态MethodInfo RegisterClientScriptIncludeMethod;
私有静态MethodInfo RegisterClientScriptResourceMethod;
//私有静态MethodInfo RegisterPostBackControlMethod;
//私有静态MethodInfo GetWebResourceUrlMethod;
ClientScriptManager 客户端脚本;
/// <summary>
/// 确定 MsAjax 在此 Web 应用程序中是否可用
/// </summary>
public bool IsMsAjax
{
get
{
if (scriptManagerType == null)
CheckForMsAjax();
返回_IsMsAjax;
}
}
私有静态布尔_IsMsAjax = false;
公共布尔IsMsAjaxOnPage
{
获取
{
返回_IsMsAjaxOnPage;
}
}
私有 bool _IsMsAjaxOnPage = false;
/// <summary>
/// 此类的当前实例,应始终用于
/// 访问此对象。没有公共构造函数来
确保引用用作单例。
/// </summary>
public static ClientScriptProxy Current
{
get
{
return
( HttpContext.Current.Items["__ClientScriptProxy"] ??
(HttpContext.Current.Items["__ClientScriptProxy"] =
new ClientScriptProxy(HttpContext.Current.Handler as Page) )))
作为 ClientScriptProxy;
}
}
/// <summary>
/// 基本构造函数。传入页面名称,以便我们可以获取
/// 股票
/// </summary>
/// <param name="CurrentPage"></param>
protected ClientScriptProxy(Page CurrentPage)
{
this.clientScript = CurrentPage .ClientScript;
}
/// <summary>
/// 检查 MS Ajax 是否已在当前
/// Web 应用程序中注册。
///
/// 注意:方法是静态的,因此可以从
/// 任何地方
直接访问/// </summary>
/// <returns></returns>
public static bool CheckForMsAjax()
{
scriptManagerType = Type. GetType("Microsoft.Web.UI.ScriptManager, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
if (scriptManagerType != null)
{
_IsMsAjax = true;
返回真;
}
_IsMsAjax = false;
返回假;
}
/// <summary>
/// 在页面中注册客户端脚本块。
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="key"></ param>
/// <param name="script"></param>
/// <param name="addScriptTags"></param>
public void RegisterClientScriptBlock(Control control, Type type, string key, string script, bool addScriptTags )
{
if (this.IsMsAjax)
{
if (RegisterClientScriptBlockMethod == null)
RegisterClientScriptBlockMethod = scriptManagerType.GetMethod("RegisterClientScriptBlock"); }
RegisterClientScriptBlockMethod.Invoke(null, new object[5] { control, type, key, script, addScriptTags });
否则
this.clientScript.RegisterClientScriptBlock(type, key, script, addScriptTags)
;
}
/// <summary>
/// 注册一个放置在页面底部的启动代码片段
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="key"></param>
/// <param name="script"></param>
/// <param name="addStartupTags" ></param>
public void RegisterStartupScript(Control control, Type type, string key, string script, bool addStartupTags)
{
if (this.IsMsAjax)
{
if (RegisterStartupScriptMethod == null)
RegisterStartupScriptMethod = scriptManagerType.GetMethod("RegisterStartupScript"); }
RegisterStartupScriptMethod.Invoke(null, new object[5] { control, type, key, script, addStartupTags });
else
this.clientScript.RegisterStartupScript(
type, key, script, addStartupTags);
}
/// <summary>
/// 将脚本包含标记注册到页面中以获取外部脚本 url
/// </summary>
/// <param name="control"></param>
/// <param name ="type"></param>
/// <param name="key"></param>
/// <param name="url"></param>
public void RegisterClientScriptInclude(Control control, Type type, string key, string url)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptIncludeMethod == null)
RegisterClientScriptIncludeMethod = scriptManagerType.GetMethod("RegisterClientScriptInclude"); }
RegisterClientScriptIncludeMethod.Invoke(null, new object[4] { control, type, key, url });
否则
this.clientScript.RegisterClientScriptInclude(
类型, key, url);
}
/// <summary>
/// 将脚本包含标记添加到 WebResource 页面中。
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="resourceName"></ param>
public void RegisterClientScriptResource(Control control, Type type, string resourceName)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptResourceMethod == null)
RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource"); }
RegisterClientScriptResourceMethod.Invoke(null, new object[3] { 控制, 类型, 资源名称 });
否则
this.clientScript.RegisterClientScriptResource(type,resourceName)
;
}
public string GetWebResourceUrl(Control control, Type type, string resourceName)
{
//if (this.IsMsAjax)
//{
// if (GetWebResourceUrlMethod == null)
// GetWebResourceUrlMethod = scriptManagerType.GetMethod("GetScriptResourceUrl");
// 返回 GetWebResourceUrlMethod.Invoke(null, new object[2] { resourceName, control.GetType().Assembly }) 作为字符串;
//}
//否则
返回 this.clientScript.GetWebResourceUrl(type, resourceName);
}
}
该代码主要检查 MS Ajax 程序集是否可以作为类型进行访问,如果可以,则假定已安装 MS Ajax。这并不是最理想的 - 最好知道 ScriptManager 是否确实在当前页面上使用,但如果不扫描所有控件(慢),我看不到轻松做到这一点的方法。
该控件缓存每个 MethodInfo 结构,以推迟对 ScriptManager 方法进行反射调用时的一些开销。我不认为这里的反射会引起太多关于开销的担忧,除非你有很多对这些方法的调用(我想如果你有很多资源的话这是可能的——例如像 FreeTextBox 这样的控件)。即使如此,反射开销可能也不值得担心。
要使用此类,所有对 ClientScript 的调用都将替换为调用此类。因此,在控件初始化期间,我添加了以下内容:
protected override void OnInit(EventArgs e)
{
this.ClientScriptProxy = ClientScriptProxy.Current;
基.OnInit(e);
}
然后使用它:
this.ClientScriptProxy.RegisterClientScriptIninclude(this,this.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE,
this.ResolveUrl(this.ScriptLocation));
请注意,第一个参数是控件实例(通常是 this),就像 ScriptManager 调用一样,因此从 ClientScript 代码转换时参数会略有变化。
一旦我将此代码添加到我的控件中,UpdatePanel 的问题就消失了,并且即使控件托管在 UpdatePanel 内部,它也可以再次正确呈现。