Embedding JavaScript and Other Resources In a .NET Assembly
One concept that I don’t see people using, understanding, or even being aware of much in .NET development circles is the Embedded Resource. Declaring a file as an embedded resource within your project means that it will be compiled into the assembly, so that you don’t have to distribute the actual file with the assembly.
In our projects, we embed many of the resources that we use, in order to reduce the number of files that we have to distribute. The biggest use of this policy is towards JavaScript files. We use a lot of JavaScript in our modules, but we don’t have to distribute any .js files, they all live in a .dll. This also makes it a lot easier to automatically minify the JavaScript files when building, but that’s a story for another time.
Down at the end of this post, I’ll also talk about embedding files that are intended to be loaded in memory to be used, rather than accessed through the web. In that case, it’s much more convenient to already have the file in the assembly, rather than having to search through the file system for it.
So, how do you invoke this magic to place regular, non-compiled files into your compiled assemblies? It all starts, as many things do, in Visual Studio. With the file added to your project, take a gander at the file’s properties (by pressing F4 if you don’t have some crazy keyboard setup). The property page for a file is rather short, and we need to look no farther than the first property of the file to accomplish our purpose. The Build Action property of the file is usually set to Content, meaning “[the] file is not compiled, but is included in the Content output group.” By changing this to Embedded Resource, “[the] file is embedded in the main project build output as a DLL or executable.” This is most of the magic we need, but we’re not quite done yet.
Since this is a JavaScript file that we’ll want to access on the web, we need to tell the assembly that it’s okay to give the web access to the file. If you miss this step, you’re going to get an error page instead of JavaScript when you try to reference it (and everything that depended on the script will be throwing errors both left and right).
So, to accomplish this, we need to add an assembly-level attribute. These usually live in the AssemblyInfo file, which, by default, is under the Properties folder of your project. After adding a using
/Imports
of System.Web.UI, we can add the following WebResource attribute to our project (given a default namespace of “Engage.Demos.EmbeddedResources” and the file “swfobject.2.1.js” placed in a “JavaScript” folder):
[assembly: WebResource("Engage.Demos.EmbeddedResources.JavaScript.swfobject.2.1.js", "text/javascript")]
So, this brings us to the issue of naming. The file is assigned a name when it is embedded into the assembly. This name is the default namespace of the project, plus the folder path, plus the file name itself, all joined together with periods. One important thing to note is that VB.NET assemblies do not have default namespaces (they have root namespaces, a source of continual confusion and frustration for C# programmers who switch back and forth). Therefore, the resource names just start with the folder path. In this case, the name would be "JavaScript.swfobject.2.1.js"
.
Now that we’ve handled that, how do we actually get the script on the page? The same way you get any script on the page, you use the ClientScriptManager (you do use the ClientScriptManager to manage your client scripts, right?). This is accessible through the page’s ClientScript property, from which you can call RegisterClientScriptResource (when using an embedded resource, you don’t get to choose whether it is a “startup script” [added at the bottom of the page] or “script block,” [added at the top1] it’s always a “block”, so you can’t use the ClientScriptManager if you want to use this technique for a “startup script”).
The RegisterClientScriptResource method takes a Type and the name of the resource. The Type, as far as I can tell, is just used to provide an additional “namespace” for the include, i.e. trying to include the same resource twice with the same Type will result in only one script being added to the page, but if they have different Types specified, the script’ll be added twice. So, in our case (working with a class called DemoPage), this looks like the following call during the page’s Load event:
this.Page.ClientScript.RegisterClientScriptResource(typeof(DemoPage),
"Engage.Demos.EmbeddedResources.JavaScript.swfobject.2.1.js");
One notable time where this won’t work is when you are trying to add a script to the page during a partial postback. In truth, you’ll probably have the same issue whether you’re embedding your JavaScript or not, but either way, the fix is to move from the ClientScriptManager to the ScriptManager (great naming for these two, don’t you think?). For embedded scripts, use the static RegisterClientScriptResource method like you would with a ClientScriptManager instance, otherwise, pick between RegisterClientScriptBlock, RegisterClientScriptInclude, and RegisterStartupScript. When using the ScriptManager methods, you’re required to add a line to the end of your script to notify the manager control that you’re script is done loading, but if your script is embedded into the assembly, it can do it for you and you’ll never have to wonder why the stupid thing isn’t working the way (you think) it’s supposed to work.
If you have a case where you want to add a reference to an embedded script but the provided methods don’t work for you, you can use the ClientScriptManager.GetWebResourceUrl method to get a URL to that resource that you can use anywhere URLs are accepted, worldwide.
Now, finally, what about files that aren’t JavaScript? What if you have an XML schema that you want to load up to valid XML against? Or what if you have an image that you want to add as a watermark to user-uploaded pictures? In those cases, when you really want to load up the file within memory, rather than expose it to the web, the workflow is rather different. At the most basic level, you’re going to use Assembly.GetManifestResourceStream to get a stream of the file. The easiest way to get an assembly reference is to start with the type you’re in, and then access its Assembly property, like so:
using (var schemaStream = typeof(DemoPage).Assembly.GetManifestResourceStream(typeof(DemoPage),
"Schemas.DemoSchema.xsd"))
{
// validate something...
}
One further note about naming. In this situation, the first Type parameter is used to lend its namespace to the resource, so, in this case, since the full name of DemoPage is Engage.Demos.EmbeddedResources.DemoPage, I don’t have to specify the Engage.Demos.EmbeddedResources part of the resource’s name.
From there, you can go wild, doing whatever you want to do with the stream of your file.
Hopefully this clears some things up, and hopefully introduces you to a tool that you may not have known about or may not have known enough about. Make sure and let me know if you have any questions, or see any better ways to go about this stuff.
1 The ClientScriptManager actually only messes with the form element on the page, so it’s really the top and bottom of the page’s form that’s where the scripts get placed.