使用回调合约通知客户端单向操作的结果
使用回调合约的原则是,提供一个服务,该服务采用单向操作—不返回任何信息—的方式通知客户端程序。本小节的例子基于之前描述过的更改产品价格场景。当客户端程序调用ProductsService服务的ChangePrice操作时,当数据库中产品的价格更新后,服务将回调客户端并通知客户端价格已经发生变化。
练习:为ProductsService服务添加回调合约以及添加调用回调的操作
1. 使用Visual Studio,打开ProductsServiceV3.sln,该解决方案位于WCF\Step.by.Step\Solutions\Chapter16\ProductsServiceV3文件夹下。
该服务实现了ListProducts,GetProduct,CurrentStockLevel和ChangeStockLevelcaozuo .此外,它还包含了一个新的操作ChangePrice.客户端程序可以调用该操作以更改产品的价格。此外,该解决方案包含了一个WPF程序寄宿该服务,一个客户端程序用以测试ProdutsService服务。
2. 生成并运行该解决方案。在Products Service宿主窗口,点击start按钮。当服务启动后,在客户端控制台窗口中按下ENTER键。
客户端程序连接到服务,首先显示一个产品列表,然后显示产品FR-M21S-40的详细信息,并更改该产品的价格,最后显示更新后的价格。
当客户端程序结束后,关闭客户端控制台窗口。在Products Service宿主窗口点击Stop按钮以停止服务,然后关闭宿主程序,最后返回到Visual Studio
3. 在解决方案窗口中,打开ProductsClient项目下的Programm.cs文件。
查看Main方法的代码。该方法创建了Client类的一个实例,然后运行该实例的TestProductsService方法。TestProductsService方法创建代理对象,并使用该对象连接到ProductsService服务,然后执行ListPoructs操作和GetProduct操作。TestProductsService方法还调用PriceChange方法以更新指定产品的价格。ChangePrice操作在数据库中产品的价格更新前会一直阻塞客户端调用它,当产品价格更新后,该操作向调用者返回一个Boolean值以标明产品价格是否更新。由于该操作需要耗费一定的时间去更新数据库,你将修改其为单向操作,并为ProductsService服务创建一个回调合约,使用该回调合约通知客户端操作的结果。该策略可以使客户端继续运行而不需等待PriceChange操作的执行完成。
4. 在解决方案浏览器窗口,打开ProductsService服务下的IProductsService.cs文件。添加下面的回调合约:
![](https://i-blog.csdnimg.cn/blog_migrate/25c49d288d29a9ab2a7997afaa0f39b4.png)
该回调合约仅仅包含了一个操作OnPriceChanged。你将在后续步骤中更改ProductsService服务中的ChangePrice操作。该操作的目的是通知客户端作为参数所传入的产品的价格已经更改。请注意该操作指定为单向操作;它仅仅通知客户端而且不返回其他任何响应。
5. 修改IProductsServiceV3接口的ServiceContract特性,使其引用第4步定义的回调合约
![](https://i-blog.csdnimg.cn/blog_migrate/f1471a2ad9ab362c2cfebd15e8a331d9.png)
因为CallbackContract属性必须为类型,所以上述代码使用typeof操作返回IProductsServiceV3Callback接口的类型。
6. 在IProductsServiceV3接口中,修改ChangePrice操作的定义并标记该操作为单向操作。单向操作不能有返回值,所以更改返回类型为void
![](https://i-blog.csdnimg.cn/blog_migrate/20758cc990e2026b5ea0f5e237702877.png)
7. 打开ProductsService.cs文件,找到ChangePrice方法。该方法使用新的产品价格更新AdventureWorks数据库,如果更新成功返回true,否则返回false。
更改该方法的返回类型为void。并修改return false或return true为return。
![](https://i-blog.csdnimg.cn/blog_migrate/3cde68bccb4f98bf5437fa9d060e594e.png)
8. 生成ProductService项目
下面的步骤是在客户都程序中实现回调合约,但首先你需要为客户端生成代理类。
练习:生成客户端代理对象并实现回调合约
1. 使用下面的步骤为客户端程序生成代理类
1).打开Visual Studio命令行工具,并且切换路径到Chapter16\ProductsServiceV3\ProductsService\bin\Debug目录下
2).在Visual Studio命令行工具中,输入下面的命令svcutil ProductsService.dll
3).然后执行下面的命令
Svcutil /namespace:*.ProductsClient.ProductsService *.wsdl *.xsd /out:ProductsServiceProxy.cs
2. 使Visual Studio命令行处于打开的状态;然后回到Visual Studio。在ProductsClient项目中,删除现存的ProductsServiceProxy.cs文件,然后添加刚生成的ProductsServiceProxy.cs文件到ProductsClient项目中。
3. 编辑ProductsClient项目下的Program.cs文件。修改Client类使其实现ProductsServiceCallback和IDisposable接口。
![](https://i-blog.csdnimg.cn/blog_migrate/be6dd0dbf65ccd54239a426fb0ab6acf.png)
ProductsServiceCallback是代理对象中定义了回调合约的接口。所以Client类需要实现该接口。
4. 在Client列的TestProductsService方法后面添加OnPriceChanged方法
![](https://i-blog.csdnimg.cn/blog_migrate/8d55f6e07be45ab5575fc9a0cad36ea2.png)
5. 在OnPriceChanged方法后,添加Dispose方法
![](https://i-blog.csdnimg.cn/blog_migrate/4e4f11b9531b6d3e6feebe341680b95a.png)
6. 在TestProductsService方法中,修改创建Proxy对象的声明:
![](https://i-blog.csdnimg.cn/blog_migrate/69b6b7433f396fb1727c1d2b6f3ee2bd.png)
上述代码常见一个InstanceContext对象,该对象引用Client对象,并作为参数传入到连接对象。请注意该连接所使用端点的名字发生了变化(现在使用WS2007HttpBinding_IProductsServiceV3);在后面的步骤中你将会在客户端的配置文件中添加WS2007HttpBinding_IProductsServiceV3端点的定义。
7. 在TestProductsService方法中,找到if/else中调用proxy对象的ChangePrice方法的代码片段。由于新的ChangePrice操作是单向操作,它不会返回任何值。更改这段代码,并移除if/else过程。
![](https://i-blog.csdnimg.cn/blog_migrate/ee4106afe85c4c4bda53ed06e60e5526.png)
8. 在catch代码后,删除关闭proxy对象的声明;因为现在关闭proxy对象由Dispose方法来处理。
9. 在Programm.cs类的Main方法中,重构创建Client对象的生命,然后调用TestProductsService方法,并操作结束后等待用户按下ENTER键结束程序。
![](https://i-blog.csdnimg.cn/blog_migrate/99cedd098f18a485c0c66c6944921f16.png)
10. 重新生成解决方案。
练习:配置WCF服务和客户端程序使用WSDualHttpBinding绑定
1. 在ProductsServiceHost项目中,使用服务配置编辑器编辑App.config
2. 在配置面板,展开服务文件夹,展开Products.ProductsServiceImpl服务,展开端点文件夹,在WS2007HttpBinding_IProductsServcie端点上点击右键,然后点击删除端点。在服务配置编辑器对话框中,点击确认删除。
3. 在配置面板,在端点文件夹上点击右键,然后点击创建新的服务端点。按照下表内容创建一个新的端点
属性
|
值
|
名字 | WSDualHttpEndpoint_IProductsService |
地址 | http://localhost:8010/ProductsService/Service.svc |
绑定 | wsDualHttpBinding |
合约 | Products.IProductsServiceV3 |
4. 保存配置文件,并退出WCF服务配置编辑器
5. 在ProductsServiceClient项目中,使用服务配置编辑器编辑App.config
6. 在配置面板,展开客户端文件夹,右键点击端点文件夹,然后点击创建新端点以创建一个新的端点。按照下表内容设置新端点
属性
|
值
|
名字
|
WSDualHttpEndpoint_IProductsService
|
地址
|
http://localhost:8010/ProductsService/Service.svc
|
绑定
|
wsDualHttpBinding
|
合约
|
ProductsClient.Products.IProductsServiceV3
|
与服务宿主程序不一样,在客户端你可以保留现有的客户端端点。
7. 保存位置文件,并退出WCF服务配置编辑器。
8. 在非调适模式下启动项目。在产品服务宿主窗口,点击开始。然后再客户端控制台窗口中,按下ENTER键。
客户端程序首先显示一份产品列表,然后电视FR-M21S-40产品的详细信息,然后调用ChangePrice操作使该款产品的价格增加10美元。请注意在Test 3开始后,将显示消息"Callback from service: Price of LL Mountain Frame – Sliver, 40 changed to $274.05"。该消息是由于服务调用OnPriceChanged操作而显示。
![](https://i-blog.csdnimg.cn/blog_migrate/88a085e6279cc1e89da2f611e67564ee.png)
使用回调合约实现事件机制
回调合约允许服务确认客户端程序已经更改了产品的价格,但是接受确认消息的客户端实例可能已经知道这点了,因为客户端激发了更改价格的操作。无疑地,通知其他并发的客户端程序价格已经改变是非常有用的。
你可以使用回调实现一个事件机制;服务发布事件并提供操作以供客户端程序订阅这些事件或取消订阅。当时事件发生时,服务使用一个回调合约发送消息至每个订阅了该事件的客户端。为了实现这点,服务不惜必须引用每个客户端实例。在下面的联系中,你将修改ProdutsService服务以激活客户端程序通过订阅操作以关注感兴趣产品的价格变化。该操作的目的是简化缓存客户端实例的引用,当服务调用OnPriceChanged操作之后将使用的这些客户但实例。你还将添加一个取消订阅操作以使客户端程序可以取消价格更该订阅。
练习:添加订阅和却笑订阅操作至ProductsService服务
1. 在Visual Studio中,打开ProductsService项目下的IProductsService.cs文件。
2. 添加SubscribeToPriceChangedEvent时间和UnsubscribeToPriceChangedEvent事件到IProductsServiceV3服务合约中
![](https://i-blog.csdnimg.cn/blog_migrate/7855d0d95b88ae47feb499050acef348.png)
客户端程序将使用SubscribeToPriceChangedEvent操作关注感兴趣产品的价格变化,使用UnsubscribeToPriceChangeEvent操作指明不再对产品价格变化感兴趣。
3. 打开ProductsService.cs文件,添加下面的私有变量:
![](https://i-blog.csdnimg.cn/blog_migrate/e7910eebb33c7a4d07380d075879ec43.png)
ProductsServiceImpl类针对每个对产品感兴趣的客户端实例,都把服务对其的客户端回调引用添加到该列表中。
4. 添加SubscribeToPriceChangedEvent方法的具体实现
![](https://i-blog.csdnimg.cn/blog_migrate/88c7c892dd1b8bdf86f76f1ef0a8194a.png)
上述方法获取调用ChangePrice操作并保存在订阅列表中的的客户端实例对应回调合约的引用。如果回调合约的引用已经在列表中存在,那么将不会把回调毁约的引用添加到该列表中。
5. 添加UnsubscribeToPriceChangedEvent方法的具体实现
![](https://i-blog.csdnimg.cn/blog_migrate/758e8c2dc4eaa570e0d770454b7e1da1.png)
上述方法从列表中移除调用ChangePrice操作的客户端实例对于的回调引用。
6. 添加下面私有方法到ProductsServiceImple类中
![](https://i-blog.csdnimg.cn/blog_migrate/d4a9b7823ce9d2cad6e591420ad8314c.png)
该方法遍历所有的订阅列表中的所有回调引用。如果发现一个引用,并且该引用是有效的(客户端程序实例仍然处于运行的状态),该方法将调用OnPriceChanged操作,并将对应的产品作为参数传递给OnPriceChanged操作。如果引用是无效的,放方法则从订阅列表中删除该引用。
7. 在ChangePrice方法中,删除获取客户端程序对应的回调引用的声明,并删除调用OnPriceChanged方法的代码。取而代之的是,创建ProductData对象以保存修改产品的详细信息,然后调用raisePriceChangeEvnet方法。
![](https://i-blog.csdnimg.cn/blog_migrate/ecaaf705dbe399d4929e53fe5c9ea2f4.png)
当客户端程序实例更改了一个产品的价格,所有订阅了价格更改时间的客户端程序实例都会由于OnPriceChanged方法的执行而得到通知。
8. 重新生成ProductsService项目
练习:更新WCF客户端程序以订阅价格更改事件
1. 重新生成客户端代理类
2. 关闭Visual Studio命令行工具窗口,然后返回到Visual Studio中。从ProductsClient项目中删除ProductsServiceProxy.cs文件,并添加上一步生成的新版本的ProductsServiceProxy.cs文件
3. 打开ProductsClient项目的Programm.cs文件,添加调用SubscribeToPriceChangeEvent操作的代码
![](https://i-blog.csdnimg.cn/blog_migrate/6a9e0982854954e8a7c47271d246f916.png)
无论何时,当客户端程序实例更新一个产品的价格,服务将调用该客户端实例的OnPriceChanged方法。
4. 重新生成ProductsClient项目。
练习:测试ProductsService服务的价格变更事件
1. 在解决方案浏览器窗口中,在ProductsServiceHost项目上点击右键,选择调适,然后点击创建新的实例。在ProductsService寄宿窗口,点击开始按钮启动服务。
2. 在Windows浏览器中,转到\WCF\Step.by.Step\Solutions\Chapter16\ProductsServiceV3WithEvents\ProductsClient 文件夹下。
在该文件夹中,有一个名为RunClients的命令行文件。该命令文件简单地实现了同时运行三次ProductsClient程序,每次都打开一个新的窗口;该文件的内容如下:
start bin\Debug\ProductsClient
start bin\Debug\ProductsClient
start bin\Debug\ProductsClient
3. 双击RunClients.cmd文件,将会出现三个控制台窗口,每个窗口都创建一个客户端程序实例。在某一个控制台窗口中,按下ENTER键,等待出现产品列表,然后显示FR-M21S-40产品的详细信息,然后更改该款产品的价格。确认回调操作的消息出现。保持当前控制大窗口处于打开的状态。
4. 在另外两个控制台窗口中,按下ENTER键。等待出现产品列表,显示FR-M21S-40产品的详细信息,以及更改产品的价格。确认回调操作的消息出现。请注意,第二个回调操作的消息也出现在第一个控制台窗口中,不仅如此,第一个窗口中还显示了由第二控制台窗口更新后的产品价格。
5. 在最后一个控制台窗口中,按下ENTER键。确认该客户端实例更新了产品的价格并返回了回调消息。然后你可以发现第一个和第二个控制台窗口也输出了回调消息。到现在为止,第一个控制台窗口显示了三个回调消息。
![](https://i-blog.csdnimg.cn/blog_migrate/13e9b0aed4c8ca9120efc9424bfb28e5.png)
6. 在每个控制台窗口中按下ENTER键以关闭客户端程序。在ProductsService寄宿窗口中,点击停止按钮关闭服务,最后关闭ProductsService窗口。
发布和订阅的传送模型
使用回调合约可以非常容易地实现基于WCF的发布和订阅服务。你应当意识到在前面的练习中一定程度上你所配置的环境是一个精心设置并且相对完美的运行环境。如果你在大型企业中,或者在Internetzhong实现这样的一个系统,那么你应当考虑安全性和稳定性,此外还应考虑所选择的模型对WCF服务回调客户端程序所产生的影响。
一般地,实现发布和订阅系统采用三种常见的模型,你可以基于任何一种模型构建WCF创建系统。每个模型都有其优缺点。
推送模型
该模型就是本章前面练习中你所使用的模型。在该模型中,发布者(WCF服务)通过回调合约中的操作直接向每个订阅者(WCF客户端程序)发送消息。服务必须有足够的资源同时回调大量的订阅者。在回调操作返回数据时服务需要对每一个订阅者生成一个新线程、或者在回调操作不返回数据时使用单向操作方式进行回调。 这种方式的主要弱点在于安全性。服务调用客户端的的回调操作可能被客户端防火墙阻塞。
拖拉模型
使用这种模型,发布者在事件发生时更新唯一的且可信任的第三方服务的信息。 每个订阅者周期地查询第三方服务的信息是否已经更新。该模型减少了防火墙的问题,但是在订阅者部分的要求更复杂。使用这个模型还可能会面临扩展性方面的问题—当大量的订阅者频繁地查询第三方服务。另外一个方面,入伙订阅者不是经常地查询第三方服务,那么客户端可能错过事件的发生。
中间人模型
该模型是前面两种模型的结合。发布者在时间发生时更新唯一的且可信任的第三方服务。第三方服务放置于一个特定的位置,比如发不事件的服务和订阅事件的客户顿均信任的网络中。订阅者在第三方服务注册事件,而不是直接在原始服务处订阅事件。
该模型是前面两种模型的结合。发布者在时间发生时更新唯一的且可信任的第三方服务。第三方服务放置于一个特定的位置,比如发不事件的服务和订阅事件的客户顿均信任的网络中。订阅者在第三方服务注册事件,而不是直接在原始服务处订阅事件。