|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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 BackgroundIn 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 As Kenny demonstrated, a simple At first thought, one might attempt to manipulate the HttpResponse.OutputStream[^] property directly to read from the HTTP stream and insert the required Filtering the HTTP StreamWhat is allowed is the filtering of the output stream. The Our filtering stream is defined by the 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 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 The final parameter for the constructor is an The most important method in our filtering 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 ModuleWith our The 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 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 ModuleAn insertion module may now be created by subclassing 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 Custom HTTP modules are included in the HTTP pipeline by adding items to the <httpModules>[^] section of web.config (which is nested within <add type="classname,assemblyname" name="modulename"/>
So to indicate the inclusion of the <configuration>
<system.web>
<httpModules>
<add type=”MyNamespace.SimpleInsertionModule,InsertionModules”
Name=”SimpleInsertionModule” />
</httpModules>
</system.web>
</configuration>
The WatermarkerInsertionModule ClassTo accomplish the original challenge, the inclusion of a watermark image using an HTTP module rather than a 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
Additional ConsiderationsWhile boiling down this replacement process to a single overridden 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 It is also important to note that when configured in About the Demo ProjectThe 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 SummaryPresented as an alternative to creating a base | ||||||||||||||||||||