Introduction
There have been several articles written on the subject of dynamic image rendering. This article discusses a more generic approach to implementing this concept. Some of the techniques discussed in this article are based on the concepts of similar articles that focused on a specific area of dynamic content rendering such as with graphs, image text, etc..
Namely, the following articles were a vital asset in writing this article:
This article attempts to generalize the concept of dynamic rendering to allow the programmer to implement their own dynamic content. This can form the basis of creating custom ASP.NET web controls that can serve up a variety of different types of dynamic content. We will look at ways to render dynamic content that is created on the fly or embedded in an assembly DLL. This helps eliminate the need for separate files such as JavaScript or images that normally have to be separately included with the web control assembly DLL.
A few advantages of dynamic rendering are:
- The ability to have all static files embedded into an assembly DLL for easier distribution.
- Hiding script source code.
- Being able to create dynamic images at the server without having to save an image file to disk.
Background
As I read articles and sample code on dynamic image rendering, I decided to take what I had learned and try to improve upon it. As I dug into dynamic image rendering, I soon realized that other contents could be dynamically rendered as well, such as scripts, style sheets, etc.. I decided to implement generic dynamic content classes based on the dynamic image content articles that I had read. I also elected to incorporate the image drawing into user controlled drawing rather than specific classes for specific types of drawing. The result is the source portrayed in this article.
Using the code
The primary class that is used for serving up the dynamic content is the CustomHttpModule
class. This is a module handler class that is used to intercept aspx URL requests. This class is registered in the Web.Config file under <System.web>
section as follows:
<httpModules>
<add name="CustomModule" type="Custom.Web.CustomHttpModule, CustomWeb"/>
</httpModules>
Note that the CustomWeb.dll produced by the CustomWeb project must be accessible to the ASP.NET page that implements it. This usually involves copying the produced CustomWeb.DLL file into the bin directory of the ASP.NET page.
In order to see how to use the CustomHttpModule
class, I have supplied two classes that implement it. One is the CustomImage
class, and the other is the EmbeddedJScript
class. Note that the EmbeddedJScript
class implements the more generic EmbeddedWebContent
class which encapsulates the CustomHttpModule
class, whereas the CustomImage
class directly implements the CustomHttpModule
class.
Let's first examine the CustomImage
class. This class is a Custom Control derived from the Image
control. It can be dropped onto a Web Form using Visual Studio .NET.
The first area of interest is in the Render()
method. It is in this method that the CustomImage
control calls an event handler called OnImageRender
. This event handler is implemented by the control object's parent page to draw into the GDI+ Graphics
object that is provided in the EventArgs
. This dynamically drawn image will then be dynamically streamed to the web page using the CustomHttpModule
class.
protected override void Render(HtmlTextWriter writer)
{
if (Site != null && Site.DesignMode)
{
Label DesignerLabel = new Label();
DesignerLabel.Width = this.Width;
DesignerLabel.Height = this.Height;
DesignerLabel.Text = "CUSTOM IMAGE";
DesignerLabel.BorderStyle = this.BorderStyle;
DesignerLabel.BorderWidth = this.BorderWidth;
DesignerLabel.BorderColor = this.BorderColor;
DesignerLabel.CssClass = this.CssClass;
DesignerLabel.RenderControl(writer);
}
else
{
String SaveUrl = null;
if (OnImageRender != null)
{
Bitmap Bmp = new Bitmap((int) this.Width.Value,(int) this.Height.Value);
Graphics Graph = Graphics.FromImage(Bmp);
MemoryStream MemStream = new MemoryStream();
Graph.FillRectangle(new SolidBrush(this.BackColor),0,0,Bmp.Width,Bmp.Height);
OnImageRender(this,new RenderImageEventArgs(Graph));
Bmp.Save(MemStream,RenderImageFormat);
ImageData.Data = MemStream.ToArray();
SaveUrl = ImageUrl;
ImageUrl = CustomHttpModule.RegisterHandler(Page.Application,
ID,
new HttpModuleHandler(ModuleHandler),
this.ImageData);
Graph.Dispose();
Bmp.Dispose();
MemStream.Close();
}
base.Render(writer);
if (SaveUrl != null)
{
ImageUrl = SaveUrl;
}
}
The CustomHttpModule
is implemented in two different areas of the CustomImage
class. Here is where the static call to CustomHttpModule.RegisterHandler()
is made.
ImageUrl = CustomHttpModule.RegisterHandler(Page.Application,
ID,
new HttpModuleHandler(ModuleHandler),
this.ImageData);
The above call basically registers the control as a custom image handler control. The return value of CustomHttpModule.RegisterHandler()
is a URL that is to be used to signal the CustomHttpModule
class to invoke the HttpModuleHandler
delegate (ModuleHandler
) to be called when the dynamic content is requested by the web page. The last argument to RegisterHandler
is optional data that will be passed to the delegate when it is called. In this case, it is the image raw data bytes that are drawn by the code in the event handler.
When the Image
control is rendered, it will use the URL returned from RegisterHandler
as the <Img>
tag SRC
attribute. When the browser goes to locate the image, the module handler takes over. It locates the appropriate registered caller and then calls the ModuleHandler
delegate. The delegate then streams out the image content to the requesting URL.
static private void ModuleHandler(CustomHttpModule Module,
CustomHttpModuleEventData e)
{
HttpContext context = e.HttpApp.Context;
CustomImage.ImageDataType ImgData = e.ModuleObj as ImageDataType;
if (ImgData != null)
{
MemoryStream MemStream = new MemoryStream();
MemStream.Write(ImgData.Data,0,ImgData.Data.Length);
MemStream.WriteTo(context.Response.OutputStream);
MemStream.Close();
context.Response.Cache.SetNoServerCaching();
context.Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
context.Response.Cache.SetNoStore();
context.Response.Cache.SetExpires(new DateTime(1900,01,01,00,00,00,00));
context.ClearError();
context.Response.ContentType = "image/" + ImgData.RenderImageFormat.ToString();
context.Response.StatusCode = 200;
context.Response.Flush();
}
}
The EmbeddedJScript
class uses a similar technique; however, it incorporates the EmbeddedWebContent
class which provides a generic ModuleHandler
delegate function that streams out the data. The main reason for using the EmbeddedWebContent
class is to allow the streaming of resource embedded data as dynamic content. Thus, you can create a JavaScript .JS file, and add it to the VS.NET project as an embedded resource. Simply set the JavaScript .JS file's Build Action to Embedded Resource in Visual Studio. This is accessed under the file properties (right click on JS file and select Properties).
When a JScript file is embedded, it is part of the assembly DLL and does not need to be included as a separate file when distributing the Web Control. The JScript source is loaded from the resource, and directly streamed to the web page as dynamic content using the HttpModuleHandler
class as shown for the CustomImage
class. Note that a similar approach can also be used for resource embedded images, style sheets, etc..
In conclusion, the primary goal here was to show the uses of various forms of dynamic content and illustrate a central control class (HttpModuleHandler
) to handle the process with some sense of structure. Who knows, maybe others will be able to build upon this code as I had from articles that I read. I hope this helps.