When building custom server controls and embedding client script, many of the Page.ClientScript object methods take a Type parameter. MSDN isn't terribly clear on what the type is actually used for:
type
The type of the client script resource to register.
Eh – ok, thanks for the clarity of that! The type is the unique identifier for this resource instance and presumably this is a Hashtable key that is used to store and retrieve this entry and ensure that the value stored is unique to the given type.
So the most common thing I’ve done in the past is out of convenience is:
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
This generates your typical Web Resource include code into the generated HTML content and it looks something like this:
<script src="/wwHoverPanel/WebResource.axd?d=vSdukC-ueCbs-z1jHdKPoIlKdvRWRkiakv0HFcGD1dgyVel-hZFiBDH3QXOh46x_30iT5D88KIXXE0qZtkII_5PqqkOj28u2YncW-YHmYWk1&t=633026802933072267" type="text/javascript"></script>
Rolls right off the tounge... the resource URLs are so nasty long because they include the resource id as well as the assembly information that contains the resource.
Seems reasonable so far, right?
Until you start sharing your script resource with multiple components. For example, in the wwHoverPanel library there are 3 different components that all share the script resource above. If I happen to have multiple components on the same page I will end up with several script references in the page. This means at best that script files load more than once and at worst that code will break because there may be code that executes in the script file that expects to load only once.
So why is the second resource getting loaded?
If I have two components using the same script resource and they each load the resource with this.GetType() as the Type parameter, ASP.NET interprets that as two separate instances. You can easily try this out in any page with resources from a component:
protected void Page_Load(object sender, EventArgs e)
{
this.Page.ClientScript.RegisterClientScriptResource(typeof(wwHoverPanel),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
this.Page.ClientScript.RegisterClientScriptResource(typeof(wwMethodCallback),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
}
Which generates two separate <script> tags in the page.
Really what you want in this case is to have a page specific instance of the script so it might seem like this would do the trick:
this.Page.ClientScript.RegisterClientScriptResource(this.Page.GetType(),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
But that won’t work because ClientScript uses the type to figure out the assembly from which to load the resource – Page is almost certainly going to be in System.Web and not your assembly (unless of course you use a page subclass that does).
Rather what’s needed is using a consistent type that exists in the assembly. So if you do:
this.Page.ClientScript.RegisterClientScriptResource(typeof(wwHoverPanel),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
this.Page.ClientScript.RegisterClientScriptResource(typeof(wwHoverPanel),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
(where this would normally be happening inside of two separate controls) you get only a single script reference.
I mention this because it’s just so convenient to think that this.GetType() is the easy way to get a script resource loaded in components. Often times you start out with a control that uses a script resource and you’re not really planning on using that resource elsewhere and so GetType() is Ok. Then later though you end up creating another control and it too uses this resource and you run into this issue. So it’s generally a good idea at least with script resources to use a common type unless you explicitly want to have separate script files loaded (can’t really think of a reason when you’d want that though <s>).
A ControlResource type
In my control libraries I typically add a special class that holds all script resource references in one place and I use the type of that class. In wwHoverPanel the class looks like this:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: TagPrefix("Westwind.Web.Controls", "ww")]
[assembly: WebResource("Westwind.Web.Controls.Resources.wwscriptlibrary.js", "text/javascript")]
[assembly: WebResource("Westwind.Web.Controls.Resources.warning.gif", "image/gif")]
[assembly: WebResource("Westwind.Web.Controls.Resources.info.gif", "image/gif")]
[assembly: WebResource("Westwind.Web.Controls.Resources.close.gif", "image/gif")]
namespace Westwind.Web.Controls
{
/// <summary>
/// Class is used as to consolidate access to resources
/// </summary>
public class ControlResources
{
public const string SCRIPTLIBRARY_SCRIPT_RESOURCE = "Westwind.Web.Controls.Resources.wwscriptlibrary.js";
public const string INFO_ICON_RESOURCE = "Westwind.Web.Controls.Resources.info.gif";
public const string WARNING_ICON_RESOURCE = "Westwind.Web.Controls.Resources.warning.gif";
public const string CLOSE_ICON_RESOURCE = "Westwind.Web.Controls.Resources.close.gif";
/// <summary>
/// Embeds the client script library into the page as a Resource
/// </summary>
/// <param name="page"></param>
public static void LoadwwScriptLibrary(Control control)
{
ClientScriptProxy p = ClientScriptProxy.Current;
control.Page.ClientScript.RegisterClientScriptResource(typeof(ControlResources), ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
}
}
}
This class holds all the meta data and a set of public constants the resource ids in one place so it’s easy to find rather than having this information scattered through various Control source files. Having the public const references is nice so you can get Intellisense for the resource ids and it abstracts the id should you ever end up moving it to a new project or rename it in the future. This has actually come in quite handy for me in several projects where I had to change the root namespace!
So, now when I make calls in my code to include any script resources I use this type as the type for the resource:
this.Page.ClientScript.RegisterClientScriptResource(typeof(ControlResources),
ControlResources.SCRIPTLIBRARY_SCRIPT_RESOURCE);
Now all I have to do is REMEMBER to use the common type <s>...
Other Posts you might also like