Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

RegisterClientScriptResource and the Type Parameter


:P
On this page:

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&amp;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>...

Posted in ASP.NET  Ajax  

The Voices of Reason


 

Joel Rumerman
December 26, 2006

# re: RegisterClientScriptResource and the Type Parameter

Rick,

Fantastic. Thanks for explaining something that I had working, but had no idea why.

- Joel

Simone Busoli
December 27, 2006

# re: RegisterClientScriptResource and the Type Parameter

Rick, I would say that this is a bug in the RegisterClientScriptResource method, because an embedded resource is bound necessarily to a single assembly, so there's no reason why the same resource gets registered twice if you call that method passing the same resource name and the type of two classes which belong to the same assembly. It's a nonsense to me.

Rick Strahl
December 28, 2006

# re: RegisterClientScriptResource and the Type Parameter

Simone - I agree. I can't think of a single reason why you'd want to have two different instances of a script resource loaded and so the type parameter seems complete redundant except that ASP.NET needs it to figure out the assembly from which to get the resources.

I suppose that's why this is easy to miss <s>... I know I've made this mistake more than a few times.

Simone Busoli
December 28, 2006

# re: RegisterClientScriptResource and the Type Parameter

Unluckily using a disassembler you notice that the Type parameter is used to generate a unique key for the script being registered, that's why the same script gets registered twice using two different class names. I think the method signature should eventually be changed to accept a string key which uniquely identifies the resource, in case someone really needed that registered twice, just like the other RegisterClientScriptxxxxx do.
Interesting post btw.

Peter
January 23, 2007

# re: RegisterClientScriptResource and the Type Parameter

I just made a BaseWebControl class and put all of the resource code there, using typeof(BaseWebControl)-- no need to remember to use it as long as all of your custom controls inherit from it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

// Web Resources
[assembly: WebResource("Framework.WebControls.Resources.builder.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.controls.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.dragdrop.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.effects.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.prototype.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.scriptaculous.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.slider.js", "text/javascript")]
[assembly: WebResource("Framework.WebControls.Resources.unittest.js", "text/javascript")]

namespace Framework.WebControls
{
public enum WebResource
{
Prototype,
Scriptaculous,
Builder,
Controls,
DragDrop,
Effects,
Slider,
UnitTest
}

[DefaultProperty("Text")]
[ToolboxData("<{0}:BaseWebControl runat=server></{0}:BaseWebControl>")]
public class BaseWebControl : WebControl
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? String.Empty : s);
}

set
{
ViewState["Text"] = value;
}
}

protected override void RenderContents(HtmlTextWriter output)
{
output.Write(Text);
}

protected override void OnPreRender(EventArgs e)
{
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.prototype.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.scriptaculous.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.builder.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.controls.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.dragdrop.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.effects.js");
Page.ClientScript.RegisterClientScriptResource(typeof(BaseWebControl), "Framework.WebControls.Resources.unittest.js");

base.OnPreRender(e);
}
}
}


Peter

Bryan D
December 10, 2009

# re: RegisterClientScriptResource and the Type Parameter

Rick,

I've looked through a few of your code examples and really appreciate your notion of clever and simple ways to get past an obstacle. I've been trying to find the answer to the issue you solve in this article for roughly 8 hours. Perhaps my ability to find resource answers is less honed in than some others; however, I imagine that I am not the only person who has had a tough time finding a solution to this issue.

I was trying to load a javascript library reference into an .aspx page using the web resource(.axd) handler to find the embedded resource (javascript file) within an assembly containing custom controls. However, the page would simply not load the library initially. I looked through a number of forums and my code seemed correct. You helped me finally clear the confusion and I wanted to pass my thanks along to you.

The cause of the whole issue was my improper understanding and use of the 'type' argument as the first argument of the RegisterClientScriptInclude method.
Page.ClientScripts.RegisterClientScriptInclude(Type type, string key, string url)


The Keynote: I now clearly understand that the ASP.NET framework uses the first argument/type definition to determine which assembly (not the type of the resource per se) the web resource/embedded resource exists within.

(My Confused Code Example):
Page.ClientScript.RegisterClientScriptInclude(this.GetType(), "TestScript", "MyNamespace.Scripts.test.js");

This was in the code-behind for an .aspx page. So, by using 'this.GetType()', I was instructing ASP.NET to look for the web resource within the System.Web assembly and not my assembly/class library because in the context 'this' refers to the ASP.NET Page class instance. I needed to provide a type that is defined within my assembly as I now finally understand.

(Code that Works):
Page.RegisterClientScriptInclude(typeof(MyAssemblyType),
"TestScript", "MyNamespace.Scripts.test.js")

Here, the "MyAssemblyType" is a class type that exists within the assembly where the "test.js" script file exists (which is not the System.Web assembly..lol).

Mike
July 26, 2013

# re: RegisterClientScriptResource and the Type Parameter

I have been searching and reading about javascript resource embedding and you are the first person i have seen to explaing this (not even MS does). Have been trying to build a control to embed jquery and noticed that there was indeed serveral reference includes for each type that used the libray resource. Thanks

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024