代码改变世界

深入剖析微软ASP.NET Ajax中的数据绑定架构上篇之一

2007-09-13 09:33  Jacky_Xu  阅读(417)  评论(0编辑  收藏  举报
一、引言

最近,微软ASP.NET Ajax 1.0框架正在引起越来越多的web开发者的注意。原因何在?一方面,因为它是微软推荐的一个基于Ajax的主要针对ASP.NET 2.0平台的web开发方案。另一方面,因为这个框架登上Ajax舞台的时间如此之晚,但却把一套新的完整的基于Ajax的web开发方案呈现在web技术人员的前面—与遗留ASP.NET系统的有机整合并实现了完全面向对象的客户端JavaScript组件模型等!在本系列文章中,我想结合自己近来的学习详细剖析微软ASP.NET Ajax 1.0框架的客户端数据绑定架构部分所涉及的关键技术。在本篇(上篇)中,我们将主要从理论角度来探讨这个框架的数据绑定架构并给出一个简单示例实现;在下篇中,我们将通过构建两个具有较大区别的示例(一个例子的数据源直接来源于普通的web服务,而另一个例子的数据源则来自于一个SQL Server 2005数据库)来探讨实际开发环境下的基于ASP.NET Ajax 1.0客户端数据绑定技术的应用程序。

关于ASP.NET AJAX Futures January CTP

首先,让我们来观察一下ASP.NET AJAX框架的整体架构图(图1):

图1:ASP.NET AJAX架构图

通常情况下,我们提到ASP.NET AJAX(以后简称“MS AJAX”)框架时,往往指如下的三个部分:

◆ASP.NET 2.0 AJAX Extensions 1.0。这一部分对应于程序集System.Web.Extensions.dll和System.Web.Extensions.Design.dll,它们共包含三个JavaScript文件—MicrosoftAjax.js,MicrosoftAjaxTimer.js和MicrosoftAjaxWebForms.js;

◆ASP.NET AJAX Control Toolkit(在前面的图1没有指出),这个包中提供了大量现成的例子并包含一个功能相当强大的SDK,其目的是进一步简化创建定制的ASP.NET AJAX控件及扩展器;

◆ASP.NET AJAX Futures January CTP(最近又刚发行了一个五月版,但本文中的所有概念及例子照样通用),这一部分对应于程序集Microsoft.Web.Preview.dll(包含三个文件—PreviewScript.js,PreviewGlitz.js和PreviewDragDrop.js)。

【作者注】为了全面理解本文中内容,读者需要安装ASP.NET 2.0 AJAX Extensions 1.0和ASP.NET 2.0 AJAX Futures January CTP(而ASP.NET AJAX Control Toolkit部分可不安装,但强烈建议安装试用)。篇幅所限,对于这些内容的安装不再赘述。

既然在本文中我们的主要目标在于ASP.NET AJAX Futures January CTP(只有它才支持丰富的客户端数据绑定机制),那么现在就让我们开始这一较长的探索历程。

二、命名空间Sys.Preview.Data中的客户端控件简介

从前面的图1中,读者可能会注意到其中有一个基础类库(对应文件PreviewScript.js),这正是整个ASP.NET AJAX Futures January CTP的核心部分。这个库中包含的命名空间和类如下图2所示。

图2:基础类库中的组件

从上图中易见,所有的绑定于数据库及那些负责从web服务中获取数据集的所有的客户端控件都定义于命名空间Sys.Preview.Data中。现在,让我们对其中几个最为重要的控件作一介绍。

(一)DataSource控件

在绝大多数的web应用程序,我们都必须进行数据管理—例如检索和向用户显示数据以及把对对它们的修改保存回数据库等。为此,ASP.NET提供了一个内置的支持对象—DataSource。MS AJAX客户端脚本库也提供了类似的DataSource概念的支持。图3展示了MS AJAX中的高级数据绑定控件与它们可能的ADO.NET 2.0对应物的比较。

图3:MS AJAX客户端数据源控件与其ADO.NET 2.0对应物间的比较

【作者注】根据我的分析,在最新的AJAX Futures CTP中,DataSet似乎被显式地删除掉了(我仔细分析了所有的相关*.js文件,但是发现在PreviewScript.js文件仅支持DataSetConverter)。

总的来看,在AJAX Futures CTP中共存在两种类型的DataSource:

①Sys.Data.DataSource—用于描述一个表格式数据结构(例如一个数据库查询的结果),非常类似于ASP.NET 2.0中的SQLDataSource对象。这个控件可以用作ListView和ItemView控件的客户端数据源。你完全可以从服务器端加载数据并且把修改结果保存回服务器端。

②Sys.Data.XMLDataSource—用于描述一个层次式数据结构(例如一个XML文件),非常类似于ASP.NET 2.0中的XMLDataSource对象。这个控件可以用作XSLTView控件的客户端数据源。注意,这是一个只读数据源—你仅可以读取并把数据显示给用户但是却无法把对它们的修改保存回服务器端。

但请注意在本系列文章中,我们仅探讨前者。

既然所有的客户端高级数据绑定控件都包括于文件PreviewScript.js内,所以让我以图形方式来给出这些控件间的直接关系描述(图4)。

图4:MS AJAX主要数据绑定控件间的层次关系图

下面的列表1相应于DataSource控件的prototype和descriptor定义:

列表1

Sys.Preview.Data.DataSource.prototype = {

_data: null,

_initialData: null,

_autoLoad: false,

_serviceURL: "",

_loadMethod: "",

_serviceType: Sys.Preview.Data.ServiceType.DataService,

_isReady: true,

_dataChangedDelegate: null,

_request: null,

_timeout: 0,

//……omitted

_onDataAvailable: Sys$Preview$Data$DataSource$_onDataAvailable,

get_data: Sys$Preview$Data$DataSource$get_data,

set_data: Sys$Preview$Data$DataSource$set_data,

get_initialData: Sys$Preview$Data$DataSource$get_initialData,

set_initialData: Sys$Preview$Data$DataSource$set_initialData,

get_isDirtyAndReady: Sys$Preview$Data$DataSource$get_isDirtyAndReady,

get_isReady: Sys$Preview$Data$DataSource$get_isReady,

_set_isReady: Sys$Preview$Data$DataSource$_set_isReady,

get_loadMethod: Sys$Preview$Data$DataSource$get_loadMethod,

set_loadMethod: Sys$Preview$Data$DataSource$set_loadMethod,

get_parameters: Sys$Preview$Data$DataSource$get_parameters,

get_serviceURL: Sys$Preview$Data$DataSource$get_serviceURL,

set_serviceURL: Sys$Preview$Data$DataSource$set_serviceURL,

get_serviceType: Sys$Preview$Data$DataSource$get_serviceType,

set_serviceType: Sys$Preview$Data$DataSource$set_serviceType,

get_rowCount: Sys$Preview$Data$DataSource$get_rowCount,

initialize: Sys$Preview$Data$DataSource$initialize,

onDataPropertyChanged: Sys$Preview$Data$DataSource$onDataPropertyChanged,

onRequestComplete: Sys$Preview$Data$DataSource$onRequestComplete,

onLoadComplete: Sys$Preview$Data$DataSource$onLoadComplete,

ready: Sys$Preview$Data$DataSource$ready,

load: Sys$Preview$Data$DataSource$load,

save: Sys$Preview$Data$DataSource$save

}

Sys.Preview.Data.DataSource.descriptor = {

properties: [ { name: 'data', type: Object },

{ name: 'autoLoad', type: Boolean },

{ name: 'initialData', type: String },

{ name: 'isDirtyAndReady', type: Boolean, readOnly: true },

{ name: 'isReady', type: Boolean, readOnly: true },

{ name: 'loadMethod', type: String },

{ name: 'rowCount', type: Number, readOnly: true },

{ name: 'serviceURL', type: String },

{ name: 'parameters', type: Object, readOnly: true },

{ name: 'serviceType', type: Sys.Preview.Data.ServiceType } ],

methods: [ { name: 'load' },

{ name: 'save' } ],

events: [ { name: 'dataAvailable', readOnly: true } ]

}

Sys.Preview.Data.DataSource.registerClass('Sys.Preview.Data.DataSource', Sys.Component);

【注】这段代码截获于调试过程中的源码文件。

根据我的分析,上面所有prototype块中的内容均可以用于JavaScript编程中,但是仅有那些位于descriptor块中的内容才可用于xml-script声明性编程中(在本系列中,我们将主要讨论这种方法)。

DataSource控件中唯一的事件—dataAvailable

DataSource控件仅含有一个自定义的(不包含那些继承自父类的事件)也是非常重要的事件—dataAvailable。当DataSource控件中的数据加载完成时激活这个事件。从随同MS AJAX的示例Tasklist中,我们可以看到这个事件的典型应用,如下:

列表2—控件DataSource的事件dataAvailable的典型应用场所

<components>

<dataSource id="listsDataSource" serviceURL="TaskListDataService.asmx" />

<dataSource id="itemsDataSource" serviceURL="TaskItemDataService.asmx">

<dataAvailable>

<invokeMethodAction target="listsDataSource" method="load" />

</dataAvailable>

</dataSource>

在此,以声明方式定义了两个DataSource控件。当第二个DataSource itemsDataSource完成加载后,然后调用第一个DataSource的load方法。

下表列出了相应于DataSource控件中的自定义方法。

方法名

描述

load

从服务器端检索数据(根据当前DataSource的配置对服务器端进行查询)。

Save

根据当前DataSource的配置,显式地把数据写向或更新到服务器端—也即是把客户端所作改变保存回服务器。

下面是控件DataSource的方法Save在文件PreviewScript.js中的定义:

列表 3

function Sys$Preview$Data$DataSource$save() {

//……省略

if (this._serviceType === Sys.Preview.Data.ServiceType.DataService) {

var method = "SaveData";

var params = {changeList: changes, parameters: this._parameters,

loadMethod: this._loadMethod};

var onComplete = Function.createDelegate(this, this.onLoadComplete);

var onError = Function.createDelegate(this, this.ready);                

this._request = Sys.Net.WebServiceProxy.invoke(this._serviceURL,

method, false, params, onComplete, onError, this, this._timeout);

}

else {

throw Error.createError("Save is not supported in Handler mode.");

}

从上面的代码中明显看出,只有当属性serviceType的类型被设置为类型DataService时才可以使用Save方法。当我们定义将用于在客户端消费的web服务时这是相当重要的。关于其中的“神秘”方法—SaveData,我们将留于下篇中再讨论。

有关控件DataSource中的自定义属性,请参考如下表格。

属性名

描述

autoLoad

Boolean值,用于指明是否这个数据源控件在初始化后将自动地从服务器端加载数据。注意,如果你想在页面加载时就应该指定数据源的内容,则应使用initialData属性;否则的话,在页面加载后,还需要一次到服务器端的信息馈送。

initialData

伴随页面的加载提供的初始数据。有些场所下,当用户第一次进入页面时就应该在页面中显示一些初始的数据—例如某个列表的第一页的记录。

isDirtyAndReady

指示是否当前DataSource已经完成数据加载,并且数据非空,且数据没有发生改变。

loadMethod

另一个有些“神秘”的方法(我们将在下篇的关于DataService处讨论它)。

rowCount

返回data属性中实际数据的行数。

serviceURL

Web服务的URLDataSource可以从中检索数据。注意,你应该总是设置这个属性。

parameters

添加到服务URL后的参数。仅用于当serviceType的类型设置为handler时。

serviceType

用于指定web服务的类型,可以被设置为DataServicehandler。缺省情况下(也是推荐的)值为DataService,这意味着你的服务要派生自Microsoft.Web.Services.DataService并且应该为典型的数据库CRUD操作提供内置的支持。

Id(定义于父类中)

控件标识。

data

检索自服务器端数据库中的数据,存储于客户端。注意:这个属性仅可以为Array类型或Sys.Preview.Data.DataTable类型。

isReady

指示是否这个DataSource已经完成从服务器加载数据。你可以把这个属性绑定到一个数据绑定控件的enabled属性上,以便当数据正处于加载过程时禁止使用此绑定控件。

下面,我们来讨论另一个DataSource相关控件—DataView。

(二)DataView控件

一般说来,我们可以从服务器端获取数据并且通过使用DataSource把它们存储于客户端,然后使用DataTable对象修改我们在客户端取得的数据。然而,有时我们需要在实际展示数据前作一些“修饰”—例如,当数据包含成千上万行记录时我们可以对页面加以分页显示,或者我们的用户仅对其中的一少部分数据感兴趣。为此,MS AJAX框架又引入了DataView和DataFilter两个对象。

DataView控件中定义的属性列表。

属性名

描述

data

被修饰的实际数据,类型为Sys.Data.IDataSys.IArray

filteredData

过滤数据,例如分页数据或经排序的数据。

filters

一个DataFilter对象的集合,用于过滤数据。你可以为DataView指定一组过滤器并把它们逐个应用于你的数据。

hasNextPage

是否存在下一个页面。

hasPreviousPage

是否存在上一个页面。

length

在当前页面中有多少行。

pageCount

在当前DataView中有多少页。

pageIndex

当前页面索引。

pageSize

每一页中有多少行。如果你需要对你的数据进行分页,则应该设置这个属性。

sortColumn

排序记录行所依据的列。如果你需要排序,则应该设置这个属性。

sortDirection

排序的方向,或者是Ascending(缺省的值)或者是Descending

注意,这个DataView对象仅有一个自定义方法—sort。这个方法将根据sortColumn属性和sortDirection属性对数据进行排序操作。此外,你还可以使用命名空间Sys.Preview.UI.Data中的另外两个控件—DataNavigator和SortBehavior来帮助你实现分页和排序。在此,为了完整起见,我们还想对DataFilter对象作一简介。

类Sys.Preview.Data.DataFilter被设计为所有过滤器的抽象基类。它提供一个抽象方法filter以便在派生类中实现特定的过滤规则。

此外,在Futures January CTP中还提供了一个内置的过滤器—PropertyFilter,通过一个指定的属性及其属性值来过滤数据项。

【作者注】首先,这个DataView控件与ADO.NET 2.0提供的DataView控件存在相当不同。其次,因为我们的示例中主要使用的是ListView和ItemView控件,所以对此有兴趣的读者可以从这里得到一个相关示例(尽管这个例子是以前版本的Atlas提供的,但是参考本文中的相关示例,你可以略加修改即可在新环境下运行)。

(三)DataTable控件

现在,我们来讨论另一个重要的控件—DataTable,这个控件实现了Sys.Data.IData接口。通过分析随同MS AJAX框架发行的源代码及示例程序,我们可以容易地发现许多重要的控件(例如DataSource,DataView,ItemView和ListView)都使用该DataTable控件来存储它们的数据。因此,当你在MS AJAX中使用数据绑定时,你将经常与这个控件打交道。但篇幅所限,在此,我们也仅列出它的常用事件、属性和方法。

DataTable控件中的事件定义:

事件名

描述

collectionChanged

当行集合改变时(例如添加,删除或者修改)调用。

propertyChanged

无论何时改变一个或多个属性时都被调用。

DataTable控件中的属性定义:

属性名

描述

columns

返回一个Sys.Preview.Data.DataColumn数组,类似于一个数据库表格的结构。

keyNames

返回一个字符串数组,用于描述DataTable中的关键字列。

length

返回DataTable中所有记录的总数。

isDirty

如果DataTable中的数据已经改变并且还没有被写回到数据库,那么,这个属性被置为true;否则为false

DataTable控件中的方法定义:

方法名

描述

DeleterowObject

从当前DataTable中删除一行(即对应作为参数的行)

get_length()

返回记录总数

addrowObject

在当前DataTable的最后添加一个新行

clear()

删除当前DataTable中的所有数据

createRowinitialData

根据当前列结构创建一个新的Sys.Preview.Data.DataRow

getChanges()

返回针对当前DataTable的修改操作,返回值为下列之一:

·         Updated最近更新Sys.Preview.Data.DataRow

·         inserted新插入的Sys.Preview.Data.DataRow

·         Deleted删除的Sys.Preview.Data.DataRow

getColumnname

根据传递的列名从该DataTable中返回一个DataColumn对象。

getRowindex

返回一个对象DataRow

getItemindex

与方法getRow相同

(四)DataColumn及DataRow控件

读者应该很容易猜出,前面的DataTable包含一个DataColumn对象的集合和一个DataRow对象的集合。既然客户端DataColumn和DataRow控件的设计是用于模仿它们的ADO.NET2.0对应物—DataColumn和DataRow,所以,我们可以从前面的图3中非常容易地理解它们的对应关系。篇幅所限,我们在此省略对这两个控件的讨论。

现在,让我们转而讨论另一个命名空间—Sys.Preview.UI.Data中的几个重要控件。