Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Watermarker Module

0.00/5 (No votes)
16 Sep 2005 1  
An implementation of IHttpModule for applying watermark images to web forms.

Introduction

This article presents a technique for applying watermark images to ASP.NET web forms though the use of an HTTP module. The concept of filtering the HTTP stream as a means for inserting additional markup is presented through the definition of the custom InsertionFilterStream class. To simplify the creation of modules that would perform this filtering, the base class InsertionModuleBase is presented with a virtual method FilterString. Finally, the WatermarkerInsertionModule class is defined as a functional subclass of the base, providing a practical means for inserting common watermark images across web forms and applications.

Background

In June of 2005, CodeProject member Kenny Young[^] posted a nice article, ASP.NET - C# Application Environment Backsplash[^], in which he demonstrated a technique of using a custom Page class as a base page for applying watermark images (Kenny refers to them as �backsplashes� in his article). Any page in an ASP.NET application that would display the watermark would inherit from his BasePage class.

As Kenny demonstrated, a simple style attribute inserted into the <body> tag of the output HTML is all that is necessary to create the watermark. I liked the concept of automating a watermark image, particularly to distinguish among development, beta, or production environments, and wondered whether or not such functionality could also be accomplished using an HTTP module. (Readers who are unfamiliar with HTTP modules are encouraged to examine George Shepherd's MSDN article on the topic [^] for an introduction.) Such an implementation would tap into the HTTP stream itself, eliminating the need to derive watermarked pages from a custom Page class.

At first thought, one might attempt to manipulate the HttpResponse.OutputStream[^] property directly to read from the HTTP stream and insert the required style attribute. After all, this property holds the Stream object that represents the outgoing HTTP content, and the HttpResponse object is certainly available in the context of an HTTP module. Unfortunately, this possibility is prevented by the fact that the OutputStream property actually contains an HttpResponseStream type, which is write-only. Attempts to read from an HttpResponseStream object result in an exception being thrown.

Filtering the HTTP Stream

What is allowed is the filtering of the output stream. The HttpResponse object exposes a Filter[^] property, which itself is a Stream type. If Filter is set, then any output written to the response stream is redirected to the filtering stream. The filter may perform its manipulation of the contents, but must then write its results back to the original stream. This effectively forms a chain of two streams; indeed, multiple filtering streams may be applied, provided each assumes the responsibility of writing back to the stream object it is replacing as the Filter.

Our filtering stream is defined by the InsertionFilterStream class included with the source download, the constructor of which follows:

public class InsertionFilterStream : Stream
{
  // the original stream we are filtering

  private Stream _originalStream;
 
  // the response encoding, passed in the constructor

  private Encoding _encoding;

  // our string replacement function for inserting text

  private FilterReplacementDelegate _replacementFunction; 

  . . .

  // the constructor must have the original stream for which this one is

  // acting as a filter, the replacement function delegate, and the

  // HttpResponse.ContentEncoding object


  public InsertionFilterStream(Stream originalStream
    , FilterReplacementDelegate replacementFunction
    , Encoding encoding)
  {
    // remember all these objects for later

    _originalStream = originalStream;
    _replacementFunction = replacementFunction;
    _encoding = encoding;
  }

  . . .
}

Three important pieces of information will be passed in to the constructor for our filtering object. The first is the original Stream object for which we�re performing our string manipulations. This will likely be the HTTP output stream, but there could be one or more additional filter streams already in place. In practice, we�ll use whatever stream is already set for the HttpResponse.Filter property; if no other filter were already applied, this would be the output stream itself.

The second piece of information is a delegate, pointing to the function that will perform a string replacement. The delegate is defined with the following signature:

public delegate string FilterReplacementDelegate(string s);

As we�ll see later, this offers simplicity for the developer; rather than dealing with encoded byte arrays, the developer will use common string objects.

The final parameter for the constructor is an Encoding object. This will also be retrieved from the HttpResponse object through its ContentEncoding property, allowing our implementation to be neutral to the given character set. These three objects are stored in private class variables for later use.

The most important method in our filtering Stream subclass is the Write method. When Write is called, it is passed a byte array, already encoded according to the response stream�s character set. Our overridden method performs these important steps: it converts the encoded byte array to a plain string for ease of use, passes that string to the replacement function delegate, re-encodes the result as a byte array, and writes the bytes back to the original output stream.

public override void Write(byte[] buffer, int offset, int count)
{
    // we want to retrieve the bytes in the buffer array, which are already

    // encoded (using, for example, utf-8 encoding). We'll use the 

    // HttpResponse.ContentEncoding object that was passed in the 

    // constructor to return a string, while accounting for the character

    // encoding of the response stream

    string sBuffer = _encoding.GetString(buffer, offset, count);
 
    // having retrieved the encoded bytes as a normal string, we

    // can execute the replacement function

    string sReplacement = _replacementFunction(sBuffer);
 
    // finally, we have to write back out to our original stream;

    // it is our responsibility to convert the string back to an array of

    // bytes, again using the proper encoding. 

    _originalStream.Write(_encoding.GetBytes(sReplacement)
        , 0, _encoding.GetByteCount(sReplacement));
    
}

The Base HTTP Module

With our InsertionFilterStream class prepared to intercept writes to the output stream, we�ll use an HTTP module to hook into the web application�s events and instate our filter. An HTTP module is created by implementing the IHttpModule[^] interface, and installed in the HTTP pipeline by adding an item to the <httpModules> subsection of the <system.web> section of a web.config or machine.config file.

The IHttpModule contract requires the implementer to define two methods: Init and Dispose. The ASP.NET runtime will call Init for each installed module, passing each the HttpApplication object. With this object, the module may install handlers for any number of events exposed by the web application. In our case, we only need to hook into the BeginRequest event.

To support a simple model for creating modules that rely on string replacement functions to insert additional markup, we�ll define a base class that handles the appropriate event interaction with the web application. We�ll also include a virtual method called FilterString which is sent to the InsertionFilterStream object as a delegate. The InsertionModuleBase class is shown below in its entirety:

public class InsertionModuleBase : IHttpModule
{    
    private FilterReplacementDelegate   _replacementDelegate = null;
    private InsertionFilterStream       _filterStream = null;
 
    public InsertionModuleBase()
    {
    }
 
    // required to support IHttpModule

    public void Dispose()
    {           
    }
     
    public void Init(HttpApplication app)
    {
        // setup an application-level event handler for BeginRequest

        app.BeginRequest 
          += (new EventHandler(this.Application_BeginRequest));          
    }
 
    private void Application_BeginRequest(object source, EventArgs e)
    {
        // upon an application page request, establish our 

        // InsertionFilterStream object in the HttpResponse 

        // filter chain

 
        HttpApplication app = source as HttpApplication;
        if (app != null)
        {
            // construct the delegate function, using the FilterString method;

            // as this method is virtual, it would be overriden in subclasses

            _replacementDelegate = new FilterReplacementDelegate(FilterString);
 
            // construct the filtering stream, taking the existing 

            // HttpResponse.Filter to preserve the Filter chain;

            // we'll also pass in a delegate for our string replacement 

            // function FilterString(), and the character encoding object 

            // used by the http response stream. These will then be used

            // within the custom filter object to perform the string 

            // replacement.

            _filterStream = new InsertionFilterStream(
                                      app.Response.Filter
                                     , _replacementDelegate
                                     , app.Response.ContentEncoding
                            );
 
            // set our filtering stream as the new HttpResponse.Filter 

            app.Response.Filter = _filterStream;
        }
    }

    // This is the function that will be called when it's time to perform

    // string replacement on the web buffer. Subclasses should override this

    // method to define their own string replacements

    protected virtual string FilterString(string s)
    {
        // by default, perform no filtering; just return the given string

        return s;
    }

}

A Simple Insertion Module

An insertion module may now be created by subclassing InsertionModuleBase and overriding the FilterString method. Here is a simple but complete example that replaces the <body> tag of the HTML output with one adorned with a background color, and a heading <table> displaying the text �Development�.

public class SimpleInsertionModule : InsertionModuleBase
{
    protected override string FilterString(string s)
    {
        return s.Replace("<body>"
             , "<body bgcolor='#EFEFEF'>"
             + "  <table width='100%' bgcolor='pink'>"
             + "    <tr><td>Development</td></tr>"
             + "  </table>");
    }
}

Provided a compiled version of this class (and the supporting classes InsertionModuleBase and InsertionFilterStream) were available to a web application (i.e. installed in the global assembly cache, or copied into the application�s /bin directory), a simple addition to the application�s web.config file is all that is necessary for this string replacement to occur with all the application�s web forms. Alternatively, the addition may be made to the server�s machine.config file, in which case all applications on the server would have the functionality available.

Custom HTTP modules are included in the HTTP pipeline by adding items to the <httpModules>[^] section of web.config (which is nested within <system.web>). The following format is used to add a module to the HTTP pipeline:

<add type="classname,assemblyname" name="modulename"/>

So to indicate the inclusion of the SimpleInsertionModule defined above, assuming the namespace MyNamespace, compiled into a .dll called InsertionModules.dll, we could use the following fragment in web.config or machine.config:

<configuration>
  <system.web>
    <httpModules>
      <add type=�MyNamespace.SimpleInsertionModule,InsertionModules�
           Name=�SimpleInsertionModule� />
    </httpModules>
  </system.web>
</configuration>

The WatermarkerInsertionModule Class

To accomplish the original challenge, the inclusion of a watermark image using an HTTP module rather than a Page subclass, we again subclass InsertionModuleBase and override the FilterString method. In addition to watermarking, we�ll provide the ability to add a header and/or footer. We�ll also use regular expressions for more robust pattern matching and case-insensitive replacements. The WatermarkerInsertionModule class is shown below in its entirety:

public class WatermarkerInsertionModule : InsertionModuleBase
{
    protected override string FilterString(string s)
    {
        string sReturn = s;        
 
        // check configuration settings for what the watermarker module 

        // should do;

        string sImage 
     = ConfigurationSettings.AppSettings["WatermarkerInsertionModule_Image"];
        string sTopContents 
     = ConfigurationSettings.AppSettings["WatermarkerInsertionModule_Top"];
        string sBottomContents 
     = ConfigurationSettings.AppSettings["WatermarkerInsertionModule_Bottom"];
 
        // if we want a watermark image, add a <style> tag just before 

        // the </head> tag

        if (sImage != String.Empty && sImage != null)
        {
            Regex rHeadEnd = new Regex("(</head>)", RegexOptions.IgnoreCase);
            
            // this style tag is borrowed directly from Kenny's article

            string sStyle = "<style>body {" 
                          + "background-image: url(" + sImage + "); "
                          + "background-attachment: scroll; "
                          + "background-repeat: repeat; "
                          + "background-color: transparent; "
                          + "}</style>";

            sReturn = rHeadEnd.Replace(sReturn, sStyle + "</head>");
        }
 
        // if we want contents at the top, insert them immediately 

        // after the body tag; we'll use a regular expression to 

        // find the body tag, in case it has other attributes

        if (sTopContents != String.Empty && sTopContents != null)
        {
            Regex rBodyBegin = new Regex("<body.*?>", RegexOptions.IgnoreCase);
            Match m = rBodyBegin.Match(sReturn);
            if (m.Success)
            {
                string matched = m.Groups[0].Value;
                sReturn = sReturn.Replace(matched, matched + sTopContents);
            }
        }
 
        // if we want contents at the bottom, insert them immediately 

        // before the closing </body> tag; again, just to make sure 

        // we're doing this in a case-insensitive way, we'll use a 

        // regular expression to locate it.

        if (sBottomContents != String.Empty && sBottomContents != null)
        {
            Regex rBodyEnd = new Regex("</body>", RegexOptions.IgnoreCase);
            sReturn = rBodyEnd.Replace(sReturn, sBottomContents + "</body>");
        }
 
        // return the filtered string

        return sReturn;
    }
}

As in the previous SimpleInsertionModule example, the deployment of WatermarkerInsertionModule involves the following steps:

  1. Install the compiled .dll containing the WatermarkerInsertionModule, InsertionFilterStream, and InsertionModuleBase classes to either the global assembly cache, or the web application�s /bin directory.
  2. Modify either the machine.config file on the server, or the web.config file in the web application directory, to include the appropriate <add> item in the <httpModules> section of <system.web>.

Additional Considerations

While boiling down this replacement process to a single overridden FilterString method in a custom subclass may provide a convenience to the developer, it�s not without a price. Converting the byte array to a string and back again entails more overhead than just manipulating the byte array directly. For that matter, using an HTTP module is its own form of overhead. While it may be appropriate to watermark using this technique for lower-volume development or beta deployments, it may not be the best option for, say, applying a common copyright notice in footers of pages on a high-volume production site. The developer must make the decision as to whether the performance hit from this technique is worth the functionality for his or her own environment.

Additionally, this technique assumes that the server is buffering page output, so the complete page may be sent to the client in one call to Write (this is the default ASP.NET behavior). If page buffering is disabled (by setting Page.Response.BufferOutput to false), this technique may produce unexpected results.

It is also important to note that when configured in <httpModules>, the module receives all requests passed to it by the ASP.NET runtime. There may be situations, for example, an .aspx page that changes the content-type and outputs a binary stream, or web methods that return SOAP-formatted messages, in which you would want the module to be disabled, or at least to ignore the request. You may wish to modify the module source to check for the content type, or choose to place such pages in their own subdirectory, incorporating additional web.config files with <remove> tags in the <httpModules> section to disable the module entirely under those types of circumstances.

About the Demo Project

The demo project contains the file InsertionModuleBase.dll, which is a compiled version of all the custom classes mentioned in this article. Web.config files are used in both the main project directory and in subdirectories to configure the inclusion or exclusion of the WatermarkerInsertionModule for different page options.

Summary

Presented as an alternative to creating a base Page class, the WatermarkerInsertionModule provides an implementation of the IHttpModule interface for the purpose of applying watermark images and common headers and footers to web forms. It inherits from the class InsertionModuleBase in which is defined the consumption of the web application�s BeginRequest event, and the incorporation of a custom filter for the HTTP output stream. The InsertionFilterStream intercepts calls to the Write method, and applies the string replacement function defined in WatermarkerInsertionModule. Provided a developer�s environment allows for page buffering and the requisite performance hit that comes with string conversion and module overhead, the WatermarkerInsertionModule can be a convenient utility for performing automated watermarking on web forms.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here