Add an HTTP Header with an HttpModule

An HttpModule is a perfect way to add an HTTP header to every request in IIS. An HttpModule is an assembly that is called on every request (and as of IIS 7 this includes non-ASPNET requests). Other uses of HttpModules include tracing, logging, and security. We have a change coming up where having an HTTP header added to certain responses will be very helpful. I thought I would share my C# code for the HttpModule and some JavaScript that will use the new HTTP header on the client to display a message.

We are in the process of updating our online documentation to be leaner and meaner. Part of the update includes new filenames because there were instances where some overloaded functions created documentation page names longer than MAXPATH. However, there are a lot of links on the web that point to our current documentation. To ensure the process goes smoothly for all users, we are going to have a transition time. During the transition, we do not want to completely take down current documentation because some users are used to it and may not want to change. Instead, we are going to display a notice that newer documentation is available with a link.

To accomplish this, I am going to use an HttpModule that will map the request for the current documentation to the new file. It will return the result in the HTTP headers as the canonical link. This will serve two purposes: 1) let GoogleBot know that we have duplicate content, but we prefer that they index the new format, and 2) I can use the HTTP header to display notice to the user with a little bit of JavaScript.

Below is the code for the HttpModule. Keep in mind that some of it is very specific to our case and will only be used during the transition period. Once we are ready to take the old documentation down, I will update the module to send back a 301 redirection instead of a canonical link.


using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Web;

namespace DocumentationMapHttpModule
{
   public class CanonicalLinkHttpHeader : IHttpModule
   {
      public void Init( HttpApplication app )
      {
         app.BeginRequest += new EventHandler( OnBeginRequest );
      }

      public void Dispose() { }

      public void OnBeginRequest( Object s, EventArgs e )
      {
         var app = s as HttpApplication;
         string requestUrl = app.Context.Request.Path.ToLower();
         string extension = Path.GetExtension( requestUrl );

         if ( requestUrl.StartsWith( "/help/leadtools/v19/" ) && 
            ( string.Compare( extension, ".html", false ) == 0 || 
              string.Compare( extension, ".htm", false ) == 0 ) )
         {
            string canonicalLink = GetCanonicalLink( requestUrl );
            
            if ( !string.IsNullOrEmpty( canonicalLink ) )
               app.Context.Response.Headers.Add( 
                  "Link", 
                   string.Format( "<{0}>; rev=canonical", canonicalLink ) );
         }
      }
      private string GetCanonicalLink( string requestUrl )
      {
         string oldFileName = Path.GetFileName( requestUrl );
         string newFileName = GetNewFileName( oldFileName );

         if ( string.IsNullOrEmpty( newFileName ) )
            newFileName = oldFileName;

         string newUrl = requestUrl
            .Replace( "/v19/", "/v19m/" )
            .Replace( oldFileName, newFileName )
            .ToLower();

         return "https://www.leadtools.com" + newUrl;
      }

      private string GetNewFileName( string oldFileName )
      {
         string newFileName = null;
         string connectionString = Properties.Settings.Default.ConnectionString;

         using ( SqlConnection con = new SqlConnection( connectionString ) )
         using ( SqlCommand cmd = new SqlCommand() )
         {
            cmd.Connection = con;
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.CommandText = "select_new_name_from_documentation_filename_maps";
            cmd.Parameters.AddWithValue( "@oldFileName", oldFileName );
            con.Open();
            using ( SqlDataReader reader = 
               cmd.ExecuteReader( CommandBehavior.SingleResult | CommandBehavior.SingleRow ) )
            {
               while ( reader.Read() )
                  newFileName = reader.GetString( 0 );
            }
         }
         return newFileName;
      }
   }
}

As I mentioned before, we want to display a message to the user that there is a newer and better version of the documentation available. To do this, I use JavaScript to to make a request for the HTTP headers. This script will be placed just before the closing body tag.


var req = new XMLHttpRequest();
req.open('GET', document.location, false);
req.send(null);
var header = req.getResponseHeader("Link");

var regExp = /\<([^>]+)\>/;
var matches = regExp.exec(header);
if ( matches instanceof Array ){
    var canonicalLink = matches[1];
    var divAlert = document.createElement('div');
    divAlert.setAttribute("style", 
        "background:yellow; border: 1px dotted red;padding: 10px;");
    document.body.insertBefore(divAlert, document.body.childNodes[0]);

    divAlert.innerHTML = "This is old documentation and will not be updated. ";
    if (canonicalLink != ""){
        divAlert.innerHTML = divAlert.innerHTML 
        + "The new version of the documentation is located at this new link: <a href='" 
        + canonicalLink + "'>" + canonicalLink + "</a>";
    }
}

The result is a big bright message that let’s users know that there is a newer or better version of a web page available.

About 

Developer Advocate

This entry was posted in General and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *