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:
Markdown Monster - The Markdown Editor for Windows

Screwing around with WebResources in ASP.NET 2.0


:P
On this page:

I'm reviewing and updating my demos for ASP.NET 2.0 Script Callbacks for ASP.NET Connections today and as I'm looking at requests firing with Fiddler I'm noticing that the script resource that links in the generic JavaScript support is reloaded on every ASPX page hit to a page that uses the scripts. The call to WebResource.axd is used to pull in the script code required to make ScriptCallbacks work and it's not exactly small at 21k.


For the various demos ScriptCallback pages I have the WebResource.axd link Is the same and it returns the same byte count, so I assume it's identical, yet the file is not cached. Different browsers, no difference. Other page resources like style sheets are cached just fine.

 

The file is sent with a Cache-Control: private header, which is meant to keep the file from caching.

 

It seems the JavaScript file should be cached since the JavaScript at first site at least appears to be the same each time. After all, aren't Web Resources supposed to be retrieved as compiled resource content, so that content should be always be static?

 

As it turns out ASP.NET is using the Cache-Control: private header only if the project is set into Debug mode. When you switch into non-debug mode changes to Cache-Control: public which indeed tries to cache the resource requests. If set Debug to false in Web.config I only see the first hit to the resource unless I'm doing an explicit Browser Refresh on the page, which is the behavior you'd expect.

 

Embedding your own Resources for use with WebResource.axd 

Speaking of WebResources in ASP.NET 2.0, I had a hell of a time to get a .js file embedded as a Web Resource today. To embed a resource you follow these steps:

 

I ran through the following steps:

 

  • Add the .js file or resource to your Class Project
  • Select Properties and select Build Action Embed as a Resource
  • Add code to embed the reference to the WebResource in the page:
    if (this.ScriptLocationType == JavaScriptCodeLocationTypes.WebResource)

{

    string Script = this.Page.ClientScript.GetWebResourceUrl(typeof(wwHoverPanel), "Westwind.Web.Controls.wwHoverPanel.js");

    this.Page.ClientScript.RegisterClientScriptInclude("wwHoverPanel", Script);

}

  • Add the WebResource attribute to AssemblyInfo.cs
    [assembly: System.Web.UI.WebResource("Westwind.Web.Controls.wwHoverPanel.js","text/js")]


I had a hell of a time to make these seemingly simple steps work however. When I ran this code ASP.NET kept kicking back a 404 error with a message stating that the resource could not be found. The original problem was that I didn't reference to resource correctly in both the script reference and the WebResource assignment: Notice that the name of the resource is the name of the default namespace + the filename. That's the namespace for the project which is set in the Project Property Sheet.

 

My problem was that at first I didn't specify any namespace, and then I incorrectly used a namespace that is actually used by the controls (Westwind.Web.Controls) without realizing that the project's default is used for the prefix. When I finally realized the problem,  renaming the namespace and building the project didn't update the resource name either because the resource itself hadn't changed VS failed to recompile the resources.

 

Not until I took out Reflector and checked the resource name actually embedded did I realize that the resource name wasn't updating until I did a full rebuilt the entire project - the incremental build didn't catch the project setting change. Grrrr….

 

It sure would be nice if ASP.NET could be a little more helpful in the wrong reference scenario. Specifically if I have a missing resource or an invalidly referenced resource, it'd be nice if the resource compiler caught the problem at compile time instead of getting a runtime error that leaves little clue on what's actually wrong.

 

In the end I managed to get this to work just fine.

 

I find that working with embedded resources is OK, but during development it's real nice to just have a JS file you can edit as part of your Web project so you don't have to stop debugging as you do when you recompile your project.

 

For this control I actually have 3 operational modes:

 

EmbeddedInPage

ExternalUrl    

WebResource

 

The first approach actually embeds the script code directly into the loaded page. The advantage of this is that it's fully self contained, there are no external links and you can easily see everything related to the page in one place. For demo'ing code and debugging this is often the best way to work. The downside here is that there's absolutely no caching of the content in the js file.

 

An external .js file is nice because you can edit the file without having to recompile the project to get the resource updated. The external js file is the most efficient option from an application perspective since it doesn't hit ASP.NET at all, but rather lets IIS handle the file retrieval and caching. Since the file is static IIS can agressively cache this file in the kernel cache.

 

Finally a WebResource is usually the best choice for deployed applications as it allows you to have a self contained control that doesn't need to ship any support files, but it has little more overhead than a JS file. Another advantage of the WebResource is that when Debug is on ASP.NET can reload the resource each time so any change made to the resource is immediately 'uncached' as the WebResource URL is changed when the control is changed, guaranteeing you're seeing the latest version without an explicit browser refresh.

 

I have all of this handled through a couple of properties on the control, ScriptLocation and ScriptLocationUrl, the latter of which is only used if an external file is used.

 

If you do this you probably want to have a single source for the Javascript file so it's best to embed the string for the control from the embedded resource at runtime, rather than embedding the script code as string reference (which is a pain because of the string formatting issues). You can use code like the following to embed the JavaScript into the page from a Resource:

private const string JAVASCRIPT_RESOURCE = "Westwind.Web.Controls.Resources.wwHoverPanel.js";

...

// *** Load the wwHoverPanel.js file from Resources for embedding

Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(JAVASCRIPT_RESOURCE);

StreamReader sr = new StreamReader( st );

 

this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
                                             "wwHoverPanel_Generic", sr.ReadToEnd(), true);

 

So now all three modes can use the same .js file content...

 


The Voices of Reason


 

Chris D
January 30, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Hey Rick, we just went through similar pains getting the WebResources up and running in our product during the 2k5 migration. We however ran in to an additional problem that you may not have encountered yet.

this.GetType() only works if it is not specified in an inherited class.

ie:
class PageOverrideClass defines and registers a webresource.

class TestPage inherits PageOverrideClass

If PageOverrideClass uses this.GetType() the WebResource request will fail returning a 404 error. Instead you must use typeof(PageOverrideClass), which will return the expected results.

So it may be safer to just always use the typeof() syntax with the type that statement exists in.

Chris D

Wilco Bauwer
January 30, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Chris: that's actually an interesting issue. The reason that doesn't work probably has to do with the fact that your TestPage class lives in a different assembly. Subsequently, GetWebResourceUrl will generate a URL that links to the assembly of the derived type.

Your suggestion to use typeof(x) seems like a reasonable suggestion.

Rick: besides the default namespace + filename, it should also include the directory name relative to the project's root. For example, if you've got a project with a default namespace "WestWind.Web.Controls", and you defined a web resource called "wwHoverPanel.js" inside a directory "Resources", the path becomes "WestWind.Web.Controls.Resources.wwHoverPanel.js".

Chip Crary
January 30, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Rick, its nice to know that I wasn't the only one banging my head against this resource think. Thanks blogging all this stuff, its a real help.

Chip C

Frank L
February 06, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Hello Rick, I am having a different problem: (forgive me if it sounds silly) I cannot find "Build Action" in the property page of js file, all I see is File Name and File Path, I thought "Build Action" is missing in VS2005. How did you make it show up?

Thanks!

Rick Strahl
February 06, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Frank, what kind of project are you working on? The Build Action option doesn't exist in Web projects, only in regular projects. So in my case the control lives in a separate class project.

I tried to embed a resource from a Web project using the Global and Local resources, but that didn't work, mainly because I didn't know how to reference the resource and there's no way to just check an assembly for the resource name.

Frank L
February 06, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Thanks Rick for you prompt response!

I am working on a web project, so it seems I have to create another assembly for the sole purpose of the single js file. I hate this

Rick Strahl
February 06, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Frank there may be a way to do this, but you'll have to figure out how to get at the resource name. If you have embedded resources it seems to make sense to create an assembly for whatever components are using these resources.

If you have a Web Project anyway, then including the JS file shouldn't be a problem? The feature is really geared at control developers, so that they don't force users into shipping and deploying a JS file in a particular place.

Worawee Sattayavinij
February 07, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Hi Rick,

I have a problem with caching the WebResource.axd content. I have tried to follow your suggestion by changing the web.config to debug="false" already.

In fiddler, I checked the response from the server. It had correct cache-control in the header. It looks like this

HTTP/1.1 200 OK
Connection: close
Date: Tue, 07 Feb 2006 18:07:46 GMT
Server: Microsoft-IIS/6.0
MicrosoftOfficeWebServer: 5.0_Pub
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: public
Expires: Wed, 07 Feb 2007 18:07:39 GMT
Content-Type: text/javascript
Content-Length: 598

but then I hit F5 on the browser. The reponse from the server was still the same. The content is not cached.

I think the reponse code should be returned as
HTTP/1.1 304 Not Modified

I don't know what's wrong with it. Could you please help or any suggestion?

Rick Strahl
February 07, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

When you press F5 in the browser, the browser forces all cached content to be reloaded. The same is true of static CSS files for example. If you make a change to a CSS file you will not see the change until you press F5 at which point the browser refreshes the current content and all loaded content from disk.

This is normal and expected behavior. Caching occurs only on normal link hits or postback operations.

Worawee Sattayavinij
February 07, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Thank you so much for the quick response and your answer solves my problem and the about about the browser behaviour.

Mark Chipman
February 15, 2006

# re: Screwing around with WebResources in ASP.NET 2.0

Just came across this link:

http://www.codeproject.com/aspnet/MyWebResourceProj.asp

I hope it might help someone out there trying to get webresources working with ASP.Net.

Regards,

Mark Chipman

OCEG Technology Blog
October 05, 2006

# OCEG Technology Blog: ASP.NET 2.0 WebResource.axd and browser caching


Custom Server Controls
November 03, 2006

# ASP.NET Forums - How to force...

# DotNetSlackers: Screwing around with WebResources in ASP.NET 2.0


Client Side Web Development
December 05, 2006

# ASP.NET Forums - Interesting WebResouce.axd cache behavior.


Sergei Shelukhin
February 28, 2007

# re: Screwing around with WebResources in ASP.NET 2.0

I get cache-control: private even though I have build the project in release configuration and web.config is set to debug="false"

Pingback
April 16, 2007

# WebResources aren't cached when debug is set to true in your web.config


Havagan's Mind Spew
May 23, 2007

# Havagan’s Mind Spew » Blog Archive » FlaDotNet Code Camp 2006


ASP.NET Forums
May 31, 2007

# Interesting WebResouce.axd cache behavior. - ASP.NET Forums


fabal
June 04, 2007

# re: Screwing around with WebResources in ASP.NET 2.0

You mention that F5 should reload all cached content. I thought that this was the function of Ctrl+F5. Can you tell me what the difference is between F5 and Ctrl+F5.

ASP.NET
June 30, 2007

# Developers.pl - Callback`s w .NET - nieco głębsze ujęcie :)

Portal społeczności developerów .NET w Polsce, .NET, developer, developers, developers.pl, programowanie, dotnet, portal, społeczność, community, c#, Visual Basic, CLR, .NET Framework, SQL, SQL Server, T-SQL, Transact SQL, IDE, Visual Studio, Visual Studio .NET, Microsoft, MS, strony internetowe, ASP, ASP.NET, ASPX, ASMX, Web Service, Web Services, Remoting, kontrolki, controls, control, user control, design, component, components

John Hann
September 27, 2007

# re: Screwing around with WebResources in ASP.NET 2.0

Hi Rick,

I'm running into an issue where I get no-cache headers on all WebResource.axd and ScriptResource.axd content. I have debug=false in my web.config, and I'm not hitting refresh in the browser to test this, but clicking between pages. Still, everything loaded from these handlers gets reloaded each time I load a page. Have you run into this? I've tried everything I can think of, and am still completely stuck. If it helps, my ScriptManager is in a master page, and I've explicitly set its ScriptMode="Release".

Thanks,
John

mclabman
May 13, 2008

# re: Screwing around with WebResources in ASP.NET 2.0

Tell me master how to get web reference to recource in .resx file. Is it possible?

Aidan Orton
February 04, 2009

# re: Screwing around with WebResources in ASP.NET 2.0

Hi Rick

Regularly find your pages a great help and I have to give thanks to Chris D, as I've been having issues with registering a webresource on an inherited class.
For us dimwits who are still confined to using VB, here's the VB equivalent of a couple of lines your code:
Dim Script As String = Me.Page.ClientScript.GetWebResourceUrl(GetType(wwHoverPanel), "Westwind.Web.Controls.wwHoverPanel.js")
Me.Page.ClientScript.RegisterClientScriptInclude("wwHoverPanel", Script)

I'd also add a "If Me.Page.ClientScript.IsClientScriptRegistered(..." to prevent multiple registrations.

Aidan

Darren
December 29, 2009

# re: Screwing around with WebResources in ASP.NET 2.0

Excellent post, thanks for the information.

for Aidan... from the help files...

"A client script include is uniquely identified by its key and its type. Scripts with the same key and type are considered duplicates. Only one script with a given type and key pair can be registered with the page. Attempting to register a script that is already registered does not create a duplicate of the script."

The routine "IsClientScriptRegistered" is most likely used internally to other script registration routines, and set as public simply because there is no reason to hide it. If you add the code line it's doing the check twice, not that it would have any noticible effect.

Darren
December 29, 2009

# re: Screwing around with WebResources in ASP.NET 2.0

Please note also for VB developers, the assembly level attribute file naming is case sensitive.

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