In den letzten Wochen seit der Einführung der MS Ajax-Beta habe ich eine Reihe von Berichten darüber erhalten, dass das wwHoverPanel-Steuerelement bei der Ausführung in Kombination mit MS Ajax auf einige Probleme gestoßen ist. Die Steuerelemente selbst stören MS AJAX nicht direkt, aber wenn Sie die Steuerelemente in ein AJAX UpdatePanel() stecken, gibt es ein Problem, da der Skriptcode, den die Steuerelemente ausspucken, nicht ordnungsgemäß in den durch Rückruf generierten Updates generiert wird . Auch wenn der Skriptcode fehlt, funktionieren die Steuerelemente weiterhin, weisen jedoch einige unerwartete Verhaltensweisen auf. Beispielsweise verliert ein Hover-Panel, das in einem Update-Panel platziert wird, in vielen Fällen seine Positionierung und erscheint nicht an der aktuellen Mauszeigerposition, sondern am Rand des Container-Steuerelements, in dem es sich befindet.
Das Problem besteht darin, dass Microosft sich in der MS AJAX Beta für eine völlig separate Skriptgenerierungs-Engine entschieden hat, die über das ScriptManager-Steuerelement gesteuert wird. Der MS Ajax ScriptManager ahmt viele Methoden des ClientScript-Objekts nach, stellt sie jedoch als statische Methoden bereit (zum Glück! Ohne das wären wir wirklich am Arsch).
Methoden wie RegisterClientScriptBlock, ResgisterClientScriptResources – alles, was sich mit dem Einfügen von Skriptcode in die Seite befasst, verfügen über entsprechende statische Methoden in ScriptManager. Die ScriptManager-Methoden übergeben das Control als zusätzlichen ersten Parameter, ahmen ansonsten aber den vorhandenen ClientScriptManager nach.
Dieses neue Verhalten setzt jedoch bestehende Steuerelemente in eine Bindung – wenn der Code ClientScriptManager verwendet, kann UpdatePanels den Skriptcode nicht sehen (wenn er in einem Rückruf aktualisiert werden muss). Gleichzeitig kann der Steuerungsentwickler jedoch nicht davon ausgehen, dass der MS Ajax ScriptManager tatsächlich existiert.
Das Endergebnis all dessen ist, dass es nicht ganz einfach ist, mit dieser Nichtübereinstimmung umzugehen, und dass ein Wrapper-Objekt erstellt werden muss, das entscheiden kann, welches Steuerelement verwendet werden soll. Der Wrapper muss sich mit der Entscheidung befassen, ob MS Ajax in der Anwendung verfügbar ist, und wenn ja, mithilfe von Reflection auf den ScriptManager zugreifen und etwaigen Skriptcode schreiben.
Dafür kann ich mich allerdings nicht rühmen: Eilon Lipton hat vor einiger Zeit über dieses Problem gepostet und sein Code war wirklich das, was ich brauchte, um dies auf den Weg zu bringen. Ich habe das Ding einfach in ein ClientScriptProxy-Objekt verpackt, das ich für eine Handvoll verwendet habe von Kontrollen. Ich habe im Grunde eine Handvoll der ClientScript-Methoden hinzugefügt, die ich in meinen Anwendungen verwende. Hier ist die Klasse:
[*** Code aktualisiert: 12.12.2006 aus Kommentaren ***]
/// <Zusammenfassung>
/// Dies ist ein Proxy-Objekt für das Page.ClientScript- und MS Ajax ScriptManager
///-Objekt, das funktionieren kann, wenn MS Ajax nicht vorhanden ist. Da MS Ajax
/// möglicherweise nicht verfügbar ist, ist ein direkter Zugriff auf die Methoden nicht möglich
/// und wir müssen über diese Klasse indirekt auf Client-Skriptmethoden
/// verweisen.
///
/// Diese Klasse sollte beim Start des Controls aufgerufen und
/// verwendet werden, um alle Page.ClientScript-Aufrufe zu ersetzen. Scriptmanager-Aufrufe erfolgen
/// über Reflection
/// </summary>
public class ClientScriptProxy
{
private static Type scriptManagerType = null;
// *** Proxy-Methoden von ScriptManager registrieren
private static MethodInfo RegisterClientScriptBlockMethod;
private static MethodInfo RegisterStartupScriptMethod;
private static MethodInfo RegisterClientScriptIncludeMethod;
private static MethodInfo RegisterClientScriptResourceMethod;
//private static MethodInfo RegisterPostBackControlMethod;
//private static MethodInfo GetWebResourceUrlMethod;
ClientScriptManager clientScript;
/// <summary>
/// Bestimmt, ob MsAjax in dieser Webanwendung verfügbar ist
/// </summary>
public bool IsMsAjax
{
get
{
if (scriptManagerType == null)
CheckForMsAjax();
return _IsMsAjax;
}
}
private static bool _IsMsAjax = false;
public bool IsMsAjaxOnPage
{
get
{
return _IsMsAjaxOnPage;
}
}
private bool _IsMsAjaxOnPage = false;
/// <Zusammenfassung>
/// Aktuelle Instanz dieser Klasse, die immer verwendet werden sollte, um
/// auf dieses Objekt zuzugreifen. Es gibt keine öffentlichen Konstruktoren, die
/// sicherstellen, dass die Referenz als Singleton verwendet wird.
/// </summary>
public static ClientScriptProxy Current
{
get
{
return
( HttpContext.Current.Items["__ClientScriptProxy"] ??
(HttpContext.Current.Items["__ClientScriptProxy"] =
new ClientScriptProxy(HttpContext.Current.Handler as Page )))
als ClientScriptProxy;
}
}
/// <summary>
/// Basiskonstruktor. Übergeben Sie den Seitennamen, damit wir
/// den Bestand
abholen können/// </summary>
/// <param name="CurrentPage"></param>
protected ClientScriptProxy(Page CurrentPage)
{
this.clientScript = CurrentPage .ClientScript;
}
/// <summary>
/// Überprüft, ob MS Ajax bei der aktuellen
/// Webanwendung registriert ist.
///
/// Hinweis: Die Methode ist statisch, sodass von/// überall
direkt auf sie zugegriffen werden kann
/// </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;
return true;
}
_IsMsAjax = false;
return false;
}
/// <summary>
/// Registriert einen Client-Skriptblock auf der Seite.
/// </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 });
}
else
this.clientScript.RegisterClientScriptBlock(type, key, script, addScriptTags);
}
/// <summary>
/// Registriert ein Startcode-Snippet, das unten auf der Seite platziert wird
/// </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>
/// Registriert ein Skript-Include-Tag auf der Seite für eine externe Skript-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 Schlüssel, Zeichenfolgen-URL)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptIncludeMethod == null)
RegisterClientScriptIncludeMethod = scriptManagerType.GetMethod("RegisterClientScriptInclude");
RegisterClientScriptIncludeMethod.Invoke(null, new object[4] { control, type, key, url });
}
else
this.clientScript.RegisterClientScriptInclude( Typ, Schlüssel, URL);
}
/// <summary>
/// Fügt der Seite für WebResource ein Skript-Include-Tag hinzu.
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="resourceName"></ param>
public void RegisterClientScriptResource(Control control, Type type, string resourcesName)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptResourceMethod == null)
RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource");
RegisterClientScriptResourceMethod.Invoke(null, new object[3] { control, type, resourcesName });
}
else
this.clientScript.RegisterClientScriptResource(type,resourceName);
}
öffentliche Zeichenfolge GetWebResourceUrl(Control control, Type type, string resourcesName)
{
//if (this.IsMsAjax)
//{
// if (GetWebResourceUrlMethod == null)
// GetWebResourceUrlMethod = scriptManagerType.GetMethod("GetScriptResourceUrl");
// getWebResourceUrlMethod.Invoke(null, new object[2] { resourcesName, control.GetType().Assembly }) als String zurückgeben;
//
// else
return this.clientScript.GetWebResourceUrl(type, resourcesName);
}
}
Der Code prüft grundsätzlich, ob auf die MS Ajax-Assembly als Typ zugegriffen werden kann, und geht in diesem Fall davon aus, dass MS Ajax installiert ist. Das ist nicht ganz optimal – es wäre besser zu wissen, ob tatsächlich ein ScriptManager auf der aktuellen Seite verwendet wird, aber ohne das Durchsuchen aller Steuerelemente (langsam) sehe ich keine einfache Möglichkeit, dies zu tun.
Das Steuerelement speichert jede MethodInfo-Struktur im Cache, um einen Teil des Overheads bei der Durchführung der Reflection-Aufrufe an die ScriptManager-Methoden zu verzögern. Ich glaube nicht, dass Reflection hier große Sorgen wegen des Overheads bereiten wird, es sei denn, Sie haben VIELE Aufrufe dieser Methoden (ich nehme an, das ist möglich, wenn Sie viele Ressourcen haben – denken Sie zum Beispiel an ein Steuerelement wie FreeTextBox). Selbst dann lohnt es sich wahrscheinlich nicht, sich über den Reflection-Overhead Gedanken zu machen.
Um diese Klasse zu verwenden, werden alle Aufrufe von ClientScript durch den Aufruf dieser Klasse ersetzt. Also füge ich irgendwo während der Initialisierung der Steuerung hinzu:
protected override void OnInit(EventArgs e)
{
this.ClientScriptProxy = ClientScriptProxy.Current;
base.OnInit(e);
}
Und um es dann zu verwenden:
this.ClientScriptProxy.RegisterClientScriptInclude(this,this.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE,
this.ResolveUrl(this.ScriptLocation));
Beachten Sie, dass der erste Parameter die Kontrollinstanz ist (normalerweise diese), genau wie der ScriptManager-Aufruf, sodass es beim Wechsel vom ClientScript-Code zu einer geringfügigen Änderung der Parameter kommt.
Nachdem ich diesen Code zu meinen Steuerelementen hinzugefügt hatte, verschwanden die Probleme mit UpdatePanel und es wurde wieder richtig gerendert, selbst wenn die Steuerelemente in den UpdatePanels gehostet wurden.