Au cours des dernières semaines, depuis le lancement de la version bêta de MS Ajax, j'ai reçu un certain nombre de rapports indiquant que le contrôle wwHoverPanel rencontrait des problèmes lors de son exécution en combinaison avec MS Ajax. Les contrôles eux-mêmes n'interfèrent pas directement avec MS AJAX, mais si vous collez les contrôles à l'intérieur d'un AJAX UpdatePanel(), il y a un problème car le code de script que les contrôles crachent n'est pas correctement généré dans les mises à jour générées par rappel. . Avec le code de script manquant, les contrôles fonctionnent toujours mais présentent des comportements inattendus. Par exemple, un panneau de survol placé dans un panneau de mise à jour perdra son positionnement dans de nombreux cas et au lieu d'apparaître à la position actuelle du curseur de la souris, il apparaîtra à la bordure du contrôle conteneur dans lequel il réside.
Le problème est que Microosft a décidé dans MS AJAX Beta d'utiliser un moteur de génération de script complètement séparé qui est piloté via le contrôle ScriptManager. Le MS Ajax ScriptManager imite de nombreuses méthodes de l'objet ClientScript, mais les fournit sous forme de méthodes statiques (heureusement ! sans cela, nous serions vraiment foutus).
Ainsi, des méthodes telles que RegisterClientScriptBlock, ResgisterClientScriptResources – tout ce qui concerne l'insertion du code de script dans la page ont des méthodes statiques associées dans ScriptManager. Les méthodes ScriptManager transmettent le Control en tant que premier paramètre supplémentaire mais imitent par ailleurs le ClientScriptManager existant.
Ce nouveau comportement place cependant les contrôles existants dans une liaison : si le code utilise ClientScriptManager, UpdatePanels ne pourra pas voir le code du script (s'il doit être mis à jour lors d'un rappel). Mais en même temps, le développeur du contrôle ne peut pas supposer que MS Ajax ScriptManager existe réellement.
Le résultat final de tout cela est qu'il n'est pas tout à fait simple de gérer cette inadéquation et ce qui doit se produire, c'est qu'un objet wrapper doit être créé pour pouvoir décider quel contrôle utiliser. Le wrapper doit décider si MS Ajax est disponible dans l'application et si c'est le cas, utiliser Reflection pour accéder au ScriptManager afin d'écrire n'importe quel code de script.
Cependant, je ne peux pas m'en attribuer le mérite : Eilon Lipton a publié un article sur ce problème il y a quelque temps et son code était vraiment ce dont j'avais besoin pour que cela démarre, j'ai juste enveloppé le tout dans un objet ClientScriptProxy que j'ai utilisé sur une poignée de contrôles. J'ai essentiellement ajouté une poignée de méthodes ClientScript que j'utilise dans mes applications. Voici la classe :
[*** code mis à jour : 12/12/2006 à partir des commentaires *** ]
/// <summary>
/// Il s'agit d'un objet proxy pour l'objet Page.ClientScript et MS Ajax ScriptManager
/// qui peut fonctionner lorsque MS Ajax n'est pas présent. Étant donné que MS Ajax
/// peut ne pas être disponible, l'accès direct aux méthodes n'est pas possible
/// et nous devons référencer indirectement les méthodes de script client via
/// cette classe.
///
/// Cette classe doit être invoquée au démarrage du Contrôle et être utilisée
/// pour remplacer tous les appels Page.ClientScript. Les appels de Scriptmanager sont effectués
/// via Reflection
/// </summary>
public class ClientScriptProxy
{
private static Type scriptManagerType = null;
// *** Enregistre les méthodes mandatées de ScriptManager
private static MethodInfo RegisterClientScriptBlockMethod ;
privé statique MethodInfo RegisterStartupScriptMethod ;
privé statique MethodInfo RegisterClientScriptIncludeMethod ;
privé statique MethodInfo RegisterClientScriptResourceMethod ;
//MethodInfo statique privé RegisterPostBackControlMethod;
// MethodInfo statique privée GetWebResourceUrlMethod ;
ClientScriptManager clientScript ;
/// <summary>
/// Détermine si MsAjax est disponible dans cette application Web
/// </summary>
public bool IsMsAjax
{
get
{
if (scriptManagerType == null)
CheckForMsAjax();
retourner _IsMsAjax ;
}
}
bool statique privé _IsMsAjax = false;
public bool IsMsAjaxOnPage
{
get
{
return _IsMsAjaxOnPage ;
}
}
private bool _IsMsAjaxOnPage = false;
/// <summary>
/// Instance actuelle de cette classe qui doit toujours être utilisée pour
/// accéder à cet objet. Il n'y a aucun constructeur public pour
/// garantir que la référence est utilisée comme Singleton.
/// </summary>
public static ClientScriptProxy Current
{
get
{
return
( HttpContext.Current.Items["__ClientScriptProxy"] ??
(HttpContext.Current.Items["__ClientScriptProxy"] =
new ClientScriptProxy(HttpContext.Current.Handler as Page )))
en tant que ClientScriptProxy ;
}
}
/// <résumé>
/// Constructeur de base. Transmettez le nom de la page afin que nous puissions récupérer
/// le stock le
/// </summary>
/// <param name="CurrentPage"></param>
protected ClientScriptProxy(Page CurrentPage)
{
this.clientScript = CurrentPage .ClientScript ;
}
/// <summary>
/// Vérifie si MS Ajax est enregistré avec l'
application Web /// actuelle.
///
/// Remarque : la méthode est statique, elle est donc accessible directement depuis
/// n'importe où
/// </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;
renvoie vrai ;
}
_IsMsAjax = faux ;
renvoie faux ;
}
/// <summary>
/// Enregistre un bloc de script client dans la page.
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="key"></ param>
/// <param name="script"></param>
/// <param name="addScriptTags"></param>
public void RegisterClientScriptBlock (Contrôle de contrôle, type de type, clé de chaîne, script de chaîne, bool addScriptTags )
{
if (this.IsMsAjax)
{
if (RegisterClientScriptBlockMethod == null)
RegisterClientScriptBlockMethod = scriptManagerType.GetMethod("RegisterClientScriptBlock");
RegisterClientScriptBlockMethod.Invoke(null, new object[5] { contrôle, type, clé, script, addScriptTags });
}
sinon
this.clientScript.RegisterClientScriptBlock(type, clé, script, addScriptTags);
}
/// <summary>
/// Enregistre un extrait de code de démarrage qui est placé en bas de la page
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="key"></param>
/// <param name="script"></param>
/// <param name="addStartupTags" ></param>
public void RegisterStartupScript (Contrôle de contrôle, type de type, clé de chaîne, script de chaîne, bool addStartupTags)
{
if (this.IsMsAjax)
{
if (RegisterStartupScriptMethod == null)
RegisterStartupScriptMethod = scriptManagerType.GetMethod("RegisterStartupScript");
RegisterStartupScriptMethod.Invoke(null, nouvel objet[5] { contrôle, type, clé, script, addStartupTags });
}
sinon
this.clientScript.RegisterStartupScript(type, clé, script, addStartupTags);
}
/// <summary>
/// Enregistre une balise d'inclusion de script dans la page pour une URL de script externe
/// </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 clé, chaîne url)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptIncludeMethod == null)
RegisterClientScriptIncludeMethod = scriptManagerType.GetMethod("RegisterClientScriptInclude");
RegisterClientScriptIncludeMethod.Invoke(null, new object[4] { contrôle, type, clé, url });
}
sinon
this.clientScript.RegisterClientScriptInclude( type, clé, url);
}
/// <summary>
/// Ajoute une balise d'inclusion de script dans la page pour WebResource.
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="resourceName"></ param>
public void RegisterClientScriptResource(Contrôle de contrôle, type de type, chaîne resourceName)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptResourceMethod == null)
RegisterClientScriptResourceMethod = scriptManagerType.GetMethod("RegisterClientScriptResource");
RegisterClientScriptResourceMethod.Invoke(null, new object[3] { control, type, resourceName });
}
sinon
this.clientScript.RegisterClientScriptResource(type,resourceName);
}
chaîne publique GetWebResourceUrl (Contrôle de contrôle, type de type, chaîne nom de ressource)
{
//if (this.IsMsAjax)
//{
// if (GetWebResourceUrlMethod == null)
// GetWebResourceUrlMethod = scriptManagerType.GetMethod("GetScriptResourceUrl");
// renvoie GetWebResourceUrlMethod.Invoke(null, new object[2] { ResourceName, control.GetType().Assembly }) sous forme de chaîne ;
//}
// sinon
renvoie this.clientScript.GetWebResourceUrl(type, resourceName);
}
}
Le code vérifie essentiellement si l'assembly MS Ajax est accessible en tant que type et, si tel est le cas, suppose que MS Ajax est installé. Ce n'est pas tout à fait optimal – il serait préférable de savoir si un ScriptManager est réellement utilisé sur la page actuelle, mais sans parcourir tous les contrôles (lent), je ne vois pas comment le faire facilement.
Le contrôle met en cache chacune des structures MethodInfo pour différer une partie de la surcharge liée aux appels Reflection aux méthodes ScriptManager. Je ne pense pas que Reflection ici va causer beaucoup de soucis de surcharge à moins que vous n'ayez BEAUCOUP d'appels à ces méthodes (je suppose que c'est possible si vous avez beaucoup de ressources - pensez à un contrôle comme FreeTextBox par exemple). Même dans ce cas, la surcharge de réflexion ne vaut probablement pas la peine de s'inquiéter.
Pour utiliser cette classe, tous les appels à ClientScript sont remplacés par l'appel de cette classe. Donc quelque part lors de l'initialisation du contrôle j'ajoute :
protected override void OnInit(EventArgs e)
{
this.ClientScriptProxy = ClientScriptProxy.Current;
base.OnInit(e);
}
Et puis pour l'utiliser :
this.ClientScriptProxy.RegisterClientScriptInclude(this,this.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE,
this.ResolveUrl(this.ScriptLocation));
Notez que le premier paramètre est l'instance de contrôle (généralement celle-ci), tout comme l'appel de ScriptManager, il y aura donc un léger changement de paramètres lors du passage du code ClientScript.
Une fois que j'ai ajouté ce code à mes contrôles, les problèmes avec UpdatePanel ont disparu et le rendu a recommencé à s'effectuer correctement, même avec les contrôles hébergés à l'intérieur de UpdatePanels.