Use the power of ASP.NET Ajax and Windows Communication Foundation (WCF) to automatically and periodically save webform data and provide an enhanced user experience.
Introduction
ASP.NET Ajax opens a lot of possibilities to provide users a rich user experience. At the
code level developers can create components, client behaviors or controls that
harness the power of ASP.NET and Ajax to provide features that are otherwise
difficult to offer. Ajax components are basically client side classes that
usually do not have any user interface. They inherit from Sys.Component
base
class. Once inherited you can add your own properties, methods and events to
them in order to provide the necessary functionality. In this article you are
going to see how an Ajax component can be developed, that talks with the server side
data via a Windows Communication Foundation (WCF) service.
Example Scenario
Many web based email systems now a days offer a rich user interface. They
provide features such as automatically checking for new emails periodically and
automatically saving the messages you are still composing. The later feature can
be implemented in lengthy data entry forms. Imagine a scenario where a user is
presented with a lengthy data entry form. He starts filling the form and mid way
goes away for some other work. By the time he returns something causes the
browser to close or machine to restart (say a crash or power failure). Obviously
his data is lost since he didn't submit the form. Wouldn't it be nice if the web
form automatically saves the entered data on the server periodically so that the
web form state can be retrieved even after a crash? This is precisely what you
are going to develop in the remainder of the article.
At first glance you may think of using the
UpdatePanel
control along with
Timer
. Though you can achieve
similar results using
UpdatePabel
and
Timer
control there are a few limitations. Consider, for example, that
your form is too lengthy and you have placed everything inside an
UpdatePanel
. Now when
UpdatePanel
refreshes the whole data entry form will be refreshed and the user will have to wait till it reappears. Also, saving selected fields
for auto save operation will be tedious and may call for a rearrangement of the data entry fields. So it is best to rollout your own
auto saving mechanism.
Software Needed
In order to work through the example that follows you need to have ASP.NET Framework
4.0 installed on your machine. Though the example is developed using Microsoft Visual
Studio 2010 even Visual Web Developer Express Edition can be used. Additionally,
you need SQL Server 2005 / 2008 database as our data will be stored there.
Creating a Web Site
To begin with, create a new ASP.NET Web Site using Microsoft Visual Studio. Figure
1 shows the "New Web Site" dialog. Give Web Site folder name as AutoSave.
Figure 1
Adding SQL Server Database
Once the web site is ready you need to add a new SQL Server database. If you
have a stand-alone installation of SQL Server along with SQL Sever Management
Studio you can also create a database externally. To add a new database to your
web site locate the App_Data folder in Solution Explorer, right click on it and
choose "Add New Item...". The Add New Item dialog as shown in Figure 2 will be
displayed.
Figure 2
Select "SQL Server database" from the list, give the database the name of your choice and click the Add button. Once the
database is added double click on it in the Solution Explorer so as to open the Server Explorer. Expand the database node, right click
on the Tables node and choose Add New Table. This will open the table definition dialog as shown in Figure 3.
Figure 3
Figure 3 shows a sample table named Article with five columns viz. Id, ArticleTitle, ArticleContent, SubmittedBy and SubmittedOn. Notice the data types of individual columns. Once the Articles table is created proceed to add a WCF
service that works with the data from this table.
Creating a WCF Service
To add a new WCF service right click on the web site, choose "Add New Item"
and then select WCF Service (Figure 4). Give service name as Service and click
the Add button.
[Figure4.jpg]
Figure 4
Adding a new WCF service will create a .svc file with the @ServiceHost directive as
shown below:
<%@ ServiceHost Language="C#" Service="Service" CodeBehind="~/App_Code/Service.cs" %>
The CodeBehind
attribute is pointing to Service.cs file automatically created
in the App_Code
folder. If you open the Service.cs file it will look like this:
[ServiceContract(Namespace = "AutoSaveService")]
[AspNetCompatibilityRequirements
(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service
{
...
}
The Service class is marked with [ServiceContract]
attribute which indicates
that the underlying class is a WCF service
. Its Namespace property is set to AutoSaveService
indicating that the caller of the service (client side Ajax code
in our case) will see the service class (Service) to be part of AutoSaveService
namespace. You can, of course, give it the name of your choice instead of
AutoSaveService
.
You need to transfer data from the web form controls to the WCF service
you
just created. One way to do this is to add the required number of parameters to
the service method. A more elegant way is, however, to create a separate class to
carry the data from client to the service. To do this, manually add a new class
in the Service.cs file as shown below:
[DataContract]
public class AutoSaveData
{
[DataMember]
public string ArticleTitle {get;set;}
[DataMember]
public string ArticleContent{ get; set; }
[DataMember]
public string SubmittedBy { get; set; }
}
The above code consists of a class named AutoSaveData
that is marked with [DataContract]
attribute. The [DataContract] attribute marks a class as a data contract and
makes it serializable for the sake of data transfer. The AutoSaveData class
consists of three public properties namely ArticleTitle, ArticleContent
and
SubmittedBy
. All of them are marked with the [DataMember]
attribute. The [DataMember]
attribute marks the members for serialization. Now that the AutoSaveData class
is ready let's complete the Service class by adding a method to get the data
in and out of the database.
You will use LINQ to SQL to communicate with the database. Right click on the App_Code folder, select "Add New Item" and choose LINQ to SQL classes (Figure 5). Doing so will add a .dbml file to your web site.
[Figure5.jpg]
Figure 5
Once added drag and drop the Articles table from Server Explorer onto the
design surface of the dbml file. This will automatically create a LINQ to SQL
class named Article as shown in Figure 6.
[Figure6.jpg]
Figure 6
As you can see, the article
class has all the properties corresponding to the
columns of the articles
table.
Next, add a new method to the Service class as shown below:
[OperationContract]
public void SaveWork(AutoSaveData data)
{
DataClassesDataContext dc = new DataClassesDataContext();
IQueryable<Article> list = from art in dc.Articles
where art.SubmittedBy == data.SubmittedBy
select art;
int i;
if (list.Count() == 0)
{
Article art = new Article();
art.Id = new Guid();
art.ArticleTitle = data.ArticleTitle;
art.ArticleContent = data.ArticleContent;
art.SubmittedBy = data.SubmittedBy;
art.SubmittedOn = DateTime.Now;
dc.Articles.InsertOnSubmit(art);
}
else
{
list.First().ArticleTitle=data.ArticleTitle;
list.First().ArticleContent=data.ArticleContent;
list.First().SubmittedOn=DateTime.Now;
}
dc.SubmitChanges();
Thread.Sleep(5000);
}
The SaveWork
method takes a parameter of type AutoSaveData
(the class you
created earlier) and is decorated with the [OperationContract]
attribute. The [OperationContract]
attribute marks a method as remotely callable. Inside the SaveWork
method you
first need to determine if there is any data saved for the same user. If not the
supplied data is inserted into the articles
table otherwise the existing record is
updated. Notice that here you are following a simplistic approach by checking merely
the SubmittedBy
value. In more realistic case you will have Forms Authentication
enabled for your site and user name along with some extra parameters will be
used while querying the data.
The SaveWork
method first creates an instance of the DataClassesDataContext
class
(the LINQ to SQL class that gets created when you add a .dbml file). As the name suggests
the DataClassesDataContext
class represents a LINQ data context and provides
access to the data. The code then finds out if there is any record matching the SubmittedBy
value. This is done using a LINQ query. If the query does not return any record that means there is no data for that user and you insert a new record
otherwise you update the existing record. The SubmitChanges()
method of the LINQ
data context saves the changes to the database. Notice that the values to be inserted
or updated are supplied by the AutoSaveData
object.
At the end of the SaveWork
method a delay is introduced using the Thread.Sleep()
method. This is done purely for testing purposes so that you can see the client messages even during local run. Once you are done you should remove
this delay.
Now that you have finished the SaveWork
method let's move ahead and create
the Ajax client side component that consumes the WCF service
you just created.
Creating the Ajax Component
The Ajax client side code can be added as a part of <script> tag within the web
form. However, since you are creating a component it is better to keep the code
in a separate file. To do so, add a new JScript file to your web site (Figure 7).
[Figure7.jpg]
Figure 7
In the JScript file you will create an Ajax class named AutoSave
. Begin by
placing the namespace declaration as shown below:
Type.registerNamespace("AjaxDemos");
The code above declares the AjaxDemos
namespace. The AutoSave
class you create
further will be part of this namespace. The following code fragment shows the
constructor of the AutoSave
class.
AjaxDemos.AutoSave = function (serviceCallback, dataCallback)
{
AjaxDemos.AutoSave.initializeBase(this);
this._interval = 5000;
this._enabled = false;
this._handle = null;
this._serviceCallback = serviceCallback;
this._dataCallback = dataCallback;
}
The code calls the initializeBase()
method that invokes the base class constructor.
Recollect from our earlier discussion that the Ajax components inherits from Sys.Component
base class.
You also need to declare certain private variables (all that
begin with an _
character are private variables for Ajax) to store interval of
auto save operation and to indicate whether the auto save operation is enabled. The
default interval for auto save operation is set to 5000 milliseconds. The serviceCallback
and dataCallback
parameters are function references. These
functions will be invoked later to construct an instance of Service
class and AutoSaveData
class respectively.
Next comes the prototype of the AutoSave
class that contains the properties and
methods. The public properties viz. Interval and Enabled
are shown below:
AjaxDemos.AutoSave.prototype =
{
get_Interval: function () {
return this._interval;
},
set_Interval: function (value) {
if (this._interval != value) {
this._interval = value;
this.raisePropertyChanged("Interval");
this._StopAutoSave();
this._StartAutoSave();
}
},
get_Enabled: function () {
return this._enabled;
},
set_Enabled: function (value) {
if (this._enabled != value) {
this._enabled = value;
this.raisePropertyChanged("Enabled");
if (value)
this._StartAutoSave();
else
this._StopAutoSave();
}
}
The get_Interval
and get_Enabled
properties are straightforward and need no
explanation. The set_Interval
property sets the _interval
variable to a new
value and calls the raisePropertyChanged()
method supplying the name of the property
being changed. If the client code has wired an event handler to propertyChanged
event of the Sys.Component
class you can process this change if needed. The _StopAutoSave()
and
the _StartAutoSave()
methods (you will code them in a minute)
simply stop and start the auto save operation so that the new interval comes into effect.
The set_Enabled
property simply calls the _StartAutoSave()
or _StopAutoSave()
methods
depending on the boolean value supplied.
AddDataSavingEvantHandler: function (handler) {
this.get_events().addHandler("DataSaving", handler);
},
RemoveDataSavingEvantHandler: function (handler) {
this.get_events().removeHandler("DataSaving", handler);
},
AddDataSavedEvantHandler: function (handler) {
this.get_events().addHandler("DataSaved", handler);
},
RemoveDataSavedEvantHandler: function (handler) {
this.get_events().removeHandler("DataSaved", handler);
}
Next the AutoSave
class defines a few methods to add event handlers for two
events viz. DataSaving and DataSaved. The DataSaving event will be raised just
before the auto save operation and DataSaved event is raised after the
auto save operation is over. All the AddXXXX
and RemoveXXXX
methods shown above
make use of events collection provided by the base class Sys.Component and add
or remove event handler using the addHandler()
and the removeHandler()
method
respectively.
_RaiseDataSavingEvent: function () {
var handler = this.get_events().getHandler("DataSaving");
if (handler)
handler(this, Sys.EventArgs.Empty);
},
_RaiseDataSavedEvent: function () {
var handler = this.get_events().getHandler("DataSaved");
if (handler)
handler(this, Sys.EventArgs.Empty);
}
The actual task of raising DataSaving
and DataSaved
events happens inside
_RaiseDataSavingEvent()
and _RaiseDataSavedEvent()
methods. These methods simply get a reference to the
handler method and invoke it with two parameters viz. object instance and EventArgs. As you can see the event handler looks similar to
standard server side event handlers.
_SaveWork: function () {
this.set_Enabled(false);
this._RaiseDataSavingEvent();
var successCode = Function.createDelegate(this, this._OnSuccess);
var errorCode = Function.createDelegate(this, this._OnError);
var objService = this._serviceCallback();
var objData = this._dataCallback();
objService.SaveWork(objData, successCode, errorCode, null);
},
_OnSuccess: function (result) {
this._RaiseDataSavedEvent();
this.set_Enabled(true);
},
_OnError: function (err) {
alert(err.get_message());
}
The _SaveWork()
method is important one as it calls the WCF service created
earlier. Inside this method you first turn off the Enabled property so that next
call to _SaveWork()
will not fire unless the previous is finished. DataSaving
event is also raised so that client web form can give some visual notification
to the user. The createDelegate()
method essentially creates a pointer to the code you wish to execute. Doing so
preserves the context for the code being called. Notice the use of _serviceCallback
and _dataCallback
function references. The first call returns an instance of the WCF
service proxy class whereas the second call returns an instance of AutoSaveData
class filled with textbox values. You then call SaveWork()
method
on the WCF service
proxy passing it four parameters. The first parameter is the
actual parameter required by the SaveWork()
method on the server. The second
parameter is a JavaScript function that gets called when the operation is
successful. The third parameter represents a JavaScript function that gets
called in case of an error. The last parameter is user context and you
pass it as a null since you don't need it in this example. Notice that since the Ajax
calls remote methods asynchronously the return value of the SaveWork()
method is not
available immediately.
The _OnSuccess()
method is called when the remote service call returns
successfully and receives a parameter that represents the actual return value of
the remote method that was called. Inside this function you just raise the DataSaved
event and enable the auto save operation again. The _OnError()
method is called in case of an error while executing the
remote method. You simply display the error message using the get_message()
method of
the error object received as a parameter.
_StartAutoSave: function () {
var code = Function.createDelegate(this, this._SaveWork);
this._handle = window.setInterval(code, this._interval);
},
_StopAutoSave: function () {
if (this._handle) {
window.clearInterval(this._handle);
this._handle = null;
}
}
The _StartAutoSave()
method first creates a delegate that points to the _SaveWork()
method. This is done with the help of the createDelegate()
method. The setInterval()
method of JavaScript window object is then called passing this delegate and
interval. The setInterval()
method is responsible for invoking the saving operation
after a predefined period of time. Notice the use of the _handle
variable to store
the returned integer value of the setInterval()
method. This value is needed further
in the _StopAutoSave()
method.
The _StopAutoSave(
) method calls the clearInterval()
method of the window
object and clears the previously set interval.
dispose: function () {
this.set_Enabled(false);
AjaxDemos.AutoSave.callBaseMethod(this, "dispose");
},
The dispose() method performs some cleanup task such as
disabling the auto save operation and calls the base class dispose() method.
Finally, the AutoSave class you just created is registered with the Ajax
framework.
AjaxDemos.AutoSave.registerClass("AjaxDemos.AutoSave", Sys.Component);
The registerClass()
method takes two parameters viz. fully qualified name of
the class being registered and base class (Sys.Component
in our case).
Creating a Data Entry WebForm
Now that your AutoSave Ajax class
is ready let's build a web form that
actually uses this class. Begin by opening the default web form (when you create
a new website in Microsoft Visual Studio 2010 a default web form with a master page gets added for
you. In our example the master page has been removed for the sake of simplicity)
and design it as shown in Figure 8.
[Figure8.jpg]
Figure 8
The web form has a ScriptManager
control placed on it. In order to use any
Ajax functionality a web form must have a ScriptManager
control. The Label
control placed at the top displays messages before and after auto saving
operation so that the user is aware that the data is automatically getting saved
on the server. The Title, Content and Submitted By textboxes collect the
respective pieces of information. The Submit button performs the saving
operation explicitly.
Once the web form is designed select the ScriptManager
control and open its
Properties window. Locate Scripts collection and add a reference to JScript.js
file that contains AutoSave class. This way the ScriptManager knows about your
Ajax class and you can use the class properties and methods on the web form. Figure 9
shows the ScriptReference
collection editor dialog.
[Figure9.jpg]
Figure 9
Similarly locate Services collection and add a reference to Service.svc. This
is necessary because in order to call methods of the WCF service (which is on
the server side) Ajax code needs a proxy for it. ScriptManager
does this job for
you based on the service references. Figure 10 shows the ServiceReference
collection editor with a reference pointing to Service.svc.
[Figure10.jpg]
Figure 10
Setting the properties as shown above adds the following markup to your web
form:
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Path="~/JScript.js" />
</Scripts>
<Services>
<asp:ServiceReference Path="~/service.svc" />
</Services>
</asp:ScriptManager>
Next, add a <script> block somewhere below the markup of ScriptManager
control and add the code shown below:
<script language="javascript" type="text/javascript">
var app = Sys.Application;
app.add_load(appLoadHandler);
var autosave = null;
...
</script>
The Application class from Sys namespace provides access to client components
that are registered with the application. The load event of Application object
is raised when all the registered components are fully constructed and
initialized. The load event handler is shown below:
function appLoadHandler(sender, args)
{
autosave = new AjaxDemos.AutoSave(GetServiceInstance,GetDataInstance);
autosave.AddDataSavingEvantHandler(OnDataSaving);
autosave.AddDataSavedEvantHandler(OnDataSaved);
autosave.set_Interval(5000);
autosave.set_Enabled(true);
}
Inside the load event handler you create an instance of the AutoSave
class and
pass two function references. The GetServiceInstance()
and GetDataInstance()
functions will be coded in a minute. Two event handlers to handle DataSaving
and DataSaved
events of
AutoSave class are added using the AddDataSavingEvantHandler()
and
AddDataSavedEvantHandler()
methods. Its Enabled property is set to true and
Interval property is set to 5000 milliseconds.
function GetServiceInstance() {
var service = new AutoSaveService.Service();
return service;
}
function GetDataInstance() {
var data = new AutoSaveData();
data.ArticleTitle = $get("Textbox1").value;
data.ArticleContent = $get("Textbox2").value;
data.SubmittedBy = $get("Textbox3").value;
return data;
}
The GetServiceInstance()
function simply creates an instance of the Service
class
from AutoSaveService
namespace. The GetDataInstance()
then creates an instance of AutoSaveData class and fills its
properties. Notice the use of $get
to retrieve textbox values instead of
traditional the getElementById()
method of document object.
function OnDataSaving(sender, args) {
$get("Label6").innerHTML="Auto saving data..."
}
function OnDataSaved(sender, args) {
$get("Label6").innerHTML = "Autosaved data successfully!";
}
The OnDataSaving()
and OnDataSaved()
methods act as event handlers for DataSaving and DataSaved
events of the AutoSave
class. They simply display a message to the end user so
that the user is informed about the status of the auto save operation.
protected void Button1_Click(object sender, EventArgs e)
{
Service s = new Service();
AutoSaveData data = new AutoSaveData();
data.ArticleTitle = TextBox1.Text;
data.ArticleContent = TextBox2.Text;
data.SubmittedBy = TextBox3.Text;
s.SaveWork(data);
Label5.Text = "Data saved successfully!";
}
The server side Click event handler of Submit button simply creates an
instance of Service class and calls its SaveWork()
method.
Testing the Web Form
Now that your coding is complete let's run the web form and see the auto save
operation in action. Run the default web form and fill the textboxes with some
data. Figure 11 and 12 show how the label placed at the top displays the auto
saving related messages.
[Figure11.jpg]
Figure 11
[Figure12.jpg]
Figure 12
Figure 13 shows the data that got saved in the database because of the auto
save operation. Try the functionality by changing the "Submitted By" value.
[Figure13.jpg]
Figure 13
Summary
ASP.NET Ajax component is a non-visual class that inherits from Sys.Component
base class. In this article you learned how Ajax and WCF can be used to implement the
auto save feature in web forms. You first developed a WCF service that talks
with the database to store information. You then consumed this service from
client web form using an Ajax component. Auto saving feature
thus implemented can be very useful for lengthy data entry forms.
Related Articles