John Papa
代码下载位置: SLData
services2008_09a.exe (234 KB)
在线浏览
代码
本专栏基于 Silverlight 2 的 Beta 2 版本。文中的所有信息均有可能发生变更。
下载本文中所用的
代码: DataPoints2008_09a.exe (414 KB)
浏览在线
代码
目录
示例应用程序
跨域通信
Silverlight 客户端
绑定产品列表
异步通信
产品详细信息和绑定模式
更改事件
结束语
毋庸置疑,Silverlight™ 2 使得利用大量图形处理技术构建丰富 Internet 应用程序 (RIA) 变得非常容易。但另一方面,Silverlight 2 可以轻松构建相当专业的业务线 (LOB) 应用程序也是不争的事实。Silverlight 2
支持已启用了 Windows® Presentation Foundation (WPF) 的
功能强大且基于 XAML 的数据绑定子集。Silverlight 2 中的 XAML 绑定
标记扩展简化了将实体绑定到 Silverlight 控件的过程。由于它们完全在客户端计算机上运行,因此 Silverlight 应用程序与通过服务器管理的实体是相互隔离的。这样一来,那些通过
RSS、具象状态传输 (REST) 以及 Windows Communication Foundation (WCF) 等技术实现的基于服务的通信必须是可供使用的。幸运的是,Silverlight 2
支持与这些技术和其他通信途径的交互,这使得 Silverlight 应用程序可以与后端 LOB 应用程序无缝交互。
我将演示如何构建 Silverlight 2 UI,以使其通过与 WCF 的通信实现与业务实体和
数据库的交互。对于业务逻辑、实体模型和数据映射
代码,任何表现层都可以使用它们。我会创建将由 Silverlight 2 应用程序使用的 WCF 服务,并建立托管 WCF 服务的服务器以允许跨域
调用。请注意,您可以从《MSDN® 杂志》网站下载这些示例。
示例应用程序
在开始编写
代码之前,我们先深入了解一下此示例。图 1 呈现的是完整的应用程序,其中
显示了从 Northwind
数据库检索到的产品列表。从 List
Box 中选择了某个产品后,该产品将被绑定到
页面下半部分的控件上。当
用户通过
check
Box 和 Text
Box 控件编辑产品并单击“Save”(保存)按钮时,产品信息会随即通过 WCF 发送到
数据库中。单击“Cancel”(取消)按钮可通过 WCF 从服务器获得最新产品列表,同时更新 List
Box 及其绑定。
图 1 示例 Silverlight 应用程序(单击图像可查看大图)
Silverlight 2 示例应用程序由为数不多的几个
用户控件和样式构成。表现层利用异步
调用通过 WCF 与服务器进行通信。它使用 WCF 服务引用并依照服务的操作约定和数据约定来实现 Silverlight 应用程序与服务的通信。数据约定(例如 Product 或 Category
实体类)公开了服务器应用程序中的实体结构。这使得 Silverlight 应用程序的控件可以轻松绑定到这些实体的实例及其
属性上。操作约定定义了 Silverlight 应用程序
调用 WCF 服务的
方法。图 2
显示的是此体系结构的高层次概观。
图 2 示例应用程序的体系结构模型
我将从 WCF 服务本身着手,先利用较低的层开始构建示例应用程序。您可以通过在 Visual
studio® 中创新建
一个 WCF 项目来构建可与 Silverlight 应用程序进行通信的 WCF 服务。只要 Silverlight 应用程序具有 basic
httpBinding 类型的绑定,它就可以
调用标准的 WCF 服务。您必须确保自己可将 WCF 服务的
默认绑定从 ws
httpBinding 更改为 basic
httpBinding,否则必须新建
一个 basic
httpBinding 类型的绑定。例如,示例 WCF 服务宿主应用程序的 web.con
fig 文件包含用来定义服务配置的以下 XML。请注意,端点绑定被定义为 basic
httpBinding:
作为创建 WCF 服务的替代
方法,您可以在 Visual
studio 中选择
文件项目模板来创建启用 Silverlight 的 WCF 服务。
图 3 显示的是 Visual
studio 中的新项目模板。此模板会@L_
262_41@将绑定设置为 basic
httpBinding 并
添加一些
属性,以使服务与 ASP.NET 兼容。尽管此
方法可为您设置正确的绑定配置,但不要忘记您仍可使用现有的 WCF 服务,但前提是这些绑定是针对 basic
httpBinding 设置的。
图 3 启用 Silverlight 的 WCF 模板(单击图像可查看大图)
WCF 服务必须能够请求产品列表来填充 List
Box,而且必须能够保存
用户对产品所做的任何更改。这都是一些简单的操作,无需专门的 Silverlight 技术。示例应用程式使用
一个名为 NW
serviceGateway 的 WCF 服务类,用于实现接口 INW
serviceGateway。此处所示的 INW
serviceGateway 被修饰为
serviceContract,它可以使实现此接口的所有类都通过 WCF 加以公开:
[
serviceContract(Namespace = "")]
publi
c interface INW
serviceGateway
{
[OperationContract]
Product FindProduct(int productId
);
[OperationContract]
List FindProductList(
);
[OperationContract(Name="FindProductListByCategory")]
List FindProductList(int categoryID
);
[OperationContract]
List FindCategoryList(
);
[OperationContract]
void SaveProduct(Product product
);
}该接口列出了使用 OperationContract
属性修饰的多种
方法。OperationContracts 可通过 WCF 服务
调用。请注意,FindProductList
方法具有两个重载。
一个接受参数,
而另一个不接受。尽管这一点在 Microsoft® .NET Framework
方法中是
完全可以接受的,但 WCF 却无法公开具
有相同
名称的两个
方法。要
解决此问题,您可以
重命名该
方法或在服务定义中使用 OperationContract 的 Name
属性指定
一个不同的
名称。图 4
显示的是如何在 WCF 服务中向
一个使用了新
名称的操作公开 FindProductList
方法及参数。
public List FindProductList()
{
var productList = new List(
);
using (var cn = new
sqlConnection(nwCn))
{
const
String
sql = @"
SELECT p.*,c.CategoryName "
+ " FROM Products p INNER JOIN Categories c ON "
+ " p.CategoryID = c.CategoryID ORDER BY
p.productName";
cn.
open();
using (var cmd = new
sqlCommand(
sql,cn))
{
sqlDataReader rdr = cmd.ExecuteReader(
CommandBehavior.CloseConnection
);
if (rdr
!= null)
while (rdr.Read())
{
var product = CreateProduct(rdr
);
productList.Add(product
);
}
return productList;
}
}
}WCF 服务需
要做的只是实现此服务约定接口并
调用一个 Manager 类,此类的任务是从
数据库获取产品并将其映射到 List,以
便它们可以从服务中传送出去。图 4
显示的是用来从 Northwind
数据库获取产品列表、将各个产品映射到 Product 类并将各个产品实例
添加到 List 中的
代码。
由 WCF 返回的实体必须用 DataContract
属性进行修饰,以使其能够被正确序列化并发送给 Silverlight 应用程序。图 4 中涉及的 Product 类具有 DataContract
属性,并且它的所有
属性都使用 DataMember
属性加以修饰。这将告知 WCF 开始序列化并将实体及其 DataMember
属性提供给 Silverlight 应用程序使用。当
调用 WCF 服务的 FindProductList
方法时,Silverlight 客户端应用程序将接收 List,并且将能够引用使用 DataMember
属性修饰的所有特性。
跨域通信
Silverlight 应用程序将在客户端计算机环境中执行。这就会产生一些问题,即基于 ASP.NET Web 的应用程序当前并不会
显示出来,因为它们都是在服务器上执行而在客户端上呈现 HTML 和编写脚本
代码。由于 Silverlight 是在客户端上执行的,因此它必须使用面向服务的技术(如 WCF)从该服务器请求信息。不过,必须要对 WCF 服务进行保护以防止某些不必要的客户端应用程序利用它。示例 Silverlight 应用程序承载在您信任的 Web 服务器上,因此应允许它与示例应用程序的 WCF 服务进行交互。如果承载在另一 Web 服务器上的其他应用程序试图与示例 WCF 服务进行通信,应将其拒绝。
对这种服务访问的控制是通过跨域策略
文件进行处理的。Adobe Flash 应用程序有
一个标准
文件,可用来处理这个名为 CrossDomain.xml 的
文件(位于该服务的 Web 服务器的根目录下)。Silverlight 应用程序的行为也非常相似,首先在 Web 服务器的根目录(不是 Web 应用程序的根目录)中查找名为 ClientAccessPolicy.xml 的
文件。如果找到该
文件,应用程序将读取它以确定是允许还是拒绝请求。如果未找到,应用程序将继续查找 CrossDomain.xml
文件。如果均未找到,该请求将被拒绝,且 Silverlight 客户端应用程序也将无法
调用该 WCF 服务。
每个
文件的
内容都必须允许
调用方具有对这些服务的权限。由于示例应用程序仅存在于受保护的开发计算机中,因此其 ClientAccessPolicy.xml 将允许所有请求,如下所示:
我创建的 ClientAccessPolicy.xml
文件允许从所有位置跨域访问全部路径。此
文件的副本
包括在示例应用程序中。在首次创建 WCF 项目后,
默认选项将使用已被@L_
262_41@分配端口的 Visual
studio Development Server。但是,示例应用程序的 WCF 服务项目被设置为“使用 IIS Web Server”,此设置可以在 Web 选项卡的项目
属性页面中进行更改。只要 ClientAccessPolicy.xml
文件被放置在 Web
站点的根目录下,每个选项就都有效。
这些策略可加以限制,以仅允许某些 URI 能够访问特定
文件夹路径中的特定服务。要执行此操作,必须在此
文件中指定承载 Silverlight 应用程序的 Web 服务器,这样它才能与 WCF 服务交互。例如,受限程度较高的跨域策略可能如下所示:
请注意,源自 johnpapa.net 中某项服务的任何请求都被允许访问服务器上 /MyAwesome
services 路径下的服务。
调试具有跨域
调用的问题可能有一些难度。您可以使用诸如 Web Development
Helper(我的最爱)或
fiddler 之类的工具来检查 Silverlight 应用程序与基于服务器的服务之
间的通信流量。这些工具将
显示各个单独的
http 请求和响应。您还应确保 ClientAccessPolicy.xml
文件被放置在 Web 根目录下,而不是应用程序根目录下。我不再占用篇幅来强调这一点。
此外,当在 Visual
studio 项目中承载服务时(例如,未直接位于 IIS 中),该项目会创建
一个@L_
262_41@分配的端口,测试此服务的最简单
方法是将 ClientAccessPolicy.xml
文件放置在项目自身的根目录下。由于该项目得到了
一个@L_
262_41@分配的端口(如 localhost:32001/MyAwesome
service),
它将在 Web 的根目录下寻找 ClientAccessPolicy.xml
文件(
在本例中为 localhost:32001)。
Silverlight 客户端
编译好这些服务并建立了跨域策略后,即可建立 Silverlight 客户端以与 WCF 服务进行通信。示例应用程序中有
一个名为 SilverlightWCF 的 Silverlight 客户端项目,它需要
一个对示例 WCF 服务的服务引用。在“Solution Explorer”(
解决方案资源管理器)中右键单击引用节点,然后选择“Add
service Reference”(
添加服务引用)。输入该服务的 URL,单击“GO”(
搜索)按钮,服务将会
显示出来。单击“OK”(确定),服务客户端配置以及
生成的代理类将被
添加到 Silverlight
项目中,以简化
调用服务和使用实体(如 DataContract 所定义)的过程。图 5
显示的是
添加服务时出现的对话窗口。
请注意,
默认情况下此示例将使用位于 localhost/MySilverlightWcf
service/NW
serviceGateway.svc 目录下的服务。毋庸置疑,如果移动了该服务,则此端点地址也需要
相应的更新。如果在 WCF 服务中进行了少量更改(如
添加新
方法或 DataContract),您可以在“Solution Explorer”(
解决方案资源管理器)中选择“
update
service Reference”(更新服务引用)。
绑定产品列表
示例应用程序需要向
用户显示产品列表。
在这里 List
Box 控件将与 DataTemplate 配合使用,这样就可以在展示产品时带有一些特殊
效果,而不再是简单地在行和列中列出值(List
Box 最初如图 1 所示)。通过设置 List
Box 的 Item
source
属性,我们指明 List
Box 将从绑定数据源中
获取它的值,如以下
标记所示:
请注意,Item
source
属性只指明
它将被绑定,而无法指明任何具体的对象或
属性。由于未指定任何源,List
Box 将引用 XAML 中任意继承的 DataCo
ntext 对象源。DataCo
ntext 可从任何父 FrameworkElement 中继承。在 WCF 服务返回产品列表后,示例应用程序将在
代码隐藏中设置 List
Box 的 DataCo
ntext,其
名称为 lbProducts:
lbProduct
s.DataCo
ntext = productList;在执行此
代码后,List
Box 将被绑定到产品列表,而 DataTemplate 中的每个 List
BoxItem 都被绑定到列表中的各个产品上。图 6
显示的是
用于创建 List
Box 并将 DataTemplate 中展示的项目绑定到源的 XAML。请注意,被绑定到源的各个控件将使用绑定
标记扩展来指示它们将被绑定到 Product 的哪个
属性,并指出将使用 OneWay 绑定模式。例如,
Text="
{Binding ProductName,Mode=OneWay}" 图 6 List
Box XAML
所有绑定 Text
Box 控件的绑定模式都被设置为 OneWay。这表示源(即 Product 实例)在最初加载时以及当源中发生任何变更时都应将其值推至目标(List
Box 和其中绑定的任意项目)(有关“模式”的详细信息,请参阅“绑定模式”部分)。
异步通信
在绑定得以完成之前,必须从 WCF 服务中
获取产品列表。Silverlight 中的所有 WCF 服务
调用都是通过异步通信进行的。示例 WCF 服务发布了
一个名为 FindProductList 的 OperationContract。由于与 Silverlight 的通信是异步的,因此这一约定是使用
异步方法(此
异步方法可启动在操作完成时所引发的通信和事件)在
生成的代理中实现的。
private void FindProductList()
{
thi
s.cursor =
cursor
s.Wait;
var proxy = new NW
serviceGatewayClient(
);
proxy.FindProductListCompleted += new
EventHandler(
FindProductListCompleted
);
proxy.FindProductListAsync(
);
}代理创建完成后,将向 FindProductListCompleted 事件
添加一个事件处理程序。这就是将要接收异步 WCF 服务
调用结果的
方法。最
后执行 FindProductListAsync
方法。
当异步 WCF 服务
调用完成后,将执行事件处理程序。以下
代码显示了事件处理程序 FindProductListCompleted 接收产品列表并将其绑定到 List
Box 的 DataCo
ntext 的过程:
private void FindProductListCompleted(ob
ject sender,
FindProductListCompletedEventArgs
E)
{
ObservableCollection productList = e.Result;
lbProduct
s.DataCo
ntext = productList;
if (lbProduct
s.Item
s.Count > 0)
lbProduct
s.SELEctedIndex = 0;
thi
s.cursor =
cursor
s.Arrow;
}产品详细信息和绑定模式
在 List
Box 中选择了某个项目后,事件处理程序中的
代码将从 List
Box 的
SELEctedItem 中
获取所选的 Product,然后将其设置为 Grid 布局控件的 DataCo
ntext。Grid 布局控件被用作产品详细信息部分的容器,
其中包含一系列可供
用户对所选产品进行编辑的控件。由于 DataCo
ntext 是在控件树中自上而下继承的,因此无需在各个 Text
Box 和
check
Box 控件中设置 DataCo
ntext。这些控件都是从其父 Grid 控件(已在 lbProducts_
SELEctionChanged 事件处理程序中设置)继承 DataCo
ntext 而来。
Grid 布局控件中有多个包含绑定声明的 TextBlock、Text
Box 和
check
Box 控件。此部分的几个 XAML
代码段如下所示:
请注意,第
一个代码段表示的是 Text
Box,它
显示 Product 源对象中的 UnitsInStock
属性。第二个
代码段
显示的是 Product 实例的 CategoryName
属性的值。请注意,Text
Box 中绑定的 Mode
属性被设置为 TwoWay,而在 TextBlock 中被设置为 OneWay(此为
默认设置)。
绑定的 Mode
属性是
一个重要的绑定设置,可用于确定在目标与源之间发生绑定的频率及方向。图 7
显示的是 Silverlight XAML 及其更新时可以使用的三种绑定模式。
图 7 Silverlight XAML 中的三种绑定模式
绑定模式
OneTime
OneWay
TwoWay
在首次设置了 DataCo
ntext 后目标会进行相应更新
Yes
Yes
Yes
在源发生更改时目标会进行相应更新
No
Yes
Yes
在目标发生更改时源会进行相应更新
No
No
Yes
当应用程序启动或 DataCo
ntext 发生更改时,OneTime 绑定将源发送到目标。OneWay 绑定也可以将源发送到目标。但是,如果源实现 INotifyPropertyChanged 接口,则当源发生更新时目标将接收到更新。TwoWay 绑定会将源数据发送到目标,但如果目标
属性的值发生更改,则会将这些更改返回给源。如果源对象实现 INotifyPropertyChanged 并且如果源的
属性 setter 引发了 PropertyChanged 事件,则 OneWay 和 TwoWay 绑定都只告知目标有关更改的消息。
在上一示例中,OneWay 绑定被用于 CategoryName,因为它
显示在 TextBlock 中而且无法进行编辑。TwoWay 绑定被用于 UnitsInStock,因为它
显示在可编辑的 TextBlock 中。当
用户在 Text
Box 中编辑 UnitsInStock 的值且 Text
Box 失去焦点时,更改结果将被发送回源对象。
OneTime 绑定最适合表示那些在
显示时从不
会发生更改的只读信息。OneWay 绑定适合表示那些在
显示过程中可能会
在某个时刻发生更改的只读信息。进一步说,如果在 Category@R_206_
4687@tBlock 中使用了 OneTime 绑定而不是 OneWay 绑定,则单击“取消”按钮并且产品列表被刷新后,使用 OneTime 绑定模式的 TextBlock 将不会更新。最后,当
用户必须能够更改某个控件中的数据并能够使更改
在数据源中反映出来时,最好选择使用 TwoWay 绑定。
更改事件
当源发生更改时,使 OneWay 和 TwoWay 绑定
通知目标的关键一点是实现 INotifyPropertyChanged 接口。Product 类可实现此接口,该类由单个事件 PropertyChanged 组成。PropertyChanged 事件接受发送者和发生更改的
属性的
名称作为参数。Silverlight 数据绑定侦听这些来自数据源的事件。如果该类既不实现此接口也不在
属性 setter 中引发 PropertyChanged 事件,则目标将永远不会收到更新。PropertyChangedEventHandler 在 Product 类中通过下列几行
代码实现:
public override event PropertyChangedEventHandler PropertyChanged;
在这种情况下,该事件将被覆盖而不是简单地定义,因为 Product 类将从
自定义的 EntityBase 类(此类可实现 INotifyPropertyChanged 接口)进行继承。事实上,这里介绍的 EntityBase 类可实现 PropertyChangedEventHandler,因此可以在 Product 或 Category 等派生类(如 )中将其覆盖:
[DataContract]
public abstract class EntityBase : INotifyPropertyChanged
{
protected void PropertyChangedHandler(EntityBase sender,
String property
Name)
{
if (PropertyChanged
!= null)
PropertyChanged(sender,new PropertyChangedEventArgs(
property
Name));
}
public virtual event PropertyChangedEventHandler PropertyChanged;
}为方
便起见,也可以
使用 ENtityBase 类为派生
类定义 PropertyChangedHandler
方法,这反过来
又可以引发 PropertyChanged 事件。这是
一个简单的重构过程,它可将引发事件的逻辑放置到单一位置上并可减少
代码量。例如,具有 UnitPrice
属性的 product 类可设置产品价格值并
调用基础类的 PropertyChangedHandler
方法:
[DataMember]
public
decimal UnitPrice
{
get
{ return _unitPrice; }
set
{
_unitPrice = value;
base.PropertyChangedHandler(this,"UnitPrice"
);
}
}此
方法反过来又会引发 PropertyChanged 事件。然后此事件将被发送到任意 OneWay 或 TwoWay 绑定,从而使这些绑定将目标控件更新为新值。Product 类中每个
属性的 setter 都使用此模式。
属性更改
通知可通过示例应用程序进行演示,
方法是从 List
Box 中选择
一个产品,然后在 Text
Box 中编辑单价。由于 tbUnitPrice Text
Box 使用 TwoWay 绑定,这会使新价格被发送到源。一旦源
被更新,即会引发 PropertyChanged 事件。这会使侦听此事件的所有绑定都更新目标值。由于 List
Box 实现 OneWay 绑定,因此价格值会@L_
262_41@将其自身更新为新的价格值。
如您所见,利用示例应用程序中所示的声明式绑定语法可轻松实现 Silverlight 2 数据绑定
功能。这些绑定将侦听 PropertyChanged 事件,以
便能够更新其目标。
在本专栏中,我向您演示了通过 Silverlight 实现绑定的容易程度,并介绍了以何种方式与 WCF 服务通信才能与业务对象和
数据库进行交互,还介绍了如何定义 ClientAccessPolicy.xml
文件以允许和限制远程通信。