使用ASP.NET Atlas编写显示真实进度的ProgressBar

作者:网络 来源:佚名 更新时间:2008-07-07 19:38:03 点击:
英文版见:http://dflying.dflying.net/1/archive/100_building_a_real_time_progressbar_using_aspnet_atlas.html
当后台在进行某些长时间的操作时,如果能在页面上提供一个显示真实进度的进度条,而不是让用户不知情的等待或是从前的那些简单的估计,将是一个非常难得的出彩之处。现在使用asp.net atlas完全有可能做到这些。这篇文章将讨论如何完成这一功能并介绍一些有关atlas客户端控件开发的基本概念。您同时可以在这里下载示例程序以及源文件。

实现网页上的进度条想法其实很简单:编写一个客户端的atlas控件,每隔一段时间请求一次服务器,并使用返回的当前进度数据更新进度条的显示。在这个示例中,将有四个部分的代码组成:

一个需要较长时间才能完成的web service
一个用来查询上述web service进度的web service
客户端atlas进度条(progressbar)控件,负责维护客户端逻辑并输出可视化ui。这也是本示例中最重要的一个组件,在将来可被重用于其他页面或程序的开发
包含上述web service以及控件的asp.net测试页面
下面我们一步一步地来实现以上四个步骤:

 需要较长时间完成的web service

在实际的程序中,一个需要较长时间完成的web service可能有如下声明:

1[webmethod]
2public void timeconsumingtask()
3{
4    connecttodatabase();
5    getsomevaluefromdatabase();
6    copysomefilesfromdisk();
7    getaremotefile();
8}
这样我们就可以插入一些辅助方法来确定当前进度完成情况,setprogress(int)用来设定当前的进度完成百分比:

 1[webmethod]
 2public void timeconsumingtask()
 3{
 4    setprogress(0);
 5    connecttodatabase();
 6    setprogress(10);
 7    getsomevaluefromdatabase();
 8    setprogress(40);
 9    copysomefilesfromdisk();
10    setprogress(50);
11    getaremotefile();
12    setprogress(100);
13}
在本示例中,我们仅仅使用cache来储存进度完成信息并利用thread.sleep()方法模拟操作的延迟:

 1[webmethod]
 2public int starttimeconsumingtask()
 3{
 4    string processkey = this.context.request.userhostaddress;
 5    string threadlockkey = "thread" + this.context.request.userhostaddress;
 6    object threadlock = this.context.cache[threadlockkey];
 7    if (threadlock == null)
 8    {
 9        threadlock = new object();
10        this.context.cache[threadlockkey] = threadlock;
11    }
12
13    // only allow 1 running task per user.
14    if (!monitor.tryenter(threadlock, 0))
15        return -1;
16
17    datetime starttime = datetime.now;
18
19    // simulate a time-consuming task.
20    for (int i = 1; i <= 100; i++)
21    {
22        // update the progress for this task.
23        this.context.cache[processkey] = i;
24        thread.sleep(70);
25    }
26
27    monitor.exit(threadlock);
28
29    return (datetime.now - starttime).seconds;
30}
31
 

查询进度的web service

很容易实现,只需从cache中取得进度信息:

 1[webmethod]
 2public int getprogress()
 3{
 4    string processkey = this.context.request.userhostaddress;
 5    object progress = this.context.cache[processkey];
 6    if (progress != null)
 7    {
 8        return (int)progress;
 9    }
10
11    return 0;
12}

客户端进度条(progressbar)控件

第一步:从sys.ui.control继承

progressbar控件应该继承自atlas的控件基类sys.ui.control,并且声明为密封类(sealed class,不能再被继承)。sys.ui.control基类包含了一些所有的控件共有的操作与方法。比如,将自己与某个html元素关联起来(也就是所谓的binding)等。同时也要注册以让atlas了解这个新的类型以便今后的声明及使用,例如,让atlas可以取得这个类型的描述等。

1sys.ui.progressbar = function(associatedelement) {
2    sys.ui.progressbar.initializebase(this, [associatedelement]);
3
4}
5type.registersealedclass('sys.ui.progressbar', sys.ui.control);
6sys.typedescriptor.addtype('script','progressbar', sys.ui.progressbar);
7

第二步:添加私有成员并书写相应的setter/getter

下面需要添加一些属性用来设定我们的控件。在这个例子中,我们需要三个属性:

interval. 每次重新查询进度并更新进度条的间隔时间。单位:毫秒
service url. web service文件的路径。
service method. 取得进度信息的方法名。
这些属性应该严格遵守atlas的命名规范:getter应该以'get_'开头,setter应该以'set_'开头并传入一个参数。还需要在控件的描述方法(descriptor)中添加对于这些属性的说明。有关描述方法(descriptor)将在第四步中说明。例如,针对service method属性,我们有如下声明:

1var _servicemethod;
2
3this.get_servicemethod = function() {
4    return _servicemethod;
5}
6
7this.set_servicemethod = function(value) {
8    _servicemethod = value;
9}

第三步:使用timer控件每隔一段时间查询一次web service

sys.timer用于每过一段时间调用一个方法(发出一个事件),我们可以定义一个委托来指向这个方法,并在并在每一个时间段内查询这个web service。为了避免浏览器内存泄露,在控件析构(dispose)的时候应该记得做一些必要的清理。

还有,注意当前一个请求并没有返回时,不应该发送第二个请求。

 1var _timer = new sys.timer();
 2var _responsepending;
 3var _tickhandler;
 4var _obj = this;
 5
 6this.initialize = function() {
 7    sys.ui.progressbar.callbasemethod(this, 'initialize');
 8    _tickhandler = function.createdelegate(this, this._ontimertick);
 9    _timer.tick.add(_tickhandler);
10    this.set_progress(0);
11}
12
13this.dispose = function() {
14    if (_timer) {
15        _timer.tick.remove(_tickhandler);
16        _tickhandler = null;
17        _timer.dispose();
18    }
19    _timer = null;
20    associatedelement = null;
21    _obj = null;
22
23    sys.ui.progressbar.callbasemethod(this, 'dispose');
24}
25
26this._ontimertick = function(sender, eventargs) {
27    if (!_responsepending) {
28        _responsepending = true;
29       
30        // asynchronously call the service method.
31        sys.net.servicemethod.invoke(_serviceurl, _servicemethod, null, null, _onmethodcomplete);
32    }
33}
34
35function _onmethodcomplete(result) {
36    // update the progress bar.
37    _obj.set_progress(result);
38    _responsepending = false;
39}

第四步:添加控制方法

我们应该可以控制进度条的开始/停止。并且,对于一个atlas控件,相关的描述方法(descriptor)也是必须的。atlas会利用它来描述这个类型的信息。

 1this.getdescriptor = function() {
 2    var td = sys.ui.progressbar.callbasemethod(this, 'getdescriptor');
 3    td.addproperty('interval', number);
 4    td.addproperty('progress', number);
 5    td.addproperty('serviceurl', string);
 6    td.addproperty('servicemethod', string);
 7    td.addmethod('start');
 8    td.addmethod('stop');
 9    return td;
10}
11
12this.start = function() {
13    _timer.set_enabled(true);
14}
15
16this.stop = function() {
17    _timer.set_enabled(false);
18}

ok,目前为止客户端的控件就完成了。我们把它存为progressbar.js。

asp.net testing page asp.net测试页面

对于任何的atlas页面,我们第一件需要做的事情就是添加一个scriptmanager服务器控件。在这个示例中我们将引用progressbar控件,较长时间才能完成的web service以及进度查询web service。(这两个web service位于同一个文件中:taskservice.asmx)

1<atlas:scriptmanager id="scriptmanager1" runat="server" >
2    <scripts>
3        <atlas:scriptreference path="scriptlibrary/progressbar.js" scriptname="custom" />
4    </scripts>
5    <services>
6        <atlas:servicereference path="taskservice.asmx" />
7    </services>
8</atlas:scriptmanager>
接下来是页面的布局与样式:

 1<style type="text/css">
 2* {}{
 3    font-family: tahoma;
 4}
 5.progressbarcontainer {}{
 6    border: 1px solid #000;
 7    width: 500px;
 8    height: 15px;
 9}
10.progressbar {}{
11    background-color: green;
12    height: 15px;
13    width: 0px;
14    font-weight: bold;
15}
16</style>
17
18<div>task progress</div>
19<div class="progressbarcontainer">
20    <div id="pb" class="progressbar"></div>
21</div>
22<input type="button" id="start" value="start the time consuming task!" />
23<div id="output" ></div>
最后是一段javascript启动那个较长时间才能完成的web service并让progressbar控件开始工作:


截图和下载

现在所有的事情都搞定了,可以运行了!

页面初始化:

运行中:

运行完成:

示例程序以及源文件可以在这里下载

菜鸟学堂: