Preventing Duplicate Record Insertion on Page Refresh or Postback of a Web Form
page 1 of 1
Published: 12 Sep 2005
Unedited - Community Contributed
Abstract
Jeremy Schell provides a free FormValidator server control, and shows how to use it to detect whether the page has been refreshed or the form resubmitted, allowing the ASP.NET developer to guard against duplicate database record insertion.
by Jeremy Schell
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 26166/ 99

Introduction

One of the biggest challenges for Internet developers is the resubmission of form data or the refresh of a form to post information to a database. This happens when the user, for whatever reason, resubmits or refreshes the form, which has the potential to cause chaos with duplicated database insertions or repeated email submissions. There have been a number of theories presented on how to deal with the problem; one of the most thorough I’ve read being from Terri Morton and located at www.aspalliance.com/687. It is not my intent to dispute which of these methods is the best, but instead to offer an alternative approach.

History

Several years ago I was challenged by a company that was performing online contest submissions to limit users to one submission per “typed” entry. Additionally, they wanted to prevent mass submission companies from flooding their contest with unprospected candidates and automated entries. After looking at many options, I decided to create a single-use formKey that I could assign and track at the server level for each user every time a form was loaded. Once the form was submitted, I would confirm the existence of the formKey for that form and allow the submission to be processed. At that point the key would be purged from the system and could no longer be used. Then, if the user hit the browser's Refresh button or tried to repost the submission, I could handle the repost in any manner knowing it was a repeat submission. In cases where a developer is using a multi-step form on a single web page, this process can be integrated easily to maintain each step of the form submission process. To expand upon our solution, we’ve integrated it into an ASP.NET server control that can easily be dragged onto a web form in Visual Studio and then attached from the code behind to authenticate the postback as a single submission, making it easy for any developer to integrate.

The Process

The overall process works as follows.

  1. During initial page load, register the web form and generate a unique formKey, e.g., “A”.
  2. Once the page is completed and a postback is performed, formKey “A” exists within the POST form collection. We authenticate this formKey against the database for the web form to confirm it is valid. Once confirmed, the page processing is allowed to continue and the formKey is deleted. If the user presses the browser's Refresh button, or presses the Back button and then reposts the data, formKey “A” will again be contained within the POST form collection and will no longer authenticate within the database.
  3. Simultaneously, a new formKey “B” is generated for the next possible postback and formKey “A” will no longer function. This is necessary for multiple-step forms, and upon postback formKey “B” can be properly authenticated.
  4. This process repeats itself each time the screen is refreshed or a postback is performed.

Because this solution required the use of a database, we also wanted the solution to be scalable to all of our clients and their forms, so we updated the database and server control to allow a single control to manage multiple sites and multiple forms on each site. As each site and form is called, the server control registers the web site and web form within the database for future use and tracking.

Implementation

All formKeys are created by the server control and then registered with the database for use on each form requested by the user. The database consists of three tables: Sites, Forms, and FormKeys. Each record within the Sites table maintains a unique hostname where forms are submitted from. Similarly, the Forms table maintains each URL where a form is submitted from. Finally, the FormKeys table tracks each formKey provided by the server control, the user’s IP address, and the specific Site and Form in which the key is valid. This is all passed through the stored procedure “spInsertFormKey” when the form is originally requested. Currently the FormValidator maintains keys for one day but the system can easily be configured from the stored procedure “spAuthenticateFormKey” to enforce a shorter life cycle for each key.

Upon each postback the code calls the ValidatePostback function which provides a Boolean response indicating whether the formKey provided on the form was valid for the current submission location. If false, either the form is a repeat submission or it was an automated submission from some other source. When the ValidatePostback function is called, the server control calls the “spAuthenticateFormKey” procedure providing the user’s IP, the formKey as found in the form collection, the hostname, and the filename of the form. This is compared against previous submissions. If a match is found a successful response is sent to the server control and the previous formKey is removed from the database.

Usage of the server control is extremely simple. Add the following four keys to your web.config file with the appropriate database connection string information.

Listing 1 – Web.Config Changes

<add key="FV_dsn_Server" value="[SERVER NAME OR IP]"/>
<add key="FV_dsn_Database" value="FormManager"/>
<add key="FV_dsn_Username" value="[USERNAME TO CONNECT TO SQL SERVER]"/>
<add key="FV_dsn_Password" value="[PASSWORD TO CONNECT TO SQL SERVER]"/>

You can also add to the web.config file a key named FV_EnforceValidation and set its value to False, which will disable all form validation on a site so that you can test submit/resubmit behavior without having to register a formKey. Be sure to either remove this key or set its value to True once you have finished testing, or it will not protect against duplicate submissions.

When your code handles a postback, just wrap your actual processing within the following code:

Listing 2 – Postback Validation

Dim validator As New TectonicConcepts.FormValidator.FormValidator
If validator.ValidatePostback = True Then
    ' Do something with the form
EndIf

If you have code to perform a redirection, display a thank you message, or proceed to the next step in a multi-form page, you’ll want to place that code outside of this validator block. For example, on our contact form we do the following: on the first postback we send an email, but on a subsequent refresh or a postback that is not valid, we don’t send an email but still display the thank you message.

Listing 3 – Sample Postback with Validation

Private Sub Submit_Click(sender As Object, e As EventArgs)
    If Page.IsValid Then
        Dim validator As New TectonicConcepts.FormValidator.FormValidator
        If validator.ValidatePostback = True Then
            Message.Text = "Form data is new, and will be processed"
        Else
            Message.Text = "Form data is a resubmission, and will not be processed"
        End If
    Else
        Message.Text = "Form data is not valid, and will not be processed"
    End If
End Sub

Conclusion

If you download the FormValidator server control and its accompanying database setup script, you will have an easy way to check whether the page has been refreshed or the form resubmitted. You can perform this check before performing sensitive actions, such as making a database insertion or sending an email. It couldn't be easier.



User Comments

Title: ValidatePostBack never returning true   
Name: Sidsoft
Date: 2007-05-18 4:13:53 PM
Comment:
I had to add the control to the form in the designer in order to get it to work. Simply declaring it as shown in the article did not work.

Does the job nicely.
Title: ValidatePostBack never returning true   
Name: Sidsoft
Date: 2007-05-18 3:53:16 PM
Comment:
It seems that the control is never "priming the pump" so to speak; I've turned on sql profiler and I can see spAuthenitcatFormKey being called but always with @formkey=''. At what point should the control initiate the sequence by establishing the valid formkey for the current "round trip". I have tried decalring the formvalidator instance variable at both the class level and at within my form submit event handler - no joy.
Title: Here is another solution to postback prevention...   
Name: Joshua Felix
Date: 2006-12-05 2:18:20 AM
Comment:
Here is the solution for preventing data to be reposted to the database on a page-Refresh event:

in the code behind....

Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender

DataList1.DataBind()
If IsPostBack Then
Response.Redirect("redirect.aspx") ' redirect has Response.Redirect("Default.aspx") in the code-behind file
End If
End Sub

btw- I think this will work for any data view control
Title: Source for control   
Name: Bob Gillette
Date: 2006-03-06 5:13:45 PM
Comment:
Hi Jeremy,

I, too, am very interested in taking a look at the code behind this server control. Any chance you could make it available? Thanks.
Title: Jayakanthan   
Name: aapNetPro
Date: 2005-11-18 8:25:58 AM
Comment:
A simple use of the "back" browser button and resubmitting the original form by an online user after a Response.Redirect will cause an additional submission. Your solution definitely prevents this problem from occuring.
Title: Jayakanthan   
Name: Jayakanthan
Date: 2005-11-17 5:35:30 PM
Comment:
We had same issue, Solved by simple Redirect.

After the Submit process:
Response.Redirect("sample.aspx")

This will not cause the postback to trigger the Submit event again.
Title: I want to know how to do this form   
Name: DJ
Date: 2005-10-10 6:22:47 PM
Comment:
Can you make this code for this form and posting available?
Title: Improvements   
Name: Bilal Haidar [MVP]
Date: 2005-09-15 9:36:32 AM
Comment:
Hello Jeremy:
We have had a detailed discussion on this topic a month or more if you still remember.
I was able to implement a similar solution using the HttpModule, which can be plugged into any ASP.NET application.
I developed it in ASP.NET 2.0, I am still waiting your comments on it ;)

Regards,
Good Job
Title: state maintenance   
Name: Jeremy Schell
Date: 2005-09-13 9:43:43 AM
Comment:
John,
Those are certainly options, here are some concerns for each.

Session variables are not always supported by all companies or ISP's. Also, with session variables you would need to create an array to track the form, step within the form and then time of the submission. Next problem would be what if the person truly does need to recomplete the form, the session variable existance may prevent that. Here we're just trying to prevent refreshes and automated submissions. Also keep in mind that session variables are lost when web.config is changed, applications are recompiled and when application pools are recycled.

Hidden text fields have the same problem so long as they do not have a legend to associate their values against. This can become a further problem when using hidden fields with multi-step web forms.

Storing within the activeState has many of the same problems referenced in the above two scenarios. Also, on a refresh or additional postback the activeState is still in the same state as the previous post and will allow the repeat submission. You could combine each of these to produce redundant checks but the database mechanism appears to be a simplier, more elegant solution.
Title: state maintenance   
Name: John Hopper
Date: 2005-09-13 9:09:43 AM
Comment:
Jeremy,

Rather than use a database could you write the key to the page's activeState collection? Or, worse, a session variable? Or even a hidden text field?

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2020 ASPAlliance.com  |  Page Processed at 2020-09-25 9:43:19 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search