Last night I was working on updating some code in my library that handles script compression in relation to my ClientScriptProxy class. The ClientScriptProxy is a wrapper around ClientScript and ScriptManager that provides access to the right one depending on which is present or as an alternative uses custom script service to serve compressed scripts even in absence of a ScriptManager on a page if ScriptManager is not available (which for me is pretty much always <s>).
One thing I've been doing more of recently is to embed more CSS resources as resources and it would be kind of cool to get compression to work with these resources as well. Previously my script compression code only defaulted to JavaScript resources simply because that is the most common scenario for resources and CSS typically isn't all that large.
But one thing to make this work is to figure out the content type of the resource. 'Standard' .NET resources do not have any associated content type - they are just binary streams that can be read out of an assembly, which in turn makes it a little difficult to dynamically figure out what type of resource you are dealing with. However, ASP.NET's resources - resources that can be also be served through ASP.NET's WebResource mechanism - require additional information in the form of a WebResource attribute that does provide content type information that's tied to the resource name.
So any resource that is to be used by ASP.NET also needs a an assembly level attribute defined once somewhere in the code for the assembly.For example, here are some of my WebResource definitions for the West Wind Ajax toolkit:
[assembly: WebResource("Westwind.Web.Controls.Resources.jquery.js", "application/x-javascript")]
[assembly: WebResource("Westwind.Web.Controls.Resources.ww.jquery.js", "application/x-javascript")]
[assembly: WebResource("Westwind.Web.Controls.Resources.calendar.gif", "image/gif")]
[assembly: WebResource("Westwind.Web.Controls.Resources.ui.datepicker.js", "text/javascript")]
[assembly: WebResource("Westwind.Web.Controls.Resources.ui.datepicker.css", "text/css")]
[assembly: WebResource("Westwind.Web.Controls.Resources.wwscriptlibrary.js", "application/x-javascript")]
Each of thes e attributes matches the actual resource file that is also embedded into the assembly (using Embedded resource as the file type). Resources are named based on the project base namespace (set in the project's property settings) plus the project relative path. So in my project all resources are stored in a /resources folder below the project root with a default namespace of Westwind.Web.Controls.
Normally to access the resources you use the Page.ClientScript or a ScriptManager instance to directly embed the resource into the page or (in the case of CSS typically) you can retrieve the URL to embed into the page:
string resName = "Westwind.Web.Controls.ww.jquery.js";
Page.ClientScript.RegisterClientScriptResource(typeof(ControlResources),resName);
string url = Page.ClientScript.GetWebResourceUrl(typeof(ControlResources), resName);
And that works well enough if that's what you're after.
Retrieving the Resources And the Content Type
In my situation though I'm effectively replacing some of the WebResource functionality specifically for script and css files since I want compression (both Gzip and in the case of script minimizing) but without requiring ScriptManager. So I need to be able to read the resource stream AND also be able to retrieve the content type from the Web Resource (if a Web Resource was passed) at least.
The process of reading the resource and getting the content type are two completely separate operations. As far as I could tell there is no public ASP.NET API that helps with reading Web resources IF you want to read them directly in code.
The process of getting the content type and reading the output looks something like this snippet:
// Look up the WebResource Attribute
WebResourceAttribute[] attr = resourceAssembly.GetCustomAttributes(typeof(WebResourceAttribute),false)
as WebResourceAttribute[];
if (attr != null)
cacheItem.ContentType = attr.Where(at => at.WebResource == resource).FirstOrDefault().ContentType;
// default to javascript
if (cacheItem.ContentType == null)
cacheItem.ContentType = STR_JavaScript_ContentType;
// Load the script file as a string from Resources
string script = "";
using (Stream st = resourceAssembly.GetManifestResourceStream(resource))
{
StreamReader sr = new StreamReader(st, Encoding.Default);
script = sr.ReadToEnd();
}
// Optimize the script by removing comment lines and stripping spaces
// Only applies to JavaScript
if (cacheItem.ContentType == STR_JavaScript_ContentType && !Context.IsDebuggingEnabled)
script = OptimizeScript(script);
// *** Now we're ready to create out output
// *** Don't GZip unless at least 4k
if (useGZip && script.Length > 4096)
{
cacheItem.Content = GZipMemory(script);
cacheItem.IsCompressed = true;
}
else
{
cacheItem.Content = Encoding.UTF8.GetBytes(script);
cacheItem.IsCompressed = false;
}
// *** Add into the cache
Context.Cache.Add(cacheKey, cacheItem, null, DateTime.UtcNow.AddDays(1),
TimeSpan.Zero, CacheItemPriority.High, null);
// *** Write out to Response object with appropriate Client Cache settings
this.SendTextOutput(cacheItem.Content, cacheItem.IsCompressed, cacheItem.ContentType);
Gotta love LINQ for making reflection queries like this easy (thanks to Dimebrain for the nudge via Twitter). So basically I have to retrieve the WebResourceAttributes separately and then retrieve the resource data using standard Assembly.GetManifestResourceStream. Not rocket science but a little concerned about performance since this might get hit fairly frequently. To avoid the repeated resource and reflection hits content - including the content type - is cached in the HttpContext cache which stores a special WebResourceItem as a cache entry that contains the content, the content type and whether the content was gZipped. All those elements need to be available if the content is served again.
This is good to know for other purposes as well. It might also be useful to build an app start up task that can optionally loop through all the resources in a script file and dump out the resources into a file directory of the app and then serve the resources from there - the advantage of this approach is that requests wouldn't have to go through ASP.NET at all.
To Resource or Not to Resource
I've brought up the subject of using Control resources on several occasions and I'm still not convinced of any one particular approach. When I build components that rely on support resources - say a component that relies on jQuery I tend to build resources into the assembly so there are no dependencies with the component loading jQuery, but with options to either specify an external file or not have the component load anything at all.
I think that this approach is pretty much a requirement or otherwise you might run into conflicts of versions or multiple loads of some libraries. Personally in my own apps I have most 'core' resources auto-loaded via resources and only support libraries (say jQUery UI components) loaded manually. Resources have big advantages in that the latest version is always available - no more copying around of resoures and making sure the latest version is loaded in many projects which really can get old.
What's your approach? Always embedding scripts manually even when building components? How do you manage dependencies and keeping scripts updated?
You can find the ScriptCompression and ClientScriptProxy code online in my Subversion repository (Guest and no password)
On a side note: Does anybody know how to configure Apache and Subversion (in a VisualSvn distro) to make anonymous access work and yet still allow authenticated access for updates? I seem to only get one or the other to work using the require configuration switch but not both. I have either open access (for read and write for all) or ALWAYS require a username without anonymous access) .
Other Posts you might also like