Introduction
As with many Internet developers one of the biggest challenges and end user frustrations is the resubmission of data or refresh of a form to post information to a database by an unaware user. Quite often this happens and the user for whatever reason will resubmit or refresh the form and potentially cause chaos with database and/or email submissions. There have been a number of theories presented on this subject, one of the most thorough I�ve read being from Terri Morton. It is not my intent to dispute which of these methods is best but instead to offer an alternative suggestion.
History
Several years ago I was challenged by a company which 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 un-prospected candidates and automated entries. After looking at many options, I concluded 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 hits refresh or tries to repost the submission, I could handle the repost in any manner knowing it was a repeat submission. In the cases when a developer is using a multi-step form on a single web form, this process can easily be integrated to maintain each �step� of the form submission process. To expand upon our solution, we�ve integrated it into a VS.NET toolbox server control that can easily be dragged onto a web form 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:
- Initial page load � Register the web form and generate a unique formKey, e.g. "A".
- Once the page is completed and a postback is done, formKey "A" exists within the POST form collection. We authenticate this formKey against the database for the web form to confirm it is validated. Once confirmed, the page processing is allowed to continue and the formKey is deleted. If the user presses Refresh within the browser or their Back button and reposts the data, formKey "A" will again be contained within the POST form collection and will no longer authenticate within the database.
- 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.
- This process repeats itself each time the screen is refreshed or a postback is initiated.
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 user requested form. 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, user�s IP address, and the specific Site and Form in which the key is valid. This is all passed through the stored procedure "spInsertFormKeymKey
" 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 developer calls the ValidatePostback
function which provides a Boolean response (True
/False
) of whether the formKey provided on the form was valid or not 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, formKey as found in the POST/GET collection, hostname and file name of the form. This is compared against previous submissions. If one matches, a successful response is sent to the server control and the previous formKey removed from the database.
Usage of the server control is extremely simple, add the server control to your VS.NET or WebMatrix toolbox and drag the control onto your web form. For best implementation, place it between the FORM
tags but outside any possible hidden panels, tables or other components that may change throughout the lifecycle of your form. Next, add the following four keys to your web.config with the appropriate connection string information to your database.
<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 the following key FV_EnforceValidation
to the web.config and set it to False
to disable all form validation on a site for your testing purposes so that you can submit/resubmit without having to register a formKey. Be sure to either remove this key or set to True
once you are done testing or it will not protect against duplicate submissions.
When you do a postback just wrap your actual processing within the following code:
Dim validator As New TectonicConcepts.FormValidator.FormValidator
If validator.ValidatePostback = True Then
EndIf
If you have a redirect or thank you display message or processing to a next screen, you�ll want to place that outside this code block. For example on our contact form, we do the following: on the first postback we send an email but on subsequent refresh of invalid postbacks we don�t send an email but still display the Thank you panel.
Private Sub subForm_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles subForm.Click
If Page.IsValid Then
Dim validator As New _
TectonicConcepts.FormValidator.FormValidator
If validator.ValidatePostback = True Then
Dim mail As New _
TectonicConcepts.TemplateMailer.SendTemplateEmail
mail.SendTemplateMail(_
"Response from the Tectonic Concepts Website", _
"MailTemplates/ContactUsForm.txt", "", _
CMS.Settings.ContactFormSender, _
CMS.Settings.ContactFormRecipient, _
CMS.Settings.ContactFormBccRecipient, _
True, True, True, False, Me.Controls)
End If
ContactForm.Visible = False
Contactus_Intro.Visible = False
ContactUs_ThankYou.Visible = True
EndIf
End Sub
The database setup script and FormValidator
server control can be downloaded here.