自從 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 日來自評論 *** ]
///
/// 這是 Page.ClientScript 和 MS Ajax ScriptManager
/// 物件的代理對象,可以在 MS Ajax 不存在時執行。因為 MS Ajax
/// 可能不可用,所以無法直接存取方法
/// 並且我們需要透過
/// 此類間接引用客戶端腳本方法。
///
/// 此類別應在控制項啟動時調用,並用於
/// 取代所有調用 Page.ClientScript。
/// 透過反射
進行 Scriptmanager 呼叫
///
public class ClientScriptProxy
{
private static Type scriptManagerType = null;
// *** 註冊 ScriptManager 的代理程式方法
private static MethodInfo RegisterClientScriptBlockMethod;
私人靜態MethodInfo RegisterStartupScriptMethod;
私有靜態MethodInfo RegisterClientScriptIncludeMethod;
私人靜態MethodInfo RegisterClientScriptResourceMethod;
//私有靜態MethodInfo RegisterPostBackControlMethod;
//私有靜態MethodInfo GetWebResourceUrlMethod;
ClientScriptManager 客戶端腳本;
///
/// 決定 MsAjax 在此 Web 應用程式中是否可用
///
public bool IsMsAjax
{
get
{
if (scriptManagerType == null)
CheckForMsAjax();
返回_IsMsAjax;
}
}
私人靜態布林_IsMsAjax = false;
公共布爾IsMsAjaxOnPage
{
取得
{
返回_IsMsAjaxOnPage;
}
}
私有 bool _IsMsAjaxOnPage = false;
///
/// 此類的目前實例,應始終用於
/// 存取此物件。沒有公共構造函數來
確保引用用作單例。
///
public static ClientScriptProxy Current
{
get
{
return
( HttpContext.Current.Items["__ClientScriptProxy"] ??
(HttpContext.Current.Items["__ClientScriptProxy"] =
new ClientScriptProxy(Hrentpdler.Cur Page. ) )))
作為 ClientScriptProxy;
}
}
///
/// 基本建構子。傳入頁面名稱,以便我們可以取得
/// 股票
///
///
protected ClientScriptProxy(Page CurrentPage)
{
this.clientScript = CurrentPage .ClientScript;
}
///
/// 檢查 MS Ajax 是否已在目前
/// Web 應用程式中註冊。
///
/// 注意:方法是靜態的,因此可以從
/// 任何地方
直接存取///
///
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;
返回假;
}
///
/// 在頁面中註冊客戶端腳本區塊。
///
///
///
/// 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)
;
}
///
/// 註冊一個放置在頁面底部的啟動程式碼片段
///
///
///
///
///
///
public void RegisterStartupScript(Control control, Type type, string key, string script, bool addStartupTags)
{
if (this.IsMsAjax)
{
if (RegisterStartupScriptMethod == null)
RegisterStartupScriptMethod = scriptManagerType.GetMethod("RegisterStartupMethodupScript");
RegisterStartupScriptMethod.Invoke(null, new object[5] { control, type, key, script, addStartupTags });
else
this.clientScript.RegisterStartupScript(
type, key, script, addStartupTags);
}
///
/// 將腳本包含標記註冊到頁面中以取得外部腳本 url
///
///
///
///
///
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);
}
///
/// 將腳本包含標記新增至 WebResource 頁面。
///
///
///
/// 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("GetScriptReUrl");
// 回傳 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 內部,它也可以再次正確呈現。