代码改变世界

Building a Real Time ProgressBar using ASP.NET Atlas

2007-08-14 10:07  Virus-BeautyCode  阅读(700)  评论(0编辑  收藏  举报
 

Building a Real Time ProgressBar using ASP.NET Atlas

Dflying | 27 March, 2006 23:44

That will be very cool and useful if you can show your user a ProgressBar on a web page which displays the actual progress of some long operations. Now let’s try to make it possible by using ASP.NET Atlas. This post can also show you some basic conceptions about extending Atlas client side controls. Also, the source code and demo can be downloaded here.

The basic ideas to implement this will be easy. Build an Atlas client side control and query a service to find how much we’ve done every tick. Then get the response and update the UI of progress bar. So in this demo, we separate the code into four parts:

  1. Web Service which processes a time consuming task.
  2. Web Service which is used to query the time consuming task and get the progress.
  3. Client side Atlas ProgressBar control which renders the UI and client side logic. This is the core component of the demo and can also be reused in other pages/projects without any changes.
  4. ASP.NET page contains the Atlas controls and Web Service references, which runs the application.

Let’s go through the four steps.

Time Consuming Web Service

In the real world, a time consuming function may runs like following:

 

[WebMethod]
public void TimeConsumingTask()
{
ConnectToDataBase();
GetSomeValueFromDataBase();
CopySomeFilesFromDisk();
GetARemoteFile();
}

Then we can insert some helpers to show how much the work has been done:

 

[WebMethod]
public void TimeConsumingTask()
{
setProgress(0);
ConnectToDataBase();
setProgress(10);
GetSomeValueFromDataBase();
setProgress(40);
CopySomeFilesFromDisk();
setProgress(50);
GetARemoteFile();
setProgress(100);
}

In this demo we just store the progress value in Cache and use Thread.Sleep() to delay the processing:

 

[WebMethod]
public int StartTimeConsumingTask()
{
string processKey = this.Context.Request.UserHostAddress;
string threadLockKey = "thread" + this.Context.Request.UserHostAddress;
object threadLock = this.Context.Cache[threadLockKey];
if (threadLock == null)
{
threadLock = new object();
this.Context.Cache[threadLockKey] = threadLock;
}
 
// Only allow 1 running task per user.
if (!Monitor.TryEnter(threadLock, 0))
return -1;
 
DateTime startTime = DateTime.Now;
 
// Simulate a time-consuming task.
for (int i = 1; i <= 100; i++)
{
// Update the progress for this task.
this.Context.Cache[processKey] = i;
Thread.Sleep(70);
}
 
Monitor.Exit(threadLock);
 
return (DateTime.Now - startTime).Seconds;
}

GetProgress Web Service

This should be easy.We just get the progress value from Cache:

 

[WebMethod]
public int GetProgress()
{
string processKey = this.Context.Request.UserHostAddress;
object progress = this.Context.Cache[processKey];
if (progress != null)
{
return (int)progress;
}
return 0;
}

Atlas ProgressBar control

Step 1: Derive from Sys.UI.Control

ProgressBar control should derive from the base Atlas control class, Sys.UI.Control and let’s make it a sealed class. The Sys.UI.Control base class contains some useful things, such as associating itself with an HTML element, which is the so called binding. Additionally, you should register your type to make it possible to instantiate it declaratively. Also register your class to let Atlas know it for further options, such as describing what the type is, etc.

 

Sys.UI.ProgressBar = function(associatedElement) {
Sys.UI.ProgressBar.initializeBase(this, [associatedElement]);
 
}
Type.registerSealedClass('Sys.UI.ProgressBar', Sys.UI.Control);
Sys.TypeDescriptor.addType('script','progressBar', Sys.UI.ProgressBar);

Step 2: Add private fields and the Setter/Getter

We have to add some configurable properties for our control. In this case, we have 3 properties:

  1. Interval. How often shall we call the service to update the progress?
  2. Service Url. Where is the Web Service file located?
  3. Service Method. Which method shall we call on this service to get the progress?

Properties have to follow an exact naming convention: the getter of the property should be a function prefixed with 'get_', and the setter should be prefixed with 'set_' and expect 1 parameter. Additionally, we should add these properties to our control's descriptor. Please see step 4 on how this should be done. For the service method property, we have following code:

 

var _serviceMethod;
 
this.get_serviceMethod = function() {
return _serviceMethod;
}
 
this.set_serviceMethod = function(value) {
_serviceMethod = value;
}

Step 3: Add a Timer to query the service on every tick

Here we include a Sys.Timer to query the service. Also define a delegate to represent the function that we want the timer to invoke on each tick. To get rid of the browser memory leak, we should make sure to finish the clean up when our control is disposing.
Still, notice that we prevent the control from querying the service multiple times when we are still waiting for a response.

 

var _timer = new Sys.Timer();
var _responsePending;
var _tickHandler;
var _obj = this;
 
this.initialize = function() {
Sys.UI.ProgressBar.callBaseMethod(this, 'initialize');
_tickHandler = Function.createDelegate(this, this._onTimerTick);
_timer.tick.add(_tickHandler);
this.set_progress(0);
}
 
this.dispose = function() {
if (_timer) {
_timer.tick.remove(_tickHandler);
_tickHandler = null;
_timer.dispose();
}
_timer = null;
associatedElement = null;
_obj = null;
 
Sys.UI.ProgressBar.callBaseMethod(this, 'dispose');
}
 
this._onTimerTick = function(sender, eventArgs) {
if (!_responsePending) {
_responsePending = true;
// Asynchronously call the service method.
Sys.Net.ServiceMethod.invoke(_serviceURL, _serviceMethod, null,
null, _onMethodComplete);
}
}
 
function _onMethodComplete(result) {
// Update the progress bar.
_obj.set_progress(result);
_responsePending = false;
}

Step 4: Add control methods

We should be able to start/stop our progress bar. And since this control is an Atlas object, we want it be known by the Atlas framework by describing its methods in the descriptor.

 

this.getDescriptor = function() {
var td = Sys.UI.ProgressBar.callBaseMethod(this, 'getDescriptor');
td.addProperty('interval', Number);
td.addProperty('progress', Number);
td.addProperty('serviceURL', String);
td.addProperty('serviceMethod', String);
td.addMethod('start');
td.addMethod('stop');
return td;
}
 
this.start = function() {
_timer.set_enabled(true);
}
 
this.stop = function() {
_timer.set_enabled(false);
}

Oh… till now, the control’s done! Save it as ProgressBar.js.

ASP.NET Testing Page

Of course, the first step of building every Atlas page is adding a ScriptManager server control. In this case we refer to our ProgressBar control, time consuming web service and GetProgress web service. (The two web services are located in one file: TaskService.asmx)

 

<atlas:ScriptManager ID="ScriptManager1" runat="server" >
<Scripts>
<atlas:ScriptReference Path="ScriptLibrary/ProgressBar.js" ScriptName="Custom" />
</Scripts>
<Services>
<atlas:ServiceReference Path="TaskService.asmx" />
</Services>
</atlas:ScriptManager>

Then styles and layouts:

 

<style type="text/css">
* {
font-family: tahoma;
}
.progressBarContainer {
border: 1px solid #000;
width: 500px;
height: 15px;
}
.progressBar {
background-color: green;
height: 15px;
width: 0px;
font-weight: bold;
}
</style>
<div>Task Progress</div>
<div class="progressBarContainer">
<div id="pb" class="progressBar"></div>
</div>
<input type="button" id="start" onclick="startTask();return false;"
value="Start the Time Consuming Task!" />
<div id="output" ></div>

At last is the JavaScript event handler which makes the ProgressBar control ran.

 

<script type="text/javascript" language="javascript">
function startTask()
{
// new ProgressBar
var pb = new Sys.UI.ProgressBar($('pb'));
pb.set_interval(500);
pb.set_serviceURL('TaskService.asmx');
pb.set_serviceMethod('GetProgress');
pb.initialize();
// start the task
TaskService.StartTimeConsumingTask(onTaskCompleted);
// start the ProgressBar
pb.start();
}
function onTaskCompleted(result)
{
// alert the time cost
if (result != -1)
$('output').innerHTML = 'Task completed in ' + result + ' seconds.';
}
</script>

Screen Shots and Download

Great, everything’s done now! Let’s run it!

Not started:

Running:

Completed:

Source code available here.