Screwing around with WebResources in ASP.NET 2.0
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.
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...
Other Posts you might also like
The Voices of Reason
# re: Screwing around with WebResources in ASP.NET 2.0
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".
# re: Screwing around with WebResources in ASP.NET 2.0
Chip C
# re: Screwing around with WebResources in ASP.NET 2.0
Thanks!
# re: Screwing around with WebResources in ASP.NET 2.0
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.
# re: Screwing around with WebResources in ASP.NET 2.0
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
# re: Screwing around with WebResources in ASP.NET 2.0
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.
# re: Screwing around with WebResources in ASP.NET 2.0
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?
# re: Screwing around with WebResources in ASP.NET 2.0
This is normal and expected behavior. Caching occurs only on normal link hits or postback operations.
# re: Screwing around with WebResources in ASP.NET 2.0
# re: Screwing around with WebResources in ASP.NET 2.0
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
# re: Screwing around with WebResources in ASP.NET 2.0
# re: Screwing around with WebResources in ASP.NET 2.0
# re: Screwing around with WebResources in ASP.NET 2.0
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
# re: Screwing around with WebResources in ASP.NET 2.0
# re: Screwing around with WebResources in ASP.NET 2.0
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
# re: Screwing around with WebResources in ASP.NET 2.0
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.
# re: Screwing around with WebResources in ASP.NET 2.0
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