Package Up Client Files

Embed Control Resources in the Assembly and Keep them Hidden

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.x | 2.0

 

Package Up Client Files

Embed Control Resources in the Assembly and Keep them Hidden

 

By Dino Esposito

 

Resources are an old acquaintance for many seasoned Windows programmers. Most pre-.NET Windows applications were used to embed dialogs boxes, images, icons, localizable text, and even custom data in a distinct segment within the executable file. Keeping resources distinct from the rest of the code has several significant benefits. In particular, you can localize, or simply replace, resources without even recompiling the source code. In the realm of Web applications, however, basic conditions lacked for code and resources separation to be a design and maintenance edge. The advent of ASP.NET finally started the process of Web componentization a process that is going to receive a definite boost with the introduction of ASP.NET 2.0.

 

In classic ASP, the structure of a Web application was such a tangled miscellany of elements (markup, literals, code blocks, COM objects, script) it was difficult for anybody to distinguish business components from UI blocks and both from glue code. In ASP.NET, controls provide an efficient way to pack UI blocks and code together and expose the resulting aggregate through a set of well-known interfaces and a public object model. Regardless of the supported programming model, an ASP.NET control always renders up as HTML and can make use of special resources, such as script code and images. The output of a control is then merged with the output of the hosting page and generates a unique markup string that embeds images and script code for all controls and the page itself.

 

How should you design and implement an ASP.NET control that needs to incorporate images and script? And what s going to change with the forthcoming hot new version of ASP.NET?

 

Find Your Resources

Let s focus on a concrete scenario and imagine you ve been assigned the task of creating a custom panel control that mimics the user interface of Windows XP panels (see Figure 1). The control, named GraphicPanel, is a relatively simple wrapper that works on the rendering engine of the Panel class. The new control overrides two methods, RenderBeginTag and RenderEndTag, to build an outer table around the default output of the panel control. The resulting table counts two rows: the header and the body. The body coincides with the output of the base class; the top-most row renders the header you see in Figure 1. The top-most row consists of three cells: left edge, right edge, and title. The side cells display an image each. How do you specify the URLs of these images?

 


Figure 1: A graphic panel control.

 

Figure 2 shows a small fragment of the control s source code, where a couple of string properties clearly stand out. They are LeftEdgePictureUrl and RightEdgePictureUrl. As the names suggests, both contain the URL to the image to be used on the left and right edge of the header row. The two properties prefigure a usage like the following example. After dropping the GraphicPanel control on a Web form, the page developer selects it in the designer and edits the contents of the properties in the Visual Studio.NET property grid. In the end, the two images are definitely part of the control s user interface, but are not embedded with the control s assembly. They remain external resources belonging to the application rather than to the control. Among other things, this means that you need to bundle the images along with the control s assembly, and you must remember to move images from site to site if you use the same control across different applications. Even more importantly, if at a certain time you reorganize the folders under your root, you need to tweak all the pages that use the control to make sure it correctly points to the images.

 

public class GraphicPanel : Panel

{

 public string LeftEdgePictureUrl { ... }

 public string RightEdgePictureUrl { ... }

 public string HeaderText { ... }

 public override void RenderBeginTag(HtmlTextWriter writer)

 { ... }

 public override void RenderEndTag(HtmlTextWriter writer)

 { ... }

}

Figure 2: Skeleton of the GraphicPanel class.

 

Although limiting, this approach is easy to code and can be used with both ASP.NET 1.x and 2.0. But there has to be a better approach.

 

Find a Proxy for Your Resources

So you want to build a control that uses images as part of its own user interface, and you especially want to be able to pack images and code within the same assembly, thus zeroing deployment and maintenance costs. Note that the problem here is not packing images and code together that s exactly what resources are for, isn t it? The point is how to retrieve the image out of the assembly and serve it to the user through an HTML tag. You need a URL to associate with the Src property of the tag. Once embedded in an assembly, though, the image can t be referenced as a URL that the browser can translate into a remote path.

 

You need an additional component that can be expressed as a URL and retrieves and downloads the bytes of the image to the client. Here s an abstract example:

 

graphicPanel1.LeftEdgePictureUrl = MyUrl + "?id=leftgrip";

 

What s Behind the MyUrl Placeholder?

You can start by writing a custom HTTP handler in the same assembly that contains the control. Next, you bind the handler to a fixed URL in the web.config file. The ASP.NET HTTP runtime guarantees that it ll be invoked whenever a request comes in for that URL. Here s some sample code to add to the application s web.config file:

 

 

   type="YourCompany.ImageService" />

 

Both the handler s path name and type used in the preceding code snippet are arbitrary. In particular, you should note that imageservice.axd doesn t have to be a physical resource located on the Web server. It is a mere name that the ASP.NET runtime maps to a particular method on the specified type. In the end, what really matters is that the current assembly that is, the one you re building that contains the custom control is plumbed as shown in Figure 3.

 

namespace Expoware

{

 public class ImageService : IHttpHandler

 {

    public bool IsReusable {

       get { return true; }

    }

    public void ProcessRequest(HttpContext context) {

       string imageID = context.Request["id"];

 if (imageID == null)

          return;

       Image img = LoadImageFromResources(imageID);

       if (img != null) {

          WriteImage(context, img);

          img.Dispose();

       }

    }

    private void WriteImage(HttpContext context,

     System.Drawing.Image img) {

        context.Response.ContentType = "image/gif";

        MemoryStream ms = new MemoryStream();

        img.Save(ms, ImageFormat.Gif);

        ms.WriteTo(context.Response.OutputStream);

        ms.Close();

    }

    private Image LoadImageFromResources(string imageID) {

       Assembly dll = Assembly.GetExecutingAssembly();

       Bitmap img = new Bitmap

         (dll.GetManifestResourceStream(imageID));

 return img;

    }

 }

}

Figure 3: A sample HTTP handler for serving images.

 

The class is an HTTP handler and implements IHttpHandler. Any request that hits imageservice.axd is served invoking the ProcessRequest method on the handler class. In the code sample illustrated in Figure 3, the ProcessRequest method uses the content of the ID query string parameter (the name ID is arbitrary) to select an image from the embedded assembly s resources. The bytes of the image are used to build a System.Drawing.Bitmap object, which is then serialized to the client:

 

img.Save(ms, ImageFormat.Gif);

 

It is interesting to notice that the System.Drawing.Bitmap object is format-agnostic; the preceding line takes the image and downloads it to the client as a GIF file. In other words, you can embed JPEG images and serve them to the client as GIFs. The picture properties of the GraphicControl class are set as follows:

 

graphicPanel1.LeftEdgePictureUrl =

 "imageservice.axd?id=Expoware.LeftGrip.gif";

graphicPanel1.RightEdgePictureUrl =

 "imageservice.axd?id=Expoware.RightGrip.gif";

 

How do you add images to an assembly? You simply select Add | Existing Item from the project s context menu and pick up any image you need. Next, select the image in the project and make sure it is configured as an Embedded Resource in the Properties window. Finally, you ought to rename the file according to a particular convention. The file name must be prefixed with the control s namespace. For example, if the control is hosted in the Expoware namespace, the file name must be Expoware.[FileName].[Extension].

 

imageservice.axd acts as a proxy for all images that a control needs to display. It is a component that uses your own code to retrieve and serve images dynamically. In this case, images are packed with the control and don t have to be deployed or maintained separately. The LeftEdgePictureUrl property can now be written as follows:

 

public string LeftEdgePictureUrl

{

 get

 {

   object o = ViewState["LeftEdgePicture"];

   if (o == null)

      return "imageservice.axd?ID=Expoware.LeftGrip.gif";

   return (string)o;

 }

 set { ViewState["LeftEdgePicture"] = value; }

}

 

If you want to use a custom picture, you simply set the property to any URL that corresponds to a physical resource. Otherwise, if you take the default value, the control picks out the images from the same assembly. The GraphicPanel control now can appropriately be defined as a self-contained control.

 

What s New in ASP.NET 2.0

The big picture doesn t change much in ASP.NET 2.0, meaning that you still need to use an HTTP handler to retrieve the bytes from the executing assembly. However, the great news is that the tools to do that are now available with the system. They are the WebResource.axd handler and the WebResource attribute. The former is a system component that controls use to retrieve resources from assemblies: script, strings, style sheets, images, whatever. The latter is an assembly-level metadata attribute that allows you to mark embedded resources as URL-accessible. In the AssemblyInfo file of your project, you add code like this:

 

[assembly: WebResource("Expoware.LeftGrip.gif",

 "image/gif")]

 

To programmatically access the resource, you use a new method on the Page class the GetWebResourceUrl method:

 

public string LeftEdgePictureUrl

{

 get

 {

    object o = ViewState["LeftEdgePicture"];

    if (o == null)

       return Page.GetWebResourceUrl(typeof(GraphicPanel),

             "Expoware.LeftGrip.gif");

    return (string)o;

 }

 set { ViewState["LeftEdgePicture"] = value; }

}

 

The method looks in the assembly that contains the specified type for a marked Web resource with the specified name. The resource is then served to the browser and cached locally. With this tool, ASP.NET 2.0 enables control developers to package client files without having to scatter them into the file system, and without enabling external handlers. It is definitely a cleaner and simpler form of deployment.

 

Fast Facts

Rich controls sometimes need embedded resources to render out their own user interface. Resources may include JavaScript files, GIF or JPEG images, style sheets, and anything you need outside code to serve up a control. When building a control, the simplest approach consists of referencing any external resource through a URL. The code has no overhead at all, but the approach jeopardizes the overall manageability of the project, and sparse files proliferate in the file system and may be even left around.

 

A second approach entails creating a custom HTTP handler and binding it to a public name. You reference external resources (images, specifically) through this sort of proxy. The handler wraps the code to load the image from the assembly, but requires you to edit the web.config file to register the public name with the application. These two solutions work great with ASP.NET 1.x and ASP.NET 2.0.

 

In ASP.NET 2.0, however, a new set of specifically designed tools the WebResource attribute and WebResource.axd handler makes working with resources as seamless as possible, and removes all the drawbacks we ve considered so far.

 

The sample code accompanying this article is available for download.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish