Click here to Skip to main content
Click here to Skip to main content

Page Refresh Detection Using HttpModule

, 17 Feb 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
How to detect and handle a page refresh using only an HttpModule

Introduction

This article describes the detection and handling of page refreshes using only an HttpModule.

Background

Critical operations done by web pages (e.g. writing stuff to databases, creating users, or deletion of stuff) should be secured from various actions. These actions are, for example, unauthorized access or multiple execution by simply refreshing the page. When I got into the situation (and also the project) that the refreshing issue came up, the project was already way too big (200+ pages, different kinds of users, etc.) to do anything on a per-page basis.

My colleagues already tried to convince our customer to not use F5 or the Refresh button of their browsers. They agreed, but users make mistakes as everyone knows, so from time to time, they do a page refresh. Since our logging is quite extensive, we were able to detect the error that occurred due to the refresh, but it is not satisfying to discuss with the customer over and over again about this issue.

However, I decided to write an HttpModule that covers this topic somehow. So, I read articles about page refreshing detections on MSDN, and this one: Using an HttpModule to detect page refresh, for instance. But they all required additional work on the web pages. In my case, it was totally OK to redirect refreshed pages, so I decided to take the basic idea on refreshing detection and put it in my own module.

About the Code

The code is written for .NET 1.1, but should work in later versions of .NET also. However, I have not tested it on later versions.

Basic Idea

The basic idea on the solution is to write a hidden field in the page containing a unique key right before the page is transmitted to the client.

Using the Code

Compile the code and add the resulting library to the web.config file, as follows:

<httpModules>
  <add name="RefreshDetectionModule" type="HttpModules.RefreshDetectionModule"/>
</httpModules>

Page-refresh Detection, Step One

To differ an HTTP-POST from another one, I decided to stick with the idea of injecting a (more or less) unique ID in every page that gets sent to the client. To achieve this, I wrote my own class that inherits from the Stream class and hooked it to the Response.Filter.

private void application_PreRequestHandlerExecute(object sender, EventArgs e)
{
       HttpApplication application = (HttpApplication)sender;
       HttpContext context = application.Context;
       //write the hidden field only if the request is made to the aspx-handler
       if(context.Request.Path.ToLower().EndsWith(".aspx"))
       {
         //attach the stream that writes the hidden field
         application.Response.Filter =
           new RefreshDetectionResponseFilter(application.Response.Filter,
           Guid.NewGuid());
       }
}

The stream-class (RefreshDetectionResponseFilter) basically just needs to override the Write-method. I write the whole stream to a StringBuilder and search in the resulting HTML-text for the form tag. The idea is to place the hidden field right behind the form tag. Once the whole stream is read and stored in the StringBuilder, I start searching for the form-tag by using a Regular Expression. Having the form-tag found, I append the hidden field to it.

public override void Write(byte[] buffer, int offset, int count)
{
     //Read the buffer from the stream
     string sBuffer = UTF8Encoding.UTF8.GetString(buffer, offset, count);
     //when the end of the html-text is read
     if (endOfFile.IsMatch(sBuffer))
     {
       //append the buffer
       html.Append(sBuffer);
       //and fire the matching for the start of the form-tag
       //the form tag contains various additional attributes, therefore
       //a non-greedy expression is used to find the whole opening tag.
       MatchCollection aspxPageMatches =
         Regex.Matches(html.ToString(),"<form[^>]*>",RegexOptions.IgnoreCase);
       //When a form-tag could be found
       if(aspxPageMatches.Count > 0)
       {
           StringBuilder newHtml = new StringBuilder();
           int lastIndex = 0;
           //usually only one form tag should be
           //inside a html-text, but who knows ;)
           for(int i = 0; i < aspxPageMatches.Count; i++)
           {
               //Get the text up to the form tag.
               newHtml.Append(html.ToString().Substring(lastIndex,
                              aspxPageMatches[i].Index -lastIndex));
               //get the opening form-tag
               string key = aspxPageMatches[i].Value;
               //generate the new hidden field
               string enc = string.Format("\r\n<input id=\"{0}\" type" +
                      "=\"hidden\" name=\"{0}\"  value=\"{1}\"/>",
                      HIDDEN_FIELD_ID, guid);
               //write both the the html-text
               newHtml.Append(key+enc);
               lastIndex = aspxPageMatches[i].Index +
                           aspxPageMatches[i].Value.Length;
           }
           //append the rest of the html-text
           newHtml.Append(html.ToString().Substring(lastIndex));
           html = newHtml;
       }
       //write the whole text back to the stream
       byte[] data = UTF8Encoding.UTF8.GetBytes(html.ToString());
       responseStream.Write(data, 0, data.Length);
   }
   else
   {
       //when the end of the html-text is not found yet,
       //write the buffer to the stringbuilder only
       html.Append(sBuffer);
   }
}

When the hidden field is appended, I write everything back to the stream.

Page-refresh Detection, Step Two

Now that all the pages contain the hidden field, I just need to look out for the value of the hidden field, once the page is posted back. To do so, I just hook up to the BeginRequest-event of the HttpModule and look in the posted form for the hidden field. If the value of the hidden field has been posted before, I know that this post is just the refreshing of a previously posted page. In that case, I just do a redirect to the logout-page.

If the value has not been posted before, I write the value to a list (I use a queue for easily dequeueing items when the list exceeds its maximum size).

private void application_BeginRequest(object sender, EventArgs e)
{
    HttpApplication application = (HttpApplication)sender;
    HttpContext context = application.Context;
    string s = "";
    //Refreshing is only prohibited of the request is a post-request.
    if(context.Request.HttpMethod.ToUpper().Equals("POST"))
    {
        //Get the guid from the http-post form
        if(context.Request.Form!=null)
            s = context.Request.Form[RefreshDetectionResponseFilter.HIDDEN_FIELD_ID];
        //if the guid is already in the queue the post is a refresh
        if(q.Contains(s) && s.Length>0)
        {
            //refresh -> Redirect to any other page
            context.Response.Redirect("Logout.aspx");
            context.Response.Flush();
            context.Response.End();
        }
        //when the queue-size exceeded its limit (queueSize), guids will be
        //removed from the queue until the queue size is lower than the limit.
        while(q.Count>=queueSize)
            q.Dequeue();
        //since the post is not a refresh the guid is written to the queue
        q.Enqueue(s);
    }
}

Conclusion and Personal Notes

I wrote this module just to provide an easy way of detecting page-refreshes. In my case, it works good. I'm quite sure there is much to improve, so if anyone has ideas for improvements, I'll be happy to hear them. This is my first article, so please excuse me if I have made any mistakes.

History

  • 16th February, 2009: Initial post
  • 17th February, 2009: Updated article and added source code

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jens Meyer
Software Developer
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionPage Refresh Detection Using HttpModule PinmemberMember 366988926-Jul-12 0:10 
Generalnice.. thanks. Pinmembermk.developer30-Nov-10 10:11 
Generaldetect page refresh PinmemberGangadhar Mahindrakar18-Feb-09 20:14 
GeneralRe: detect page refresh PinmemberJens Meyer18-Feb-09 21:28 
GeneralRe: detect page refresh [modified] Pinmembersiphu18-Feb-09 23:52 
GeneralRe: detect page refresh PinmemberJens Meyer19-Feb-09 2:21 
Thanks siphu,
 
in the context of a POST we know that the request is based on a button for example. But the context is also a POST if you pressed F5 after a POST (eg. a button press).
Since I wanted to redirect a refresh/F5 only I had to differ between a button press or a refresh/F5. To do so I injected a GUID in the previous page that was sent to the client. When I receive a request containing a GUID that I already received I know that the request is a refresh/F5. A refresh/F5 of a post sends the same data to the server again including the same GUID that i injected when I sent the page to the client.
 
In my case only the POST-Requests are relevant, coz they are the only ones that do critical operations (e.g. DB update/insert/delete or starting next level services).
 
I hope this makes more sense to you now.
 
Best regards
 
Jens
 
When in trouble,
when in doubt,
run in circles,
scream and shout

GeneralRe: detect page refresh PinmemberPhu Nguyen Si21-Feb-09 19:05 
GeneralWhat's the diffrent with SimoneB's solution Pinmemberguaike18-Feb-09 17:22 
GeneralRe: What's the diffrent with SimoneB's solution PinmemberJens Meyer18-Feb-09 21:40 
QuestionSource code? PinmemberMichael B. Hansen17-Feb-09 0:40 
AnswerRe: Source code? PinmemberJens Meyer17-Feb-09 1:11 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411023.1 | Last Updated 17 Feb 2009
Article Copyright 2009 by Jens Meyer
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid