A while back I had posted a ClientScript Manager wrapper that is able to detect whether the MS Ajax extensions are loaded and if so uses the MS Ajax ScriptManager instead of the standard Page.ClientScript to handle embedding of script resources into the page. As you probably know the UpdatePanel control requires that any controls write their output to the new ScriptManager rather than the to the Page.ClientScript object in order for MS Ajax to detect when script, resources and other page related objects are embedded into the page.
To make this a little more complicated than it sounds a custom control can’t make any assumptions about MS Ajax being available or even referenced in the application so you can’t actually reference the new ScriptManager directly and instead have to check for the existence first and then use Reflection to make the calls. So this class acts as a go between that checks whether MS Ajax is available and if it is calls ScriptManager, otherwise calls standard Page.ClientScript.
The idea is that a control developer can add an instance of this class and then simply use it to make the ClientScript calls on it instead. Note that I didn't implement all of the ClientScript methods - only a handful of the ones that I actually use frequently. I've been adding additional methods as needed. <s> It should be easy enough to add any others that need to go to the ScriptManager.
I originally posted this tool with Beta 2 updated it for the RC and now it requires yet another set of changes for the final release version of MS Ajax RTM. Actually one of the commenters on the last post (SpeedNet – thanks man!) pointed out a couple of issues that I originally dismissed because I had overlooked the latest update of RC1 which introduced a few more changes that I hadn’t updated for yet. So rather than keep fixing that particular post I’m reposting it here for RTM because there have been a number of changes.
First it includes a new instance method to check whether a ScriptManager is loaded onto a page. There are really two things that you need to check for:
- Whether MS Ajax and the ScriptManager is available in the app (as Reference)
- Whether ScriptManager is actually on the page and used for Script Compression
The first is useful for generic checking – basically when MS Ajax is loaded you generally just want to fire away at the ScriptManager rather than using ClientScript. In some situations though it’s useful to know whether there’s actually a ScriptManager on the current page (you can check on ScriptManager class with the static GetCurrent() method). For my scenario specifically I have a custom script compression module I run for compressing my own scripts and it needs to know whether MS Ajax is actually running and enabled. MS Ajax will do script compression as well, but only if the a scriptmanager is actually on the page. So if it’s not on the page I continue to compress script resources using my own module compression.
It’s a shame that ScriptManager doesn’t actually do things resource compression unless the ScriptManager control is on the page. It seems that it should be perfectly capable of doing this without a SM on the page – there should be no dependencies since RegisterClientScriptResource only registers the resource URL and a separate handler serves it. But alas it doesn’t so I rolled my own.
As far as I can tell I think that the static ScriptManager script functions don’t do anything different than pass through to the ClientScript manager when a ScriptManager is not present.
So anyway here’s the updated ClientScriptProxy class:
/// <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 GetCurrentMethod;
//private static MethodInfo RegisterPostBackControlMethod;
//private static MethodInfo GetWebResourceUrlMethod;
/// <summary>
/// Internal global static that gets set when IsMsAjax() is
/// called. The result is cached once per application so
/// we don't have keep making reflection calls for each access
/// </summary>
private static bool _IsMsAjax = false;
/// <summary>
/// Flag that determines whether check was previously done
/// </summary>
private static bool _CheckedForMsAjax = false;
/// <summary>
/// Cached value to see whether the script manager is
/// on the page. This value caches here once per page.
/// </summary>
private bool _IsScriptManagerOnPage = false;
private bool _CheckedForScriptManager = 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. If you use the IsMsAjax property to check the
/// value this method fires only once per application.
/// </summary>
/// <returns></returns>
public static bool IsMsAjax()
{
if (_CheckedForMsAjax)
return _IsMsAjax;
// *** 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;
scriptManagerType = ScriptAssembly.GetType("System.Web.UI.ScriptManager");
if (scriptManagerType == null)
{
_IsMsAjax = false;
_CheckedForMsAjax = true;
return false;
}
// *** Method to check for current instance on a page - cache
// *** since we might call this frequently
GetCurrentMethod = scriptManagerType.GetMethod("GetCurrent");
_IsMsAjax = true;
_CheckedForMsAjax = true;
return true;
}
/// <summary>
/// Checks to see if a script manager is on the page
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public bool IsScriptManagerOnPage(Page page)
{
// *** Check is done only once per page
if (this._CheckedForScriptManager)
return _IsScriptManagerOnPage;
// *** Must check whether MS Ajax is available
// *** at all first. Method sets up scriptManager
// *** and GetCurrentMethod on success.
if (!IsMsAjax())
{
this._CheckedForScriptManager = true;
this._IsScriptManagerOnPage = false;
return false;
}
// *** Now check and see if we can get a ref to the script manager
object sm = GetCurrentMethod.Invoke(null, new object[1] { page });
if (sm == null)
this._IsScriptManagerOnPage = false;
else
this._IsScriptManagerOnPage = true;
this._CheckedForScriptManager = true;
return this._IsScriptManagerOnPage;
}
/// <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.IsScriptManagerOnPage(control.Page) )
{
// *** NOTE: If MS Ajax is referenced, but no scriptmanager is on the page
// script no compression will occur. With a script manager
// on the page compression will be handled by MS Ajax.
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 });
}
//// *** If wwScriptCompression Module through Web.config is loaded use it to compress
//// *** script resources by using wcSC.axd Url the module intercepts
//if (wwScriptCompressionModule.wwScriptCompressionModuleActive)
//{
// if (type.Assembly == this.GetType().Assembly)
// RegisterClientScriptInclude(control, type, resourceName, "wwSC.axd?r=" +
// Convert.ToBase64String(Encoding.ASCII.GetBytes(resourceName)));
// else
// RegisterClientScriptInclude(control, type, resourceName, "wwSC.axd?r=" +
// Convert.ToBase64String(Encoding.ASCII.GetBytes(resourceName)) +
// "&t=" +
// Convert.ToBase64String(Encoding.ASCII.GetBytes(type.Assembly.FullName)));
//}
else
// *** Otherwise just embed a script reference into the page
control.Page.ClientScript.RegisterClientScriptResource(type, resourceName);
}
/// <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 (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 (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 (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 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)
{
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 (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);
}
}
You can also pick up the source code as part of the free wwHoverPanel library which includes the ClientScriptProxy.cs file... Note there’s one external dependency in the download code on the wwScriptCompressionModule which is commented out in the code above. That’s the hook I use to hook my own script resources to my own script resource url. More on that in a couple of days...
Other Posts you might also like