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

Automatically Saving Web Form Data

, 24 Sep 2006
Rate this:
Please Sign up or sign in to vote.
How to automatically save user input in web applications.

Introduction

This article explains how to automatically save user input in the background whilst the user is filling forms. This is particularly useful for large forms and you don't miss the data if the user closes the browser without saving the form, or when they navigate away from the web page or the browser crashes. The technique used here can also be used to implement roaming user session for an authenticated web user, i.e., the user rebinds to an existing session if he logs in again prior to session expiration - even if he logs in from another system. This functionality is somewhat similar to ASP.NET 2.0 profiling.

The Idea

The idea is to send user data to a web server on a periodic basis where it is stored in memory and later flushed to the database for persistent storage.

JavaScript is used to monitor the user input in the browser and fill in a hash table with the user input. On specified periods, the hash table is serialized as a query string and sent to an ASPX page (AutoSave.aspx) using an XmlHttp object. AutoSave.aspx populates an in memory object with the values from the query string. After a specified period, usually at session timeout, the values are flushed to the database.

The Implementation

  1. Bind the OnBlur event of all input controls to populate a hash table when the user enters data in a form.
  2. Submit user data to the server by calling the AutoSave() function. The XmlHttp wrapper implementation is from http://www.codeproject.com/Ajax/AJAXWasHere-Part1.aspx.
  3. Call AutoSave just before the browser is closed by the user, i.e., window.onbeforeunload event.
  4. //WebForm1.aspx
    
    <script defer="defer" language="javascript">
    
        bindEvents(); //binds onblur events,  onchange for DropDown Lists 
    
    
        var xmlHttp; 
        xmlHttp = GetXmlHttpObject(CallbackMethod); 
                        
        function AutoSave()
        { 
            if (!Data.isEmpty()) {
                qstring = Data.toQueryString();
                SendXmlHttpRequest(xmlHttp, "AutoSave.aspx?" + 
                                   qstring.substring(0,qstring.length-1)); 
                Data.clear();
            }
        } 
            
        function CallbackMethod() 
        { 
            try
            {
                //readyState of 4 or 'complete' represents 
    
                if (xmlHttp.readyState == 4 || xmlHttp.readyState == 'complete')
                {
                    var response = xmlHttp.responseText; 
                    if (response.length > 0)
                    {
                        alert("Unable to Auto Save Data. Please " + 
                              "check your internet connectivity");
                    } 
                }
            }
            catch(e){}
        }
                
        window.setInterval(AutoSave, 15000);
        window.onbeforeunload = AutoSave;
    </script > 
  5. Using the hash table implementation in JavaScript by Michael Synovic:
  6. //WebForm1.aspx
    
    <script defer="defer" language="javascript">
    
    /*Bind event with Controls */
    
        function bindEvents(){
            var textBoxes = document.getElementsByTagName("input");
            for (i=0; i< textBoxes.length; i++){
                if (textBoxes[i].type == 'text' || textBoxes[i].type == 'radio'){
                    textBoxes[i].onblur = updateHashTable;
                }
            }
            for (i=0; i< textBoxes.length; i++){
                if (textBoxes[i].type == 'checkbox'){
                    textBoxes[i].onblur = updateHashTableforCheckBox;
                }
            }
        
            var comboBoxes = document.getElementsByTagName("select");
            for (j=0; j< comboBoxes.length; j++){
                comboBoxes[j].onchange = updateHashTableforCombo;
            }
        }
    
        var Data= new Hashtable();
    
        function updateHashTable(){
        Data.put(this.id, this.value);
        }
        function updateHashTableforCheckBox(){
        Data.put(this.id, this.checked);
        }
        function updateHashTableforCombo(){
            Data.put(this.id, this.options(this.selectedIndex).value);
        }
    </script>
  7. First, we check if the DTO is already there, i.e., we've a user session that is not yet expired. We bind it with the existing in-memory DTO. Otherwise, we create a new object in the Cache to hold the user data. The Cache is used instead of the Session because it can survive across user sessions/logins and we can use a callback method to emulate the Session_End event. The CacheExpired method is invoked after cache timeout and flushes the data to the database.
  8. //WebForm1.aspx.cs
    
    private void Page_Load(object sender, System.EventArgs e){
    
        if (!Page.IsPostBack){
                    
        UserData userData;
        if (Cache[Context.User.Identity.Name] == null){
            userData = new UserData();
            Cache.Insert(Context.User.Identity.Name, userData ,null, 
               Cache.NoAbsoluteExpiration, 
               TimeSpan.FromMinutes(Session.Timeout), 
               CacheItemPriority.Default, new  CacheItemRemovedCallback(CacheExpired));
        }
        else{
            userData = Cache[Context.User.Identity.Name] as UserData;
        }
    
        FillPage(userData); //to populate web form controls with values from DTO
    
    }
    
    internal void CacheExpired(string key, object val, CacheItemRemovedReason reason) {
        if (reason != CacheItemRemovedReason.Removed){
        //Save.aspx invokes userData.updateDB() to update data in database
            HttpContext.Current.Server.Execute("Save.aspx", new StringWriter());
        }
    }
  9. On AutoSave.aspx, we iterate through the query string values and set the properties of the DTO and update the DTO object in memory.
  10. //AutoSave.aspx.cs
    
    private void Page_Load(object sender, System.EventArgs e)
    {
        UserData userData = Cache[Context.User.Identity.Name] as UserData;
        for(int i=0; i < Request.QueryString.Count; i++){
            try{
                userData[Request.QueryString.GetKey(i)] = Request.QueryString.Get(i);
            }
            catch (Exception ex){
                continue;
            }
        }
            Cache[Context.User.Identity.Name] = userData;
        
    }
  11. A DTO (Data Transfer Object) is defined to hold the user data in memory. A string indexer is implemented to directly assign the values to the class properties from the query string.
  12. The updateDB() method is to flush the data from the memory to the database. updateDB() is invoked only when the user submits the form or when the cached DTO expires.
  13. //DTO.cs
    
    public class UserData {
        public string this[string paramName]{// string indexer
    
            get {
                return this.GetType().GetProperty(paramName).GetValue(
                                      this, null).ToString();
            }
            set{
                this.GetType().GetProperty(paramName).SetValue(this,value,null);
            }
        }
    
        private string _LastName;
        public string LastName {
            get { return _LastName; }
            set { _LastName = value; }
        }
    
        private string _FirstName;
        public string FirstName {
            get { return _FirstName; }
            set { _FirstName = value; }
        }
            
        private string _Email;
        public string Email {
            get { return _Email; }
            set { _Email = value; }
        }
            
        public void updateDB() {
            
            /***here: invoke Stored Procedure to update data in database***/
            HttpContext.Current.Cache.Remove(HttpContext.Current.User.Identity.Name);
        }

Limitations

  1. The solution won't work if a web page is automatically filled in by a client browser by some web-form-filler software.
  2. If the user input fields are defined inside user controls (ASCX) that are reused in many places, we might need to modify the solution a bit as currently, we have name-mapping between data values to DTO properties.

History

  • First release - 6/29/2006.
  • Update - 9/25/2006.

License

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

Share

About the Author

Usman Shaheen
Team Leader
Pakistan Pakistan
Usman is living in Doha, Qatar, working as BizTalk Consultant at Arab national Bank.
Programming experience includes SOA implementations on Microsoft Platform, In WCF, Workflows and BizTalk Server.
Also working in ASP.NET, C#, Ajax, JavaScript, SharePoint Portal Server, SQL Reporting and Integration services
 
www.linkedin.com/in/usmanshaheen
http:\\usmanshaheen.wordpress.com

Comments and Discussions

 
Questionhow to insert these saved data to our database tabls PinmemberMember 913191926-Oct-13 0:31 
GeneralMy vote of 1 Pinmembergpaantony11-Nov-11 0:00 
GeneralRe: My vote of 1 Pinmvpthatraja11-Nov-11 0:22 
GeneralThis is easy done in .net 2.0 with AJAX PinmemberMr Orange19-Mar-09 1:59 
GeneralUse intellilogin to protect your web login forms. PinmemberHoochie16-Oct-06 18:19 
GeneralYou dont have to use a database! Pinmemberzygan21-Aug-06 22:11 
GeneralRe: You dont have to use a database! PinmemberUsman Shaheen22-Aug-06 3:14 
QuestionNo downloadable samples PinmemberCatalin M.11-Aug-06 14:26 
GeneralThanks for sharing your work PinmemberBrian Leach5-Jul-06 9:19 

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
Web01 | 2.8.141223.1 | Last Updated 25 Sep 2006
Article Copyright 2006 by Usman Shaheen
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid