One of the greatest difficulties when developing common web controls (either server or user controls) that we’ve come across in our development group is how to deal with client-side scripts and images. In ASP.NET 1.1 we have recently started using an HttpModule and compiled resource files, but ASP.NET 2.0 makes it much easier to manage embedding resources in assemblies.
The Problem
During your development of robust, rich-client web applications, you’ll quickly develop a set of controls that use JavaScript to provide your application’s users with a really pleasant experience. Now, you have to figure out what to do with these controls. Do you create user controls and cut and paste them between your web applications, or do you create custom server controls that compile nicely and are easy to share? Once you tackle that decision, you have to figure out what you’re going to do with the JavaScript. If you embed it in the user control, it has to travel over the wire every time the page is requested, which will negatively impact any low-bandwidth users of the web application. If you make it a server control, then you have to create an installation script or somehow manage the distribution and location of the external files across all applications using the control.
The ASP.NET 1.1 Solution
To avoid these issues in ASP.NET 1.1, you could create an HttpModule or HttpHandler that responded to your own key filename. This HttpModule or HttpHandler would then return the appropriate resource that had been compiled into a specific resource assembly. In this case, you had to manage the HttpModule or HttpHandler, make sure it was referenced correctly in the web.config, make sure that the compiled resource was available, and cross your fingers and hope for the best. While this is a solution to the problem described above, it is still a lot of work.
The ASP.NET 2.0 Solution
The new version of ASP.NET has integrated the 1.1 solution, and now we get to reap the rewards. So, you can just embed your resources in the appropriate assemblies and reference them without having to worry about configuration or deployment issues. The necessary process is described in the following sections.
Embedding the Resource
To make the file or image accessible from your server control’s assembly, simply add the file to your project, go to the Properties pane, and set the Build Action to Embedded Resource. To expose the file to a web request, you need to add code like that shown in Listing 1 to your AssemblyInfo.cs file. These entries expose the embedded resource so that the ClientScriptManager can both get to the file and know what kind of file it is.
Listing 1: AssemblyInfo.cs entries
[assembly: System.Web.UI.WebResource("myImage.gif", "img/gif")]
[assembly: System.Web.UI.WebResource("myStylesheet.css", "text/css")]
[assembly: System.Web.UI.WebResource("myJavascript.js", "text/js")]
Namespace Note
The project’s default namespace (defined in the Application tab of the project's Properties page) will be added as a prefix to the filename of embedded resources. In this case, I’ve set the default namespace to an empty string. Otherwise, the tag’s first parameter would need to be DefaultNamespace.Filename.Extension instead of simply Filename.Extension. (This was the biggest pitfall I encountered because it wasn’t obvious that the namespace would be added as a prefix, and so I was referencing the resources by their short names when I should have been using the long format.)
Accessing the Embedded Resource
To add the embedded resource to our ASP.NET page, we will be calling the ClientScriptManager’s GetWebResourceUrl method. Its first parameter is the Type of the control’s class (which will eventually provide .NET with an assembly reference where the embedded resource is contained) and its second parameter is the name of the resource as specified in the AssemblyInfo.cs file. For example, to load an image in an Image control, use the code in Listing 2. To add a stylesheet to the Page header area, use the code in Listing 3. To render a JavaScript include tag, use the code in Listing 4.
Listing 2: Setting an image control’s source
Image theImage = new Image();
theImage.ImageUrl =
Page.ClientScript.GetWebResourceUrl(this.GetType(), "myImage.gif");
Listing 3: Adding a stylesheet to the page header
string includeTemplate =
"<link rel='stylesheet' text='text/css' href='{0}' />";
string includeLocation =
Page.ClientScript.GetWebResourceUrl(this.GetType(), "myStylesheet _Links.css");
LiteralControl include =
new LiteralControl(String.Format(includeTemplate, includeLocation));
((HtmlControls.HtmlHead) Page.Header).Controls.Add(include);
Listing 4: Rendering a javascript include
string scriptLocation =
Page.ClientScript.GetWebResourceUrl(this.GetType(), "MSDWUC_WindowStatus.js");
Page.ClientScript.RegisterClientScriptInclude("MSDWUC_WindowStatus.js", scriptLocation);
ClientScriptManager Quirks
One of the things I encountered when working with this technique was that most examples of server controls do their “thing” during the Render event. So, I attempted to be a good corporate citizen and have my controls behave the same way and I found that I could not use RegisterClientScriptBlock at this point in the page life cycle. RegisterStartupScript seemed to work fine in this scenario and IE 6.0 apparently tolerates rendering styles at this point and applying them to the page’s rendered contents, but I wasn’t happy with that. So, I discovered that I had to make all calls to RegisterClientScriptBlock on or before the OnPreRender event for them to be added to the correct part of the page.
Conclusion
We’ve covered how you can embed resources in your server control projects and how to reference them in a web environment. This should simplify your life from a deployment perspective, and should make your server controls tighter now that they can leverage static images and files, and do not require any installation other than deployment of the compiled assembly.