/// <summary>
/// This is a proxy object for the Page.ClientScript and MS Ajax ScriptManager
/// object that can operate when MS Ajax is not present. Because MS Ajax
/// may not be available accessing the methods directly is not possible
/// and we are required to indirectly reference client script methods through
/// this class.
///
/// This class should be invoked at the Control's start up and be used
/// to replace all calls Page.ClientScript. Scriptmanager calls are made
/// through Reflection
/// </summary>
public class ClientScriptProxy
{
private static Type scriptManagerType = null;
// *** Register proxied methods of ScriptManager
private static MethodInfo RegisterClientScriptBlockMethod;
private static MethodInfo RegisterStartupScriptMethod;
private static MethodInfo RegisterClientScriptIncludeMethod;
private static MethodInfo RegisterClientScriptResourceMethod;
private static MethodInfo RegisterHiddenFieldMethod;
//private static MethodInfo RegisterPostBackControlMethod;
//private static MethodInfo GetWebResourceUrlMethod;
/// <summary>
/// Determines if MsAjax is available in this Web application
/// </summary>
public bool IsMsAjax
{
get
{
if (scriptManagerType == null)
CheckForMsAjax();
return _IsMsAjax;
}
}
private static bool _IsMsAjax = false;
/// <summary>
/// Current instance of this class which should always be used to
/// access this object. There are no public constructors to
/// ensure the reference is used as a Singleton to further
/// ensure that all scripts are written to the same clientscript
/// manager.
/// </summary>
public static ClientScriptProxy Current
{
get
{
return
(HttpContext.Current.Items["__ClientScriptProxy"] ??
(HttpContext.Current.Items["__ClientScriptProxy"] =
new ClientScriptProxy() ) )
as ClientScriptProxy;
}
}
/// <summary>
/// No public constructor - use ClientScriptProxy.Current to
/// get an instance to ensure you once have one instance per
/// page active.
/// </summary>
protected ClientScriptProxy()
{
}
/// <summary>
/// Checks to see if MS Ajax is registered with the current
/// Web application.
///
/// Note: Method is static so it can be directly accessed from
/// anywhere
/// </summary>
/// <returns></returns>
public static bool CheckForMsAjax()
{
// *** Easiest but we don't want to hardcode the version here
// scriptManagerType = Type.GetType("System.Web.UI.ScriptManager, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", false);
// *** To be safe and compliant we need to look through all loaded assemblies
Assembly ScriptAssembly = null; // Assembly.LoadWithPartialName("System.Web.Extensions");
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
string fn = ass.FullName;
if (fn.StartsWith("System.Web.Extensions") )
{
ScriptAssembly = ass;
break;
}
}
if (ScriptAssembly == null)
return false;
//Assembly ScriptAssembly = Assembly.LoadWithPartialName("System.Web.Extensions");
//if (ScriptAssembly != null)
scriptManagerType = ScriptAssembly.GetType("System.Web.UI.ScriptManager");
if (scriptManagerType != null)
{
_IsMsAjax = true;
return true;
}
_IsMsAjax = false;
return false;
}
/// <summary>
/// Registers a client script block in the 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(Control control, Type type, string key, string script, bool addScriptTags)
{
if (this.IsMsAjax)
{
if (RegisterClientScriptBlockMethod == null)
RegisterClientScriptBlockMethod = scriptManagerType.GetMethod("RegisterClientScriptBlock", new Type[5] { typeof(Control), typeof(Type), typeof(string), typeof(string), typeof(bool) });
RegisterClientScriptBlockMethod.Invoke(null, new object[5] { control, type, key, script, addScriptTags });
}
else
control.Page.ClientScript.RegisterClientScriptBlock(type, key, script, addScriptTags);
}
/// <summary>
/// Registers a startup code snippet that gets placed at the bottom of the 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(Control control, Type type, string key, string script, bool addStartupTags)
{
if (this.IsMsAjax)
{
if (RegisterStartupScriptMethod == null)
RegisterStartupScriptMethod = scriptManagerType.GetMethod("RegisterStartupScript", new Type[5] { typeof(Control), typeof(Type), typeof(string), typeof(string), typeof(bool) });
RegisterStartupScriptMethod.Invoke(null, new object[5] { control, type, key, script, addStartupTags });
}
else
control.Page.ClientScript.RegisterStartupScript(type, key, script, addStartupTags);
}
/// <summary>
/// Registers a script include tag into the page for an external script 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", new Type[4] { typeof(Control), typeof(Type), typeof(string), typeof(string) });
RegisterClientScriptIncludeMethod.Invoke(null, new object[4] { control, type, key, url });
}
else
control.Page.ClientScript.RegisterClientScriptInclude(type, key, url);
}
/// <summary>
/// Returns a WebResource or ScriptResource URL for script resources that are to be
/// embedded as script includes.
/// </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",new Type[3] { typeof(Control), typeof(Type), typeof(string) });
RegisterClientScriptResourceMethod.Invoke(null, new object[3] { control, type, resourceName });
}
else
control.Page.ClientScript.RegisterClientScriptResource(type, resourceName);
}
/// <summary>
/// Returns a WebResource URL for non script resources
/// </summary>
/// <param name="control"></param>
/// <param name="type"></param>
/// <param name="resourceName"></param>
/// <returns></returns>
public string GetWebResourceUrl(Control control, Type type, string resourceName)
{
//if (this.IsMsAjax)
//{
// if (GetWebResourceUrlMethod == null)
// GetWebResourceUrlMethod = scriptManagerType.GetMethod("GetScriptResourceUrl");
// return GetWebResourceUrlMethod.Invoke(null, new object[2] { resourceName, control.GetType().Assembly }) as string;
//}
//else
return control.Page.ClientScript.GetWebResourceUrl(type, resourceName);
}
/// <summary>
/// Injects a hidden field into the page
/// </summary>
/// <param name="control"></param>
/// <param name="hiddenFieldName"></param>
/// <param name="hiddenFieldInitialValue"></param>
public void RegisterHiddenField(Control control, string hiddenFieldName, string hiddenFieldInitialValue)
{
if (this.IsMsAjax)
{
if (RegisterHiddenFieldMethod == null)
RegisterHiddenFieldMethod = scriptManagerType.GetMethod("RegisterHiddenField", new Type[3] { typeof(Control), typeof(string), typeof(string) });
RegisterHiddenFieldMethod.Invoke(null, new object[3] { control, hiddenFieldName, hiddenFieldInitialValue });
}
else
control.Page.ClientScript.RegisterHiddenField(hiddenFieldName, hiddenFieldInitialValue);
}
}
The code basically checks to see whether the MS Ajax assembly can be accessed as a type and if so assumes MS Ajax is installed. This is not quite optimal – it’d be better to know whether a ScriptManager is actually being used on the current page, but without scanning through all controls (slow) I can’t see a way of doing that easily.
The control caches each of the MethodInfo structures to defer some of the overhead in making the Reflection calls to the ScriptManager methods. I don’t think that Reflection here is going to cause much worry about overhead unless you have a LOT of calls to these methods (I suppose it’s possible if you have lots of resources – think of a control like FreeTextBox for example). Even then the Reflection overhead is probably not worth worrying about.
To use this class all calls to ClientScript get replaced with call this class instead. So somewhere during initialization of the control I add:
protected override void OnInit(EventArgs e)
{
this.ClientScriptProxy = ClientScriptProxy.Current;
base.OnInit(e);
}
And then to use it:
this.ClientScriptProxy.RegisterClientScriptInclude(this,this.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE,
this.ResolveUrl(this.ScriptLocation));
Notice the first parameter is the control instance (typically this) just like the ScriptManager call, so there will be a slight change of parameters when changing over from ClientScript code.
Once I added this code to my controls the problems with UpdatePanel went away and it started rendering properly again even with the controls hosted inside of the UpdatePanels.
You can grab the code for the client script proxy and sample control code through the wwHoverPanel download.