Your Ad Here
Google

Tuesday, March 06, 2007

 

Automatically Saving Web Form Data





 







Introduction




This article explains how to automatically save the user input in background whilst the user is filling forms. This is particularly useful for large forms and you don't miss the data if user closes the Browser without saving the form, 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 its existing session if he logs in again prior to session expiration - even if 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 web server on periodic basis where it is stored in memory
and later flushed to database for persistent storage. 



JavaScript is used to monitor the user input in browser and fill in a hash table
with user input. On specified periods, the hash table
is serialized as querystring and sent to an aspx page (AutoSave.aspx) using
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 database.



The Implementation




 1. Bind onblur event of all input controls to populate hash table when user enters
data in form.

 2. Submit user data to server by calling AutoSave() function. xmlHttp wrapper
implementation is from http://www.codeproject.com/Ajax/AJAXWasHere-Part1.asp


3. Call AutoSave just before browser is going to close by the user, i.e. window.onbeforeunload
event.


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 > 

4. Using Hash Table Implementation in JavaScript by Michael Synovic





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>




5. First we check if the DTO is already there, i.e. we've user session that is
not yet expired, bind with the existing in-memory DTO, otherwise, create a new
object in Cache to hold user data. Cache is used instead of Session object because it can survive across user sessions/logins and we can use Callback method to emulate
Session_End event. CacheExpired method is invoked after Cache timeout and flushes the data to
database.




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());
}
}




6. On AutoSave.aspx, we iterate through the Query String values and set the properties
of DTO and update the DTO object in memory.

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;

}


7. 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 class
properties from Querystring.



 8. updateDB() method is to flush the data from memory to database.
updateDB()is invoked only when user Submits the form or Cached 
DTO is
expired.


 


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 web page is automatically filled in 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 've 
name-mapping between data values to DTO properties.



History




first release 6/29/2006. 

update 9/25/2006. 



Labels:


Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?