Introduction
Web components (such as ASP.NET Web Controls) often need to contain or access binary data such as images, style sheets, and JavaScript. Of course, you can package them loosely with your assembly and force developers to copy them to places where your components would expect them. But that’s a deployment nightmare and sometimes it’s not even possible.
Here is where ASP.NET WebResources come to the rescue, but at some cost and with some drawbacks of their own. In this article, I will describe how you can use WebResources and how you can minimize the drawbacks with data URIs and content delivery networks.
Using WebResources in ASP.NET
The web resources mechanism allows you to compile any data (such as images, CSS, and JavaScript files) into your assembly. You can request the embedded resources using a special handler which is part of the .NET Framework.
The main advantage of this feature is the smaller number of files you need to deploy. The typical examples are icon libraries. You can easily get some library, such as the Silk Icon Set (http://www.famfamfam.com/lab/icons/silk/), but then you need to maintain and carry with you a bunch of quite small but numerous icons. Some things in life can never be fully appreciated nor understood unless experienced firsthand. Copying the complete set of 1000 Silk icons to an FTP server in a hurry and over an unstable connection is one of them.
The process is actually quite simple, but is prone to errors, typos, and misunderstandings which do not manifest as error messages or exceptions. It simply does not work and you don’t know why.
This article offers a step-by-step guide of how to create a simple web control library with embedded resources.
Preparing the files
First, create a special folder for the files you want to include in your assembly. It’s usually called Resources and lies in the root of your project, but you may name and place it anywhere in your structure. Put your files there. When including multiple files, the easiest way is to drag them from an Explorer window to the Solution Explorer in Visual Studio.
Then you need to mark all these files as Embedded Resources. Select all the files (you may select multiple files) and in the Properties window, set the Build Action to Embedded Resource (not just Resource).
Registering the web resources
The next step is registering the resource as a WebResource. You can do it in any code file (C# or VB.NET) using the WebResource
attribute. It does not really matter where you place the attribute, but I usually create a file called _ResourceRegistration.cs in the folder where the embedded files are placed (the underscore ensures that the file is on top of the list when sorted alphabetically).
The following is a sample _ResourceRegistration.cs file registering a single image:
using System.Web.UI;
[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
"image/png")]
The first argument of the attribute definition is the logical name of the resource. The logical name is the most problematic part of the resource registration, because it must exactly match your project settings and file name. The logical name is case sensitive, even though it contains file names which are generally not case sensitive. A problem in its definition would not cause any compilation or runtime errors, it just won’t work.
The logical name consists of three parts which are concatenated and separated by a dot. The first part is the root namespace of your project. The root namespace is set in the project properties (Application tab) and by default matches the project name. In the above example, the root namespace is Altairis.ResourceDemo.Controls
.
The second part is the path within the project folder structure. In our case, the files are placed in the Resources folder in the root of the project; therefore the part of the name is just Resources. In more complex structures, a dot is used as the path separator. The third and last part is the file name, including the extension. In the above example, it’s FlagBlue.png.
The second argument is the MIME type of the content. When requested using the WebResource.axd handler, the type is sent as a Content-Type HTTP header and may be important from the web browser standpoint. The most common MIME types are image/png, image/gif, and image/jpeg for image formats, text/javascript for JavaScript, and text/css for style sheets.
Getting the resource URL
The last step is to get the final resource URL on which your embedded data can be accessed. This can be done by calling the method Page.ClientScript.GetWebResourceUrl
. The method has two arguments: first is the type to which the resource is related, usually the control using it. The second one is the logical name of the resource. You may get the path to the above resource by calling:
var imageLink = this.Page.ClientScript.GetWebResourceUrl(this.GetType(),
"Altairis.ResourceDemo.Controls.Resources.FlagBlue.png");
The resulting paths point to the ~/WebResource.axd handler and looks similar to the following:
/WebResource.axd?d=dseRjrCHl7PvN70cMzoiaJKEsWjA2FTOp8sEou4qLQKJBO_kc1b3cnhEI6E-eWw
poXM-z4_GG5ApZbQR4SIsEOcpPyodXAeRk7ZKg-YQLxdLqIcBtUWb5mRQ-82wT8Pe2MxOiYZOyAN3dUvEj
bsvhqPfDjQdtgCF4HtTkdsrUF9XBmnkjvja_B1l9bG0GZce0&t=634317785760223219
The long Base-64 encoded query string parameter is the encrypted logical name of the resource and the assembly where it’s contained.
Content Delivery Networks (CDN)
The above mechanism is used in the ASP.NET Web Forms infrastructure itself. For example, the helper JavaScript files used by validation controls (such as RequiredFieldValidator
, RegularExpressionValidator
etc.) are loaded this way.
With the release of ASP.NET 4.0, Microsoft launched its own Content Delivery Network (CDN). All the files required by the ASP.NET infrastructure, as well as jQuery and some jQuery plug-ins, are hosted at ajax.aspnetcdn.com. A full list of the CDN files can be found at the following address: http://www.asp.net/ajaxlibrary/cdn.ashx.
The main reason for using content delivery networks is performance. The CDN consists of many servers placed in different parts of the world and a user’s request is routed to the nearest edge cache server, which may be reachable more quickly than your own server. Also, CDN resources are cached by the browser. When different ASP.NET applications use the same support scripts, they aren’t loaded many times (like when they are requested separately from each web site), but only once from the shared location.
To enable ASP.NET CDN for the core server controls, place the ScriptManager
control in your page and set its EnableCdn
property to true
. If you don’t use the Microsoft AJAX framework, you can disable its automatic referencing as well:
<asp:scriptmanager runat="server" enablecdn="true" ajaxframeworkmode="Disabled" />
Using CDN with web resources
If you are, for example, a component vendor, you might want to use a similar technique for distributing your resources. You might want to run your own CDN and reference the embedded resources from there.
It’s however not a good idea to simply hardcode the CDN paths into your components and force users to use them. CDNs are not welcome in some scenarios, such as intranet applications, where access to the Internet is limited, or offline development. It’s therefore recommended to do as Microsoft does: allow developers to turn CDN on and off using the ScriptManager
control.
When registering the resources, you might use an additional named argument of the WebResource
attribute called CdnPath
, which contains the full path to where the resource file is located in your CDN. When EnableCdn
is set to true
, the CDN link is issued instead to call to WebResource.axd.
The above sample can be modified as follows:
using System.Web.UI;
[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
"image/png",
CdnPath = "http://yourcdn/Samples/EmbeddingResources/FlagBlue.png")]
ASP.NET won’t help you with building the CDN itself, it would just link to it. To build the CDN, you can use various ways starting with easy and free solutions like CloudFlare (www.cloudflare.com), over paid services of big providers like Akamai, and ending with deploying your own server farms worldwide, that’s up to you.
Using SSL with CDN
Usage of CDNs can be problematic when your application uses an encrypted connection – HTTPS or SSL/TLS. Referencing unsecured content from a secured page is a security risk and browsers would display miscellaneous annoying warnings to users.
In the default settings, CDN would be used only when the page is requested using a non-secured connection. If SSL is employed, the link would fall back to the WebResource.axd approach.
If your CDN offers an HTTPS connection as well, you may indicate it by adding an additional argument called CdnSupportsSecureConnection
set to true
. The registration would then look similar to:
[assembly: WebResource("Altairis.ResourceDemo.Controls.Resources.FlagBlue.png",
"image/png",
CdnPath = "http://yourcdn/Samples/EmbeddingResources/FlagBlue.png",
CdnSupportsSecureConnection = true)]
When this argument is set to true
and the page is requested using HTTPS, CdnPath
is modified to use HTTPS as well. In the above example, the link would thus begin with https://yourcdn/….
Data URI scheme
Sometimes you can avoid the use of web resources altogether using data URIs. Commonly, the URI contains the HTTP address which identifies the object (Uniform Resource Identifier) and usually also locates it, gives the address where the object can be located.
In the case of data URIs, the object itself is contained within the URI. This approach is most often used for small images, such as icons, where the binary image data is embedded in Base64 encoded form in the URI itself. See the following example (shortened for readability):
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB………QCAYAAAAf8/9h=" />
The structure of the data URI is formally defined in RFC2397, but usually takes the form shown above. It starts with the “data:
” scheme prefix followed by the content type of the data (here “image/png”, as discussed above), semicolon, encoding type (“base64”), colon, and the Base64-encoded binary data.
The main advantage of using data URIs is limiting the number of HTTP requests. If your page uses lots of small images, displaying it in the browser may involve quite a number of HTTP requests. Although the amount of data itself is not big, the overhead is quite significant. By embedding the data in the page itself, you limit the number of requests. In addition to lowering the transaction costs, limiting the number of requests lowers the load on the web server.
The main disadvantage is that Base64-encoded data is about 1/3 bigger than their binary form. Also, when used in the naïve way outlined above, when used repeatedly in a page, they must be repeated each time.
Using style sheets with a data URI scheme
The second disadvantage, repeating occurrences of a particular image, can be easily solved by not referencing the data directly using the “src
” attribute, but using CSS classes and containing the image data in the style sheet file.
To display a red flag icon, we might define the following HTML in the page itself:
<span class="redflag"></span>
Then we define the following markup in the page style sheet (again, the Base64 data is trimmed for readability):
.redflag {
display: inline-block;
width: 16px;
height: 16px;
background-position: center center;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAA…);
}
The complete example
All the methods shown in this article are combined in the sample application you can download. The source code contains two projects. Altairis.ResourceDemo.Controls is a class library, containing a single web control and a bunch of resources. The second project is a sample web site using the control library.
We define a single web control called FlagIcon
. The control displays one of seven colored flag images contained in the Silk icon set already mentioned in this article. The control has two interesting properties:
FlagColor
is the enum containing all the available flag colors and defines which image is going to be displayed.LinkMode
controls the means by which the image is displayed. When set to WebResource
, the embedded resources or CDN are used. When set to DataUri
, the picture is in-lined into the HTML as a data URI. The third possible value is StyleSheet
, which combines the approaches.
Combining web resources and data URIs
The last method combines the techniques mentioned here and tries to maximize the gains and minimize the losses.
The core is a style sheet containing all seven in-lined images. The style sheet is compiled in the component library as a web resource, as well as available through the Altairis Content Delivery Network.
The main challenge there is to reference the style sheet in a page and do it only once, even when the page contains multiple icons. That’s what the EnsureStyleSheetRegistration
method I developed is good for:
private void EnsureStyleSheetRegistration() {
if (this.Page.Header == null)
throw new NotSupportedException(
"No <head runat="\""server\"> control found in page.");
var styleSheetUrl = this.Page.ClientScript.GetWebResourceUrl(
this.GetType(), STYLE_RESOURCE_NAME);
var alreadyRegistered = this.Page.Header.Controls.OfType<HtmlLink>().Any(
x => x.Href.Equals(styleSheetUrl));
if (alreadyRegistered) return;
var link = new HtmlLink();
link.Attributes["rel"] = "stylesheet";
link.Attributes["type"] = "text/css";
link.Attributes["href"] = styleSheetUrl;
this.Page.Header.Controls.Add(link);
}
Conclusion
ASP.NET offers a nice mechanism for embedding resources, such as images, style sheets, or JavaScript, into assemblies and reading them via the WebResource.axd handler. In the case of small images, in-lining them using data URIs may be a better alternative. Many negatives of in-lining may be solved by combining the above methods and defining in-lined images in style sheets.
Another alternative, not discussed in this article, is the use of image sprites. If you want to explore this idea in ASP.NET, there is a Sprite and Image Optimization Framework project available in ASP.NET Futures on CodePlex: http://aspnet.codeplex.com/releases/view/50869.