Click here to Skip to main content
13,548,587 members
Click here to Skip to main content
Add your own
alternative version


128 bookmarked
Posted 1 Dec 2003

Watermark Website Images At Runtime

, 1 Dec 2003
Rate this:
Please Sign up or sign in to vote.
The article describes how HTTP Handlers can be used to place a watermark on images at runtime.


This article seeks to demonstrate a potential use of the ASP.NET Pipeline architecture. The Handler technique of dealing with requests on the server-side is adopted to place a watermark string on all images sent to the client. The original image is however not modified in the process. A copy of the image is created and modified and flushed into the output stream connected to the client browser. If your website offers a picture gallery, this method could be used to put a custom message (watermark) into every image that gets rendered on the client browser. More often than not, users download images and forget where they came from. The technique employed by the code in this article can serve to effectively advertise the source of the image. With a little imagination, the basic idea can be used to provide custom captions for images as well. One could come up with a myriad of uses for this technique.


Old ASP was based on sending a response for every client request that makes its way through several ISAPI filters installed on the IIS. These ISAPI filters and extensions required to be coded in C++, and hence was not widely adopted to follow, although, they offered great benefits by adding more punch to the services offered by the web server. However, ASP.NET takes the focus away from ISAPI and introduces the concepts of handlers and modules to meet this end. Read on, people!

Requests are received by IIS and passed to the ASP.NET Worker Process (aspnet_wp.exe) by an ISAPI filter (provided by ASP.NET) called aspnet_isapi.dll. This filter re-routes the request to the worker process, thereby bypassing a lot of IIS features in favor of those offered by the CLR and ASP.NET. The worker process dispatches HTTP requests through a pipeline which contains several modules that can modify and filter the requests. From that point on, the request is wrapped up into an instance of HttpContext and piped through a number of ASP.NET classes that implement the IHttpModule interface. There are a number of system-level HTTP modules, providing services ranging from authentication to state management to output caching. The number of modules that get to intercept the request is based upon settings within the host machine's machine.config file and the application's web.config file. In classic ASP, this role of providing pre- and post-processing fell upon ISAPI filters.

The ASP.NET pipeline represents a series of extensible objects that work in a sequential-chain and act on an incoming request, one after the other. As the requests pass through the pipeline, they are modified and filtered until they are finally handed over to a handler object that emits a suitable response back to the client. Through the use of these modules and handlers, we can effectively extend the capabilities of our web server, just like what ISAPI extensions and filters are used to do for IIS.

Every incoming request will have a URI. ASP.NET allows us to map every single URI to a specific handler class that will send a suitable response. A URI that is not mapped to a specific handler class will be passed to ASP.NET's default handler. This default handler will target the URI as a file name, loading the file specified within the URI.

By default, requests for a ‘.aspx’ page are handled by a compiled class that inherits from the Page class (the request handler in this case), which implements the IHttpHandler interface. If we want another handler to cater to a request, we need to map the request to the desired handler class in the web application's configuration files, and also instruct the ASP.NET ISAPI extension to look out for the particular request. Classes implementing IHttpHandler can hook into the HTTP pipeline and service requests through the interface's ProcessRequest method.

The ASP.NET ISAPI extension will only pick those URI requests, it has been mapped or configured to acquire. This configuration has to be done in the IIS web server configuration property sheet.

This article will focus only on one potential use of HTTP handlers.


To create a class that acts as a HTTP handler, we must implement the IHttpHandler interface. The interface has two prominent members that we must implement in our class: -

  1. Sub ProcessRequest (context)- called by ASP.NET to perform processing on the request context.
  2. Property readonly IsReusable as Boolean- called by ASP.NET to determine if our handler class can be reused for multiple requests.

Session State In HTTP Handlers

An HTTP handler does not have access to session state variables and objects by default. To acquire this privilege, the handler class also needs to implement either one of the following interfaces depending on the extent of access required: -

  1. IRequiresSessionState-implement if both read and access to session state is required.
  2. IReadOnlySessionState-implement if only read access to session state is required.

Both the above interfaces do not have any member signatures to implement. They simply serve as markers or flags for the ASP.NET engine to determine the degree of access to session information that must be provided to the Handler object.

Registering a Handler With The Application Configuration Files

When the ASP.NET engine receives a request, it will decide on which handler to invoke, by screening for <HttpHandler> elements in the web.config file. This is what the element entry should look like in a configuration file: -

<add verb="" path="" type="" validate=""/>

Attribute-Value Options

  1. verb=["comma-delimited list of HTTP verbs like GET, PUT, POST" | script-mapping characters or strings].

    e.g. verb="*", verb="GET, PUT". The verb attribute is used when you want to restrict requests via 'POST' or 'GET' or 'HEAD'. You'll just want to stick with a '*' - this will allow all of the above.

  2. path=["single URL path" | "wildcard strings"].

    e.g. path="*.aspx", path="resource/"

  3. type="fully-qualified class name, assembly name"

    e.g. type="Namespace.Class, AssemblyName"

  4. validate=[true | false]-- If false, then ASP.NET will not load the class at startup until a matching request is received.

Using HTTP Handlers To Add watermark to images

Here, we will attempt to insert a custom watermark into every image file that is requested. To follow this sample, I expect you to have a basic idea of how to use the System.Drawing namespace and classes. For the uninitiated, you use either a brush or a pen to draw on a canvas (to put it bluntly!).

Using the code


This class is our HTTP Handler. You could compile this class as a separate DLL assembly or as a part of another is your call. However, note the name of the class, the namespace it belongs to and the assembly it is packaged in, as these pieces of information prove vital at the handler registration stage. The ASP.NET worker process passes the HttpContext object (that wraps the request) to the ProcessRequest routine. We obtain the physical path of the requested image on the server, and proceed to apply a watermark to the image. The resultant image is then written to the output stream of the Response object. Follow the comments in the ImageWatermark class to comprehend its logic.

Imports System.Web
Imports System.Drawing

Public Class ImageHandler
    Implements IHttpHandler

    Public ReadOnly Property IsReusable() _ 
      As Boolean Implements IHttpHandler.IsReusable
        'We dont want this class to be reused simply 
        'because we dont want other requests to wait and lie pending 
            'suggests that an instance of this class cannot 
            'be reused to serve more than one request.
            Return False 
        End Get
    End Property

    Public Sub ProcessRequest(ByVal context _ 
      As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim output As ImageWatermark = _ 
          New ImageWatermark(context.Request.PhysicalPath)
        output.AddWaterMark("This is the custome string")
        output.Image.Save(context.Response.OutputStream, _ 
    End Sub

    Private Class ImageWatermark
        Private bmp As Bitmap
        Public Sub New(ByVal physicalPathToImage As String)
            bmp = New Bitmap(physicalPathToImage)
        End Sub
        Public Sub AddWaterMark(ByVal watermark As String)
            'get the drawing canvas (graphics object) from the bitmap
            Dim canvas As Graphics
                canvas = Graphics.FromImage(bmp)
            Catch e As Exception
                'You cannot create a Graphics object 
                'from an image with an indexed pixel format.
                'If you want to open this image and draw 
                'on it you need to do the following...
                'size the new bitmap to the source bitmaps dimensions
                Dim bmpNew As Bitmap = New Bitmap(bmp.Width, bmp.Height)
                canvas = Graphics.FromImage(bmpNew)
                'draw the old bitmaps contents to the new bitmap
                'paint the entire region of the old bitmap to the 
                'new bitmap..use the rectangle type to 
                'select area of the source image
                canvas.DrawImage(bmp, New Rectangle(0, 0, _ 
                   bmpNew.Width, bmpNew.Height), 0, 0, _ 
                   bmp.Width, bmp.Height, GraphicsUnit.Pixel)
                bmp = bmpNew
            End Try
            canvas.DrawString(watermark, _ 
              New Font("Verdana", 14, FontStyle.Bold), _ 
              New SolidBrush(Color.Beige), 0, 0)
        End Sub

        Public ReadOnly Property Image() As Bitmap
                Return bmp
            End Get
        End Property
    End Class
End Class

At this point, I should probably bring your attention to the exception handling block employed in the AddWaterMark routine of the ImageWatermark class. It was not part of my original idea, simply because I did not expect to encounter the following error message:

Error: An unhandled exception of type 
  'System.Exception' occurred in system.drawing.dll
Additional information: A Graphics object cannot be 
  created from an image that has an indexed pixel format...

As it turns out, a GIF image with an indexed pixel format does not allow its color palette to be modified. As a workaround, we draw the contents of the image into a new Bitmap class and proceed with our operation on the new instance. This error really annoyed me until I found out! Thanks to the Internet!

Registering the ImageHandler class

This part is easy. Just add the following XML section to the <System.Web> element of your web.config file. Note that I have registered the handler to only deal with requests for GIF and JPG files. The assembly name provided is Home. You should replace it with the name of the assembly you compile the handler class into.

<add verb="*" path="*.jpg,*.gif" type="ImageHandler,Home" validate="false"/>

Configuring IIS to RE-route requests to ASP.NET ISAPI filter

This is the last and most critical step to getting this whole exercise to work. We need to tell IIS to pass requests to files with .jpg and .gif extensions to ASP.NET's very own ISAPI filter-aspnet_isapi.dll. To do so, we have to map the extensions to the filter process. This is done in the Application Configuration property sheet of your website or virtual directory.

Pump up the Internet Services Manager, and access your web or virtual directory properties.

  1. In the Application Settings groupbox, find and click the Configuration button.
  2. In the next window, select the Mappings tab and click Add to create a new mapping.
  3. On this dialog, set the Executable to the aspnet_isapi.dll file in the .NET folder (you can just copy this from another .NET extension).
  4. Then set the Extension to whatever you set in web.config.
  5. We should sensibly limit this handling only to GET requests for files of the extension in question.
  6. Press OK and you are done!

Repeat steps 2 to 6 for each extension you wish to handle.

With that out of the way, load up your favorite browser and try accessing any image file, or any page that contains graphics pulled from your website, or a virtual directory on your website, depending on your IIS configuration.

And that's that! Please feel free to contact me at if you have any query or feedback to impose on my humble self.

Seasons Greetings to one and all!


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


About the Author

Web Developer
India India
Jaison John is currently working as a Software Engineer at Net Asset Management,India. He is an Electrical & Electronics engineering graduate from India with over 4 years of development experience behind him.His main areas of interest have been Visual Basic (both version 6 and .NET flavors),ActiveX, DHTML and most recently and Winforms Development in C#/

You may also be interested in...

Comments and Discussions

Question1and1 shared server Pin
Member 1096899625-Jul-14 3:09
memberMember 1096899625-Jul-14 3:09 
GeneralMy vote of 2 Pin
Akshit Soota2-Dec-08 1:52
memberAkshit Soota2-Dec-08 1:52 
Generali cant see any image Pin
yucel_kandemir15-Oct-08 0:22
memberyucel_kandemir15-Oct-08 0:22 
Questionusing wildcard values in the filename. [modified] Pin
childg11-Mar-08 9:46
memberchildg11-Mar-08 9:46 
Questionhow to improve quality Pin
SaferSephy10-Oct-07 21:19
memberSaferSephy10-Oct-07 21:19 
QuestionConfiguration Error [modified] Pin
XPos6-Feb-07 8:32
memberXPos6-Feb-07 8:32 
AnswerRe: Configuration Error Pin
sqlgo11-Feb-07 10:24
membersqlgo11-Feb-07 10:24 
AnswerRe: Configuration Error Pin
woonboon28-May-07 21:56
memberwoonboon28-May-07 21:56 
QuestionA very Little Problem! Pin
Web Guru..!15-Sep-06 2:22
memberWeb Guru..!15-Sep-06 2:22 
GeneralAdding watermark to databse renderd image Pin
rbygrave11-Aug-06 14:06
memberrbygrave11-Aug-06 14:06 
QuestionCan we decide where to add text? Pin
uy_do28-Jul-06 6:26
memberuy_do28-Jul-06 6:26 
Questionhow to call Pin
nandhucbm17-Jan-06 19:14
membernandhucbm17-Jan-06 19:14 
QuestionLatest Copy of the Code? Pin
noveljosh3-Aug-05 11:38
membernoveljosh3-Aug-05 11:38 
GeneralQuality Issues Pin
Suman Mukherjee28-Jul-05 3:23
sussSuman Mukherjee28-Jul-05 3:23 
GeneralRe: Quality Issues Pin
Roy Oliver4-Feb-07 12:02
memberRoy Oliver4-Feb-07 12:02 
GeneralCould not load type ImageHandler from assembly Watermark. Pin
tvhouten21-Jun-05 10:37
membertvhouten21-Jun-05 10:37 
GeneralRe: Could not load type ImageHandler from assembly Watermark. Pin
woonboon28-May-07 21:52
memberwoonboon28-May-07 21:52 
GeneralRe: Could not load type ImageHandler from assembly Watermark. Pin
woonboon28-May-07 21:53
memberwoonboon28-May-07 21:53 
GeneralExcellent Pin
Thomas Kurek10-Jun-05 11:51
memberThomas Kurek10-Jun-05 11:51 
QuestionFirefox? Pin
cvidler23-May-05 3:50
membercvidler23-May-05 3:50 
AnswerRe: Firefox? Pin
cvidler23-May-05 4:01
membercvidler23-May-05 4:01 
fixed with this code:

Dim oMem As New MemoryStream

Select Case LCase(Right(context.Request.PhysicalPath, 3))
    Case "jpg"
        output.Image.Save(oMem, Drawing.Imaging.ImageFormat.Jpeg)
        context.Response.ContentType = "image/jpeg"
    Case "gif"
        output.Image.Save(oMem, Drawing.Imaging.ImageFormat.Gif)
        context.Response.ContentType = "image/gif"
    Case "png"
        output.Image.Save(oMem, Drawing.Imaging.ImageFormat.Png)
        context.Response.ContentType = "image/png"
End Select


edit: This code also fixes the problem with PNG files. They need to write to a 'seekable' stream, the output stream doesn't support seeking, so I used a memory stream.
GeneralRe: Firefox? Pin
KingLeon18-Jun-05 18:31
memberKingLeon18-Jun-05 18:31 
GeneralError during delete Pin
smartnfast12-Jan-05 23:40
membersmartnfast12-Jan-05 23:40 
GeneralRe: Error during delete Pin
KingLeon15-Jan-05 3:29
sussKingLeon15-Jan-05 3:29 
GeneralRe: Error during delete Pin
KingLeon15-Jan-05 3:30
sussKingLeon15-Jan-05 3:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04-2016 | 2.8.180515.1 | Last Updated 2 Dec 2003
Article Copyright 2003 by KingLeon
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid