学习完本章,你将掌握:
1.在你的工作流中调用Web服务
2.添加和配置Web服务代理
3.在你的工作流中进行会话(sessions)管理
拿我来说,一些基于网络进行数据的发送和接收的事情很吸引我,由此多年以来我很喜欢写基于通信的代码。当我看到WF内置了连接Web服务和作为Web服务的能力时,我就更深入地对其进行了研究。
WF集成了几个基于XML Web服务的活动,我们将在本章调查客户端(client)一侧的活动。(在最后一章“把工作流作为Web服务”中我们将讨论服务器一侧的活动)。在实际使用InvokeWebService活动之前,我认为我应该描述一下Web服务是怎样工作的,因为在本章以及最后一章中我们需要去理解该术语。
Web服务架构
一个观点是任何基于Internet的根据你的请求来执行任务的服务器就是一个Web服务。但是,在本章以及下一章我将用这个术语来和一个更为精确的解释作对比。这里,我将提到XML Web服务,它是一个基于SOAP协议的服务。
备注:“SOAP”来源于Simple Object Access Protocol(简单对象访问协议)的首字母缩写,它是一个基于XML的通信框架。但是,在当前SOAP的正式发行版本中,该协议就被简单地称为SOAP协议。你能在http://www.w3.org/TR/soap/找到最新的SOAP规范。
促使创建SOAP协议的基本的想法是意图能把原有的数据类型甚至是复杂的数据结构从原来的二进制格式转换到XML中,然后该XML就能在Internet之上发送,就像通过使用HTML发送网页一样。起初的SOAP规范清晰地勾勒了“序列化格式(serialization format)”来对整数、字符串、枚举到数组和复杂结构的描述。后来的规范随着序列化描述语言(serialization descripton languages)的出现而对序列化格式进行了放宽。当前,选择的序列化描述语言是Web服务描述语言(Web Service Description Language),或者称作WSDL。
WSDL可让你的SOAP消息以你认为合适的内容进行格式化。在WSDL格式中你可以简单方便地对内容进行描述并把你的WSDL提供给用户,因此,使用你的服务,从中它们能对你的Web服务输出的结构和方法进行解释。虽然原来的SOAP规范想把SOAP用在远程方法调用方面,但是最新的规范加上了WSDL,允许用在更多的基于消息的架构中,消息的内容可以是你能序列化进XML中的任何东西。
因此,当你引用一个远程的Web服务的时候,你将下载并解释该服务为你提供的WSDL。当你使用.NET的时候,.NET Framework内置的工具会为你做这些解释工作并为你创建一个C#类,你能使用该类去调用远程服务的方法。这个C#类被称作代理(proxy),因为它的行为是替换真正的服务。最终,除了通信延迟,你的软件甚至不会知道它实际上是在和一个Internent上的服务通信。该代理(proxy)使它看起来仿佛该服务就在你的本地计算机上。
再回顾一下,你可以引用一个Web服务,届时它会把它的WSDL发送给你。.NET为你创建一个代理,你能使用它根据远程服务器的规范(按照它的WSDL)去和远程服务器进行通信。当你进行通信的时候,你发送的数据会被转换到XML中并在(通信)线路上使用SOAP协议进行发送。远程服务器对你的请求和响应进行解释。当你接收到服务器的响应时,响应的XML会被转化回你的代码所要使用的二进制数据。
备注:对你来说,继续讨论Web服务以及它们是如何执行的来说将是非常容易的,但是我们真正需要的是继续深入并把它迁移进工作流中。但是,假如你想学习更多东西的话,这方面知识的许多的书籍和在线的参考文献都是可以得到的。一个很不错的起点是:http://msdn.microsoft.com/webservices/。
使用InvokeWebService活动
WF内置的面向客户端的XML Web服务支持来源于InvokeWebService活动。在许多方面,InvokeWebService活动仅仅是你的一个代理,但是它新增了使用一个单一的session cookie来控制多个调用的能力。
在使用InvokeWebService活动的时候会需要用到一些关键的属性,它们如表18-1所示。
表18-1 InvodeWebService活动的一些关键属性
属性 | 功能 |
MethodName | 获取或设置在执行活动时所要调用的方法。这个属性代表了你想调用的远程方法的方法名称。 |
ProxyClass | 获取或设置代理类的类型名称。你既可以亲自提供它也可以在你拖拽该活动到你的工作流的时候通过创建一个代理类来让WF帮你提供。 |
SessionId | 获取或设置要被使用的session。你可以使用这个机制来把不同的XML Web服务调用全部都捆在一起。我将在“带会话(Session)协同工作”一节中更进一步地讨论这个属性。 |
URL | 获取或设置要用来和XML Web服务进行通信的URL。尽管你能容易地使用属性面板来为你的InvokeWebService活动对它进行修改,但实际上该URL本身被保存在你工作流项目的设置属性包(Settings property bag)中。 |
使用 InvokeWebService活动是一件简单的事情,只需把它拖拽进你的工作流中。当你开始使用的时候,Microsoft Visual Studio会对服务器发送请求,以便它能检索WSDL并创建其代理,然后会帮你配置该活动。假如你想自己创建该代理,可以直接取消Visual Studio弹出的对话框。假如你想让Visual Studio来创建代理的话,你将要为通常的Visual Studio项目选中一个Web引用,其对话框如图18-1所示。
图18-1 Visual Studio的“添加 Web 引用”对话框用户界面
一旦你添加了Web引用,你就需要从列表中可获取的方法中指定 MethodName并绑定任何可能需要的参数。
添加Web引用
正如图18-1所指出的,有四种方式来把XML Web服务绑定到你的InvokeWebService活动上。有三个链接很显眼:此解决方案中的Web服务,本地计算机上的 Web 服务 ,浏览本地网络上的 UDDI 服务器。当然,第四个方式是直接输入URL然后按下“前往”按钮。
备注:UDDI指统一描述、发现和集成(Universal Description, Discovery, and Integration)协议,它是一个基于XML的跨平台的描述规范,可以使世界范围内的企业在互联网上发布自己所提供的服务。但是,假如你使用的是Windows Server 2003的话,你能在你的本地网络上使用UDDI。可以参考下面的在线信息资源:http://www.microsoft.com/windowsserver2003/technologies/idm/uddi/default.mspx。
假如你选择的是本地解决方案链接的话(可选列表中的第一个链接),Visual Studio会检查你当前项目中的XML Web服务项目。假如有任何发现的话,它们就会被呈现给你并且你能选中一个去进行绑定(一旦你选中了一个服务,就会激活“添加引用”按钮,然后再点击该按钮)。
从你的本地服务器上选择一个服务来让Visual Studio在本地IIS的控制执行下对本地的IIS元数据库中配置的XML Web服务进行扫描。假如找到了任何的XML Web服务的话,会为你把它们呈现在一个列表*你选择,你可以选择你想要的服务。同样,一旦你选择了一个服务就将会激活“添加引用”按钮。点击它,然后Visual Studio会为你创建你的代理。注意,假如你想引用你本地硬盘驱动器上的WSDL文件的话,你需要通过使用文件协议(http://www.cnblogs.com/gyche/admin/file://%7bfilename/})来输入URL的名称。
至于UDDI的使用,假如你选中了这个选项并且在你的本地网络上至少有一个UDDI服务器可以使用的话,你就能使用UDDI提供的服务来检索并选择一个你感兴趣的XML Web服务。只要有一个服务被发现,UDDI就会为Visual Studio提供相关的服务的URL,然后Visual Studo会再次为你创建你的代理。
在你添加Web服务引用的时候要牢记的是在你选择服务的时候你可以指定代理的名称。这是很有用的,尤其是在你引用一个以上的服务时候。你指定的名称会成为代理的命名空间的一部分。
假如你的Web服务真正被放在Internent上的话,你需要在位于图18-1中对话框的顶部的URL名称中自己去提供它的URL然后点击“前往”按钮。Visual Studio会为获取它的WSDL而对该URL进行查询,然后会创建该代理就像该Web服务就位于你的本地系统中一样。
配置代理
一旦你添加了InvokeWebService活动,在许多方面其实就和把一个Web引用添加到一个标准的Visual Studio项目中是相似的,你需要去配置该代理。有两种方式可以完成这项任务:静态配置和动态配置
静态代理配置
静态代理配置通过设置Visual Studio属性面板中的InvokeWebService活动的属性来进行。当你添加一个新的InvokeWebService活动并绑定到一个Web服务的时候(使用我们已经谈到的“添加 Web 引用”对话框),几乎所有的工作都已完成并可以准备去使用了。网址是已知的并保存到了工作流项目的设置属性包(Settings property bag)中,代理的类型已被指定。(ProxyClass属性将有一个值。)MethodName属性则例外。
Visual Studio在解释WSDL并创建代理的时候“了解”了你可以使用哪些有效的方法。指定一个要调用的方法就和拖拽一组可使用的有效方法然后从中选择一个同样简单。除非该方法有可进行绑定的参数,否则的话你的配置就已经完成了。假如有传入或者传出的参数的话,你就需要把一个当前的字段或者依赖属性绑定到这些参数的值上。在创建了MethodName并绑定了该方法的属性后,你就真正做好了使用InvokeWebService来访问XML Web服务的准备。
动态代理配置
但是,任何东西一旦被设置为静态的,我们看起来总是有重新把它配置为动态的需求。例如,或许你想动态地指定URL而不是把它放到属性的设置中,或者你可能需要在调用时分配凭证(credentials)以便能获得对一个安全的Web服务的访问。而这些事情通过在Visual Studio中静态地配置参数是不能做到的。
解决办法在于处理Invoking事件。假如你为InvokeWebService活动的Invoking事件提供了一个事件处理程序的话,你就能通过该事件的InvokeWebServiceEventArgs参数获得对代理类本身的访问能力。InvokeWebServiceEventArgs有一个名称为WebServiceProxy的属性,它是用来调用该XML Web服务的InvokeWebService活动的代理的一个实例。通过直接把这个对象设置为你正使用的代理类型,你就能动态地更改为你需要的任何值,包含URL和凭证(credentials)。
备注:对于关于怎样访问被一个代理服务器所保护的Internet的信息,可以看看http://msdn2.microsoft.com/en-us/library/system.net.configuration.proxyelement(vs.80).aspx。对于访问安全的XML Web服务方面的信息描述可以看看http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetch10.asp,尤其是“将身份验证的凭证传递给 Web 服务 (Passing Credentials for Authentication to Web services)”一节。
带会话(session)协同工作
Sessions,在Web用语中是指一组套接的请求-响应对。也就是说,假如你在一个调用中对Web资源发出请求的话,你期望接下来的调用要和前面的调用保持联系。这绝对不是超文本传输协议(HTTP)的工作原理。HTTP的开发目的是支持一个单一的请求-响应对。任何前面处理过的内容服务器都会完全把它们遗忘。
但是,作为Web的用户,我们需要记住Web的请求-响应。最显而易见的例子是基于Web的购物车。当你在Internet上订购商品并且这些商品被搜集到一个虚拟的购物车中去的时候,你期望你选中的东西将可在一段时间后进行结帐操作。我们该如何协调HTTP所支持的特性和我们作为用户所需要的特性之间的差异呢?
答案是Web应用程序来保持会话状态(session state)。当你开始使用Web应用程序的时候,你就初始化了一个session。该session会一直被跟踪,直到你停止使用该Web应用程序,或者是你注销了应用程序(假如它是安全的),或者是你直接关闭了你的浏览器。
跟踪会话状态的一个常见的方式是使用cookie。cookie是HTTP的真实载体(基于SOAP的XML,HTML或者其它任何东西)的请求-响应包之外的辅助数据容器。在服务器上,他们通常被加载到内存中并且可通过你的Web应用程序进行访问以便你跟踪session信息。在客户端,它们通常以文件的形式存储起来。当你访问指定的Web资源的时候,任何打算传送的cookies资源都从cookie文件夹中检索而来并被传回服务器。尽管这个过程不是唯一的保持session信息的方式,但这是一个常见的情况。
但是,XML Web服务有点小的差异。其中一种情况是它们不会使用浏览器进行调用,至少在使用WF的时候不会。由于这个原因,和一个XML Web服务相联系的cookie都不是基于文件的,而是仅仅存在于内存中或者在上线时和服务器来回传送。
另外一个不同的地方是它们可能并不总是存在着,因此你不能依赖于它们的存在。假如XML Web服务不是被配置为发送基于session的cookie的话,session继续是不可能的。
提示:假如你使用.NET Framework来写你的XML Web服务并且你想让session起作用的话,需要确认在WebMethod的特性中使用了EnableSession关键字来标注你的基于Web的方法,以便打开session管理。默认情况下,session管理子系统在.NET的XML Web服务的操作中是被停用了的。
在你的需要时间,在有些情况下会是大量的时间来完成工作的工作流中,至少有一个比较好的理由来支持让session管理可以使用。假如XML Web服务要长时间运行,你就需要把相关的查询组织在一起以便检索长时间运行的服务的执行结果。
长时间运行的XML Web服务
涉及到人的工作流自然会长时间运行。往往在等待某人进行响应或者进行操作的时候你还要维持你的工作流。
假如一个XML Web服务因为一些原因需要人为干预,或者,如果把它设计为需要很长的时间来完成(就像等待订单被履行)的话,你的客户端工作流也会变成长时间运行的工作流。不要自始至终维持连接,这通常最容易导致停止处理,而是要等待一段时间,然后再次查询该XML Web服务以便查看它是否完成。已经发布了基于Web通知的规范,例如WS-Eventing,但是.NET迄今为止还没有实现。)
想像你对一个XML Web服务进行了请求,然后停止处理了一段时间,最后启动备份并再次查询该服务。假如你没有使用同一个session的话,通常服务器将很有可能抛出一个异常,除非有一些其它的机制来把已使用的对服务器的查询联系起来。
接下来就看你了,通过检索由第一次XML Web服务调用所创建的session cookie,然后复制它的信息并保存起来以便以后使用。假想session cookie是能够使用的,为了创建一个副本,你要为第一个InvokeWebService活动的Invoked事件提供一个仿照如下代码的事件处理程序:
{
MyWebServiceProxy ws = e.WebServiceProxy as MyWebServiceProxy;
foreach (System.Net.Cookie cookie in ws.CookieContainer.GetCookies(new Uri(ws.Url)))
{
// The following values must be saved in order to re-create
// The cookie, There should be only one cookie, but just in
// case, you can examine them all if there is more than one,
string a = cookie.Name; // (persist values for later recall)
string b = cookie.Path;
string c = cookie.Value;
string d = cookie.Domain;
}
}
你在变量a,b,c和d中保存了字符串的值以便在以后使用。随后的调用必定要使用相同的session cookie,然后就使用以前保存在a,b,c和d中的信息来重新创建下一次也许是要请求查询状态或者结果的调用所需要的cookie。在下面的例子中,处理了接下来的InvokeWebService的Invoking事件:
{
// Create the cookie from persisted values.
System.Net.Cookie cookie = new System.Net.Cookie();
cookie.Name = a; // (values from previous Invoking handler)
cookie.Path = b;
cookie.Value = c;
cookie.Domain = d;
// Add the cookie to the cookie collection going to the
// remote server.
MyWebServiceProxy ws = e.WebServiceProxy as MyWebServiceProxy;
ws.CookieContainer.Add(cookie);
}
注意,这种方式只有在工作流实例被持久化或者以其它的方式从内存中移除后才是必要的。如果两个session相互关联的InvokeWebService活动在相同的工作流实例执行中使用的话,只要它们共享相同的SessionID值,该cookie就会被传给每一个XML Web服务的方法调用。
创建一个使用了XML Web服务的工作流
我已创建了一个包含几个项目的应用程序:一个驱动工作流的控制台应用程序,一个返回股票代码信息的Web服务以及一个基本的顺序工作流库。我把该解决方案称作QuoteRetrieve,它使用了你在第10章的范例应用程序中用过的三个相同的股票代码。我们就来开始该范例的工作并从工作流中调用一个XML Web服务。
向你的工作流中添加一个InvokeWebService活动
1.本范例同样为你提供了两个版本:完整版本和非完整版本。你需要下载本章源代码,打开“QuoteRetrieve”目录中的解决方案。
2.找到QuoteFlow项目中的Workflow1.cs文件,然后在解决方案资源管理器的右键菜单上点击“视图设计器”按钮。这将打开工作流视图设计器。
3.该工作流此时是空的,因此让我们来添加它的第一个活动。拖拽一个InvokeWebService活动到设计器界面上。
4.尽管你可能期望看到你贯穿本书看到过的界面效果:在设计器界面上呈现一个圆角矩形,其实呈现给你的是Visual Studio的“添加 Web 引用”对话框。因为在这个项目中并没有识别到之前创建的要使用的一个Web引用,因此我们要完成Web引用的添加以及初始化InvokeWebService活动的属性设置。点击“此解决方案中的 Web 服务”链接。
5.呈现出的项目中的XML Web服务只有QuoteService。点击该“QuoteService”链接。
备注:QuoteService预先填充了一个在第10章中使用过的,包含了三只股票代码并对它们的值进行了初始化的Dictionary对象。然后把该Dictionary对象放到ASP.NET的缓存(cashe)中以便在调用该XML Web服务的时候使用它。(对于初始化该缓存的代码可以看看Global.asax。)
6.Visual Studio要工作一会儿:它正为该服务加载WSDL并对该服务暴露的方法进行解析。一旦它加载完WSDL并为你呈现出该服务所暴露的方法后,“添加引用”按钮就能使用了。点击该按钮把该Web引用添加到你的项目中。
Visual Studio同时也会添加相似的圆角矩形到工作流视图设计器中,这就是你刚才拖拽到你的工作流中去的InvokeWebService活动。
7.InvokeWebService1的大多数设置已经由Visual Studio为你完成了。但是你必须通知Visual Studio你想去调用该InvokeWebService活动的哪些方法。因此在属性面板中,选中MethodName属性,这将激活下拉箭头,然后点击该下拉箭头。从列表中选中GetQuote。
8.选择该方法名将导致Visual Studio去检查该方法并把该方法所支持的参数添加到属性面板中。选择(Return Value)属性将激活熟悉的浏览(...)按钮。点击该浏览按钮将打开“将‘(ReturnValue)’绑定到活动的属性”对话框。点击“绑定到新成员”选项卡,在“新成员名称”字段中输入StockValue,确认选择的是“创建属性”选项。点击“确定”将把StockValue依赖属性添加到你的工作流中。
9.为symbol属性做同样的工作,把它的依赖属性命名为Symbol。
10.对本工作流来说,InvokeWebService活动现在就完全配置好了,因此拖拽一个Code活动到工作流视图设计器界面上并把它放到invokeWebService1的下面。指定它的ExecuteCode属性为DisplayQuoteValue。
11.Visual Studio添加DisplayQuoteValue方法后会自动为你切换到代码编辑器界面下。添加这些代码到Visual Studio刚刚增加的DisplayQuoteValue方法中:
{
// Found the stock value.
Console.WriteLine("The value for '{0}' is: {1}",
Symbol, StockValue.ToString("C"));
}
else
{
// Unknown stock.
Console.WriteLine("Stock symbol '{0}' is unknownplease try" +
" again using a valid stock symbol.", Symbol);
}
12.编译整个解决方案。纠正任何可能出现的编译错误。
13.这个主应用程序只需要所期望的股票代码这一个唯一的命令行参数。该XML Web服务只有三只股票代码的信息,它们是:CONT,LITW,和TSPT。因此,你只能向应用程序中传送这三个值中的一个。假如你执行该应用程序并传入了CONT股票代码的话,该程序的输出结果就像下面所呈现的一样。
股票的输出值将会是变化的,因为该XML Web服务执行模拟过程的时候使用了随机数,但该应用程序应该仍然会执行并从XML Web服务中检索股票的值。假如存在问题,反馈回来的方便进行调试的异常信息会被显示出来。一般来说,你所有需要去做的事就是重新启动一下Web服务。
本章源代码:里面包含本章的练习项目和完整代码
下一篇:WF从入门到精通(第十九章):把工作流发布为Web服务