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...
Other Posts you might also like