代码改变世界

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

2007-09-13 09:37  Jacky_Xu  阅读(349)  评论(0编辑  收藏  举报
四、例2—数据库绑定

现在,我们来讨论更为复杂的数据库绑定的例子。根据我们前面的讨论,我们找到了使用DataSource的典型场所;在前面的例1中,我们使用了一种内存数据来模拟有状态的web服务。但是,在实际开发中,一般不这样使用。在本节中,我们要创建一个非常贴近于实际开发环境应用的例子。

1.创建工程

启动Visual Studio 2005并选用模板“ASP.NET AJAX CTP-Enabled Web Site”创建一个新的网站并命名为DatabaseBinding,最后选定Visual C#为内置语言。然后,稍微修改页面Default.aspx,最后的结果如下图4所示。

图4:示例2的设计时刻页面

2.后台代码分析

下面是页面Default.aspx中HTML元素的定义部分。

列表6

<!--------------------------------------------------->

<div id="detailsView"></div>

<!--------------------------------------------------->

<input type="button" id="previousButton" value="Previous"

title="Go to previous row" style="width: 67px; height: 30px;" />

<span id="rowIndexLabel"></span>

<input id="nextButton" type="button" value="Next" title="Go to next row"

style="width: 67px; height: 30px;" />

<input type="button" id="addButton" value="Add" title="Create a new row"

style="width: 67px; height: 30px;" />

<input type="button" id="delButton" value="Delete"

title="Delete the current row" style="width: 67px; height: 30px;" />

<input type="button" id="saveButton" value="Save"

title="Save all pending changes" style="width: 67px; height: 30px;" />

<input type="button" id="refreshButton" value="Refresh"

title="Discard pending changes and get the latest data from the server"

style="width: 73px; height: 30px" />

<!--------------------------------------------------->

<div style="visibility:hidden;display:none" >

<div id="detailsTemplate" class="ListWindow">

Name: <input id="nameField" size="30" /><br />

Address:<br />

<textarea id="addressField" style="width: 428px;

height: 130px" rows="4" cols="4"></textarea><br />

</div>

<div id="emptyTemplate">

Loading Data...

</div>

</div>

根据图4中的布局和上面的代码,我们在此首先定义了两个导航按钮—previousButton和nextButton—它们都用于显示于客户端(而不是服务器端)的控件ItemView相应的数据源的数据记录间的导航。然后,我们定义了两个按钮(addButton,delButton)以实现对数据库中记录的修改操作。最后两个按钮—saveButton和refreshButton直接相应于MS AJAX客户端控件DataSource的save和load两个方法。之后,我们使用了一组HTML DIV元素来描述控件ItemView。在此,建议你把这里的对应关系与例一1中的控件ListView与HTML元素的对应关系加以比较。

3.创建一个连接到数据库的Web服务

(1)创建一个示例数据库—DataBind.mdf

右击工程并选定“添加新项”,然后选择模板“SQL数据库”,你可以容易地创建一个空的数据库—在此,我们命名它为DataBind.mdf。然后,我们把唯一的一个表(Employees)添加到其中。这个表中包含三个字段:Id(int,primary key),Name(nvarchar(50),not empty)和Address (nvarchar(50),not empty)。同时,我们还创建了四个简单的存储过程:DeleteRecord,GetAllRecords,InsertRecord,UpdateRecord,它们相应于典型的数据库CRUD操作。因为我们的重点不在此,所以不再详细加以讨论。

(2)创建一个类—Employee

注意,这个类非常类似于第一个例子中的类Employees—充当数据库表格的OOP包装;具体地说,这是通过把它的修饰有属性DataObjectField的成员变量映射到定义于表格Employees中的字段实现的。

(3)两个帮助者类—SqlHelper(来自于MS AJAX示例中)和SqlTaskProvider

为了问题的简化和通用起见,我们创建了两个帮助者类。一个是SqlHelper(来自于随同MS AJAX发行的示例程序TaskList);另一个是SqlTaskProvider。由于这些内容有些远离了本文的主题,所以,在此也不多解释,有兴趣的读者可详细研究本文所附源码。

现在,让我们来创建一个DataService(派生自Web Service)并使之与数据库相连接。

2.创建连接到数据库的DataService

下面,我们先列出这个DataService相关的MyDataService.asmx的关键代码:

列表7

//……(省略)

using System.ComponentModel;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.Data;

using System.Web.Script.Services;

using Microsoft.Web.Preview.Services;

using Demos.Employee;//defines class SqlTaskProvider

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[ScriptService]

public class MyDataService : DataService

{

[WebMethod]

[DataObjectMethod(DataObjectMethodType.Delete)]

public void DeleteRecord(Employee emp)

{

if (emp.Name == null)

{

throw new AccessViolationException();

}

new SqlTaskProvider().DeleteRecord(emp);

}

[WebMethod]

[DataObjectMethod(DataObjectMethodType.Select)]

public List<Employee> GetAllRecords()

{

return new SqlTaskProvider().GetAllRecords();

}

[WebMethod]

[DataObjectMethod(DataObjectMethodType.Insert)]

public void InsertRecord(Employee emp)

{

if (emp.Name == null)

{

throw new AccessViolationException();

}

new SqlTaskProvider().InsertRecord(emp);

}

[WebMethod]

[DataObjectMethod(DataObjectMethodType.Update)]

public void UpdateRecord(Employee emp)

{

if (emp.Name == null)

{

throw new AccessViolationException();

}

new SqlTaskProvider().UpdateRecord(emp);

}

}

略微加以分析,你应该得如图5所示的调用关系图:

图5:例2中各主要模块间的调用关系

接下来,让我们分析数据绑定是如何在客户端实现的。注意,这里我们仍然使用xml-script声明性方式。

4.客户端声明性编程

在此,非常类似于前一个例子,我们首先建立HTML元素与控件ItemView的模板间的映射关系:

列表8

<script type="text/xml-script">

<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">

<components>

<dataSource id="EmployeeDataSource" serviceURL="MyDataService.asmx" >

</dataSource>

<itemView id="detailsView">

<bindings>

<binding dataContext="EmployeeDataSource"

dataPath="data" property="data" />

<binding dataContext="EmployeeDataSource"

dataPath="isReady"   property="element" propertyKey="enabled"/>

</bindings>

<itemTemplate>

<template layoutElement="detailsTemplate">

<textBox id="nameField">

<bindings>

<binding dataPath="Name"

property="text" direction="InOut"/>

</bindings>

</textBox>

<textBox id="addressField">

<bindings>

<binding dataPath="Address"

property="text" direction="InOut"/>

</bindings>

</textBox>

</template>

</itemTemplate>

<emptyTemplate>

<template layoutElement="emptyTemplate" />

</emptyTemplate>

</itemView>

在此,有几处需要注意。首先,控件ItemView典型地用于显示一条记录—基于MS AJAX客户端数据绑定方案,而控件ListView却用于显示满足一定范围的一批记录。其次,控件ItemView使用了两个绑定:第一个绑定将把从DataSource返回的数据绑定到控件ItemView的data属性上,以确保ItemView控件能够从数据源取得它所要求的完整的数据;第二个绑定把ItemView控件的enabled属性绑定到DataSource的IsReady属性上。这意味着,当数据源还没有准备好时(例如数据源正在从服务器端读写数据),控件ItemView将被禁用。第三,我们使用了双向绑定技术,这意味着不仅源控件属性(dataContext属性指向的那个)的改变将更新目标控件相应的属性,而且反过来也如此。最后,我们还要注意,DataSource的改变将使数据变‘脏’—DataSource控件的isDirty属性将被置为true。

接下来,让我们看一下页面中使用的两个导航按钮的定义。

列表9

<button id="previousButton">

<click>

<invokeMethodAction target="detailsView" method="movePrevious" />

</click>

<bindings>

<binding dataContext="detailsView" dataPath="canMovePrevious" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

<label id="rowIndexLabel">

<bindings>

<binding dataContext="detailsView" dataPath="dataIndex"

property="text" transform="Add" />

</bindings>

</label>

<button id="nextButton">

<click>

<invokeMethodAction target="detailsView" method="moveNext" />

</click>

<bindings>

<binding dataContext="detailsView" dataPath="canMoveNext" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

在此,控件ItemView提供的一些方法和属性用于实现加载到其中的相邻记录之间的导航。如果用户正在浏览第一条记录,那么属性canMovePrevious被设置为false;否则为true。此外,我们还为按钮previousButton的click事件指定了一个相应的行为。至于按钮nextButton,情况与之一致。另外,我们通过dataIndex属性来读取当前记录的索引值并把它绑定到label控件。

现在,让我们来讨论最有趣也是最重要的与数据库相关的CRUD操作部分。

列表10

<button id="addButton">

<click>

<invokeMethodAction target="detailsView" method="addItem" />

</click>

<bindings>

<binding dataContext="EmployeeDataSource" dataPath="isReady" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

<button id="delButton">

<click>

<invokeMethodAction target="detailsView" method="deleteCurrentItem" />

</click>

<bindings>

<binding dataContext="EmployeeDataSource" dataPath="isReady" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings> 

</button>

<button id="saveButton">

<click>

<invokeMethodAction target="EmployeeDataSource" method="save" />

</click>

<bindings>

<binding dataContext="EmployeeDataSource" dataPath="isDirtyAndReady" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings>

</button>

<button id="refreshButton">

<click>

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

</click>

<bindings>

<binding dataContext="EmployeeDataSource" dataPath="isReady" 

property="element" propertyKey="disabled" transform="Invert" />

</bindings> 

</button>

在此,当我们需要向数据集中添加新记录时,调用控件ItemView的addItem方法—此时数据源必须准备好。对于按钮delButton,情况也非常类似。当数据源准备好后,调用控件ItemView的方法deleteCurrentItem;否则按钮delButton被置为disabled。

对于按钮saveButton,情况则比较复杂。只有当数据源变‘脏’并且数据源已经准备好后,我们才能够保存数据。读者应该还记得在前面定义的那几个TextBox控件(它们位于ItemView控件的ItemTemplate模板内,并且都是进行双向的数据绑定)。所以,当用户更改任何一个TextBox控件中的内容时,ItemView控件的数据集将被自动更新,而且其数据源中的数据集也是如此。最后,数据源变‘脏’;同时,数据源也准备好,于是属性isDirtyAndReady被置为true—此时,按钮saveButton才会激活可用;否则不可用。

当你点击按钮refreshButton时,将再次发生一次SELECT查询,这又进一步触发所有绑定并把最新数据加载到当前页面中的控件内。请注意,这里的刷新操作是以AJAX方式(异步)实现的,因此,仅有控件ItemView被更新而不会产生整个页面的闪烁问题。

4.运行程序

如果没有什么问题的话,按下F5键,你将会看到如下图6所示的运行时刻快照。

图6:例2的运行时刻快照

乍看这个屏幕,你会感觉它非常类似一个传统的桌面数据库应用程序的界面,但实际上其中显示的数据却是来自于一个远方的服务数据库!在此,我们再次领略了MS AJAX框架的威力。

五、总结

在本系列的这两篇文章中,我们深入剖析了微软ASP.NET Ajax中的数据绑定构架。因为本人也是这个框架的新手,而且这个框架也一直处于发展当中(特别是Futures CTP部分),所以在其中涉及的许多概念和例子里面很可能存在一定的错误,真诚希望读者朋友能够帮助批评指正。