第十章 工作流Web服务
工作流可以寄宿在一个Web服务中,它提供了一个理想的方式来将工作流程(非工作流客户端)解决方案发布为Web应用程序。Web服务接收到一个请求然后执行处理并返回响应,这自然地切换到你上两章所使用的Receive和Send活动,因为这些活动与WCF已经集成在一起了,所以你能轻松地创建WCF服务。
创建工作流服务
启动Visual Studio(VS)2010,选择WCF->WCF工作流服务应用程序,输入BookInventory作为项目名称,Chapter10为解决方案名称,如图10-1所示:
Figure 10-1. Creating a WCF Workflow Service Application
项目模板默认创建了包含Receive和SendReplay活动的Sequence的工作流,如图10-2所示:
Figure 10-2. The initial workflow sequence
你首先配置满足这些活动的服务契约,然后在Receive和SendReplay活动之间添加工作流处理。项目模板默认生成的初始工作流文件为Service1.xamlx,在项目中将这个文件重新命名为BookInventory.xaml,这个服务用来查找指定的书并返回图书馆所拥有每个副本的状态。
定义服务契约
在Solution Explorer中,右击BookInventory项目,选择添加➤类,并输入类名为BookInfo.cs,这个文件的具体代码实现如清单10-1所示:
清单 10-1. Service contract definition: BookInfo.cs
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace BookInventory
{
/*****************************************************/
// Define the service contract, IBookInventory
// which consists of a single method, LookupBook()
/*****************************************************/
[ServiceContract]
public interface IBookInventory
{
[OperationContract]
BookInfoList LookupBook(BookSearch request);
}
/*****************************************************/
// Define the request message, BookSearch
/*****************************************************/
[MessageContract(IsWrapped = false)]
public class BookSearch
{
private String _ISBN;
private String _Title;
private String _Author;
public BookSearch()
{
}
public BookSearch(String title, String author, String isbn)
{
_Title = title;
_Author = author;
_ISBN = isbn;
}
#region Public Properties
[MessageBodyMember]
public String Title
{
get { return _Title; }
set { _Title = value; }
}
[MessageBodyMember]
public String Author
{
get { return _Author; }
set { _Author = value; }
}
[MessageBodyMember]
public String ISBN
{
get { return _ISBN; }
set { _ISBN = value; }
}
#endregion Public Properties
}
/*****************************************************/
// Define the BookInfo class
/*****************************************************/
[MessageContract(IsWrapped = false)]
public class BookInfo
{
private Guid _InventoryID;
private String _ISBN;
private String _Title;
private String _Author;
private String _Status;
public BookInfo()
{
}
public BookInfo(String title, String author, String isbn,
String status)
{
_Title = title;
_Author = author;
_ISBN = isbn;
_Status = status;
_InventoryID = Guid.NewGuid();
}
#region Public Properties
[MessageBodyMember]
public Guid InventoryID
{
get { return _InventoryID; }
set { _InventoryID = value; }
}
[MessageBodyMember]
public String Title
{
get { return _Title; }
set { _Title = value; }
}
[MessageBodyMember]
public String Author
{
get { return _Author; }
set { _Author = value; }
}
[MessageBodyMember]
public String ISBN
{
get { return _ISBN; }
set { _ISBN = value; }
}
[MessageBodyMember]
public String status
{
get { return _Status; }
set { _Status = value; }
}
#endregion Public Properties
}
/*****************************************************/
// Define the response message, BookInfoList, which
// is a list of BookInfo classes
/*****************************************************/
[MessageContract(IsWrapped = false)]
public class BookInfoList
{
private List<BookInfo> _BookList;
public BookInfoList()
{
_BookList = new List<BookInfo>();
}
[MessageBodyMember]
public List<BookInfo> BookList
{
get { return _BookList; }
}
}
}
服务契约IBookInventory包含一个叫作LookupBook()的方法,该方法传入含查找所需书各种属性(如Author和Title)的BookSearch类,并返回包含BookInfo类集合的BookInfoList。你可以参考第八章,在那章中我已经讲解了ServiceContract, MessageContract及MessageBodyMember特性。
按F6重新生成项目
打开BookInventory.xamlx文件并选择”ReceiveRequest”活动,在属性窗口中ServiceContractName属性默认值为{http://tempuri.org/}IService,将IService改为IBookInventory。接着将OperationName属性值改为LookupBook。
配置Receive和SendReply
在工作流设计器,单击设计器底部左边的变量控件,你会发现,模板为您创建了两个变量。handle变量是用于关联同样的实例发送请求的响应,data变量是传入的数据。选择data变量然后按Delete键删除它。接下来创建两个新的变量,第一个名为变量search,变量类型在如图10-3弹出的对话框中选择BookInventory程序集中的BookSearch类。
Figure 10-3. Selecting the BookSearch data type
第二个名为result的变量,变量类型选择BookInfoList。完成的变量清单如图10-4所示:
Figure 10-4. Variables defined for the workflow
在工作流设计器上的“ReceiveRequest”活动中有一个【查看消息…】的链接,点击弹出对话框后定义传入消息(或点击“ReceiveRequest”活动属性窗口中的Content属性)。输入消息定义可以有两种方式:消息和参数。本章的后面我再讲解参数的定义方式,现在我们选择参数单选按钮。
在消息数据输入框中输入search ,它指定了进来的消息应当存储到search变量中,消息类型选择BookInventory.BookSearch,弹出的对话框如图10-5所示:
Figure 10-5. Specifying the incoming message
“ReceiveRequest”的属性窗口如图10-6所示:
Figure 10-6. The Receive activity Properties window
选择“SendResponse”活动并点击【查看消息…】的链接,同样,请确保消息单选按钮被选中,消息数据框输入result,消息类型选择BookInfoList类。
创建PerformLookup活动
对于这个项目,你要创建一个自定义的活动来执行查找,只要简单返回一引硬编码的数据。在实际的解决方案中,它可能会对数据库的检索请求数据执行查询,在解决方案资源管理器,右键单击该项目BookInventory并选择添加➤新建项,在添加新项对话框中选择WorkFlow分类下的代码活动,并输入类文件名称为PerformLookup.cs。如图10-7所示:
Figure 10-7. Creating a custom activity
PerformLookup活动的实现如清单10-2所示:
Listing 10-2. Implementation of the PerformLookup activity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
namespace BookInventory
{
/*****************************************************/
// This custom activity creates a BookInfoList class
// which is a collection of BookInfo classes. It uses
// the input parameters (BookSearch class) to "lookup"
// the matching items. The BookInfoList class is
// returned in the output parameter.
/*****************************************************/
public sealed class PerformLookup : CodeActivity
{
public InArgument<BookSearch> Search { get; set; }
public OutArgument<BookInfoList> BookList { get; set; }
protected override void Execute(CodeActivityContext context)
{
string author = Search.Get(context).Author;
string title = Search.Get(context).Title;
string isbn = Search.Get(context).ISBN;
BookInfoList l = new BookInfoList();
l.BookList.Add(new BookInfo(title, author, isbn, "Available"));
l.BookList.Add(new BookInfo(title, author, isbn, "CheckedOut"));
l.BookList.Add(new BookInfo(title, author, isbn, "Missing"));
l.BookList.Add(new BookInfo(title, author, isbn, "Available"));
BookList.Set(context, l);
}
}
}
按F6重新生成应用程序,打开BookInventory.xamlx文件,请注意,自定义活动PerformLookup已经显示在你的工具箱中(如图10-8)。
Figure 10-8. Toolbox with custom PerformLookup activity
拖动PerformLookup活动到“ReceiveRequest”和“SendResponse”活动之间。该工作流程看起来应该如图10-9所示:
Figure 10-9. Completed service workflow
选择PerformLookup活动,并在其属性窗口中的BookList属性中输入result,在Search属性中输入search。
测试服务
按F5调试服务,因为这是一个Web服务,Visual Studio会自动启动的WCF测试客户端,它加载了Web服务和发现服务提供的方法并显示在左边的中,如图10-10所示:
Figure 10-10. The initial WCF Test Client window
双击LookupBook()方法,在窗口右边面板中显示含有类和属性的集合,输入Anthor,ISBN,Title信息后点击【调用】,你能看到如图10-11所示:
Figure10-11. WCF Test Client showing service results
服务返回的四个BookInfo类。
警告:在VS2010中如果.xamlx是当前文件,按F5显示WCF测试客户端。不过,如果当前文件为其他文件,当你按F5,你可能会看到网页上显示,显示了当前文件夹的目录清单。如果出现这种情况,只需关闭网页停止调试器,选择.xamlx文件,然后按再次F5。
使用参数
在以前的项目中,使用MessageContract特性为Web服务指定输入,而不是创建一个包含所有输入数据的消息。现在可以作为单独的参数传递给工作流服务,为了验证这一点,你将创建第二个相同的使用参数代替消息服务
创建第二个服务
在解决方案资源管理器,右键单击该项目BookInventory并选择添加➤新建项,在添加新项对话框中选择WorkFlow分类下的WCF工作流服务,并输入文件名称为BookInventory2.xamlx。如图10-12所示:
Figure 10-12. Creating a WCF Workflow Service
在工作流设计器,单击设计器底部左边的变量控件,在变量列表中显示和第一个服务相同的初始变量。删除data并创建一个新的名为result的变量,变量类型选择ArrayOf<T>,将出现一个选择类型的对话框。
在下拉框中选择【浏览类型…】会弹出“浏览并选择.Net类型”对话框,然后选择BookInventory程序集中的BookInfo类。添加三个字符串变量author,title,和isbn,定义好的变量清单如图10-13所示:
Figure 10-13. The variables list
在属性窗口中,将ServiceContractName属性中的IService改为Book,OperationName设置为LookupBook2.。
警告:必须确保每个服务的CanCreateInstance属性为true。
选择“ReceiveRequest”活动,然后点击【查看消息】链接,这时选择参数按钮,点击【添加新参数】输入Author参数并赋值为author,接着添加第二个参数Titel并赋值为title,最后添加第三个参数ISBN并赋值为isbn,参数定义完成后如图10-14所示:
Figure 10-14. The ReceiveRequest parameter list
点击“SendResponse”活动中的【查看消息】链接,在弹出对话框中点击【添加新参数】输入Result参数,在下拉框中将类型选择为BookInventory.BookInfo[](因为你将要使用result值的类型为BookInventory.BookInfo[])。如图10-15所示:
Figure 10-15. The SendResponse parameter list
创建一个可修改的PerformLookup活动
自定义PerformLookup活动的创建是因为第一个服务类需要BookSearch类作为输入参数并返回BookInfoList类。现在您将要使用单独的参数创建一个不同的自定义活动,在解决方案资源管理器,右键单击该项目BookInventory并选择添加➤新建项,在添加新项对话框中选择WorkFlow分类下的代码活动,并输入文件名称为PerformLookup2.cs。如清单10-3所示:
Listing 10-3. Implementation of PerformLookup2
using System;
using System.Collections.Generic;
using System.Activities;
namespace BookInventory
{
/*****************************************************/
// This custom activity creates a BookInfo array and
// uses the input parameters to "lookup" the matching
// items. The BookInfo array is returned in the output
// parameter.
/*****************************************************/
public sealed class PerformLookup2 : CodeActivity
{
public InArgument<String> Title { get; set; }
public InArgument<String> Author { get; set; }
public InArgument<String> ISBN { get; set; }
public OutArgument<BookInfo[]> BookList { get; set; }
protected override void Execute(CodeActivityContext context)
{
string author = Author.Get(context);
string title = Title.Get(context);
string isbn = ISBN.Get(context);
BookInfo[] l = new BookInfo[4];
l[0] = new BookInfo(title, author, isbn, "Available");
l[1] = new BookInfo(title, author, isbn, "CheckedOut");
l[2] = new BookInfo(title, author, isbn, "Missing");
l[3] = new BookInfo(title, author, isbn, "Available");
BookList.Set(context, l);
}
}
}
上面的代码就第一个PerformLookup类,除输入参数单独传入外,返回结果是一个数组而不是类。按F6重新生成应用程序。选择BookInventory2.xamlx并拖动PerformLookup2活动到“ReceiveRequest”和“SendResponse”活动之间。在属性窗口中,输入适当的值。如图10-16所示:
Figure 10-16. Properties window of the PerformLookup2 activity
测试服务
将BookInventory2.xamlx设置为起始页,然后按F5启动调试。将显示WCF测试客户端,双击LookupBook2()方法并在请求列表中输入信息,然后点击【调用】按钮。将显示如图10-17所示:
Figure 10-17. WCF Test Client
响应结果格式与第一个有点不同,但功能上基础相同。在图中,我展开的第二条记录显示状态已经“CheckOut”,在第二个服务中你没有创建服务契约,你只是定义了服务的传递和返回,至于服务契约则是自动生成的。
警告:在定义Receive/SendReply对时,你可以选择任何消息和参数。但是,你不能混淆这两种选择,如果对Receive活动使用参数,则SendReply活动不能使用消息。此外,当使用参数时,参数类型不能使用含有MessageContract特性的参数。如果你违反以上规则的任何一个,程序将会在运行期出错。
创建客户端工作流
现在来创建一个调用Web服务的客户端工作流。在解决方案” Chapter10”上右击选择添加➤新建项目➤Workflow中的工作流控制台应用程序,输入名称BookLookup,如图10-18所示:
Figure 10-18. Adding a workflow console application
从解决方案资源管理器中右击BookLookup,然后选择【添加服务引用】,将弹出如图10-19所示:
Figure 10-19. Finding the available services
选择【解决方案中的服务】,将在服务列表中显示你在BookInventory项目中创建的两个服务:
Figure 10-20. Selecting the desired service
你可以展开并看到每个服务中提供的方法,选择第二个服务(BookInventory2.xamlx)并点击【确定】,你将会看到如图10-12所示提示:
Figure 10-21. Operation completed dialog
它提示你服务引用已添加到项目中,按F6重新生成解决方案。打开Window1.xaml文件,你将在工具箱中看到工作流服务显示在工具箱中,如图10-22所示:
Figure 10-22. The updated Toolbox, with the service wrapper
在工具箱中,BookLookup.ServiceReference1.Activities命名空间包含了服务中对应每个方法的自定义活动。图中所示只有一个LookupBook2活动,在解决方案中将BookLookup项目设置为起始项目。
定义工作流
拖动LookupBook2活动到工作流中,现在你需要去设置工作流的搜索标准参数和返回结果。点击左下角参数控件,新增三个命名为Title,Author,ISBN的字符串输入变量和一个命名为BookList且类型为Array Of [T]输出参数,在如下图显示的对话框中
选择【浏览类型…】,然后在弹出的窗口BookLookup.ServiceReference1名称空间下选择BookInfo类。定义完成的参数列表如图10-23所示:
Figure 10-23. The workflow arguments
选择“LookupBook2”活动,在其属性窗口中将属性值设置为如图10-24所示:
Figure 10-24. LookupBook2 properties
实现主应用程序
打开LookupBook项目中的Program.cs文件,并实现如清单10-4所示:
Listing 10-4. Implementation of Program.cs
using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using BookLookup.ServiceReference1;
namespace BookLookup
{
class Program
{
static void Main(string[] args)
{
// create dictionary with input arguments for the workflow
IDictionary<string, object> input = new Dictionary<string, object>
{
{ "Author" , "Margaret Mitchell" },
{ "Title" , "Gone with the Wind" },
{ "ISBN" , "1234567890123" }
};
// execute the workflow
IDictionary<string, object> output =
WorkflowInvoker.Invoke(new Workflow1(), input);
BookInfo[] l = output["BookList"] as BookInfo[];
if (l != null)
{
foreach (BookInfo i in l)
{
Console.WriteLine("{0}: {1}, {2}",
i.Title, i.status, i.InventoryID);
}
}
else
Console.WriteLine("No items were found");
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
}
}
此代码通过传入一个包含Author, Title和ISBN参数的Dictionary对象,来返回一个BookInfo的对象数组集合,并显示每个对象的详细内容。
运行应用程序
按F5你将看到如下的输出结果:
使用Pick
WF4.0提供了名为Pick的活动,它还有支持多个分支的PickBranch活动。每个分支包含”触发器”和”操作”属性,每个属性执行一个活动(或sequence活动)。当执行Pick活动时,所有的Trigger活动启动。只要有一个活动完成,则执行相应的Action,并且取消其它分支的执行。
对来某些事件来说确定适当的Action是非常有用的。例如,你能对”触发器”使用Receive活动。每个分支有一个等待不同的消息的Receive活动,其接收不同的消息后采取相应的动作。在此项目中,可以使用Pick给工作流提供超时特性。
提示:如果你对以前版本的工作流熟悉的话,Pick相当于Listen活动。
打开Window1.xaml文件,剪切LookupBook2并拖动Pick到工作流上,然后粘贴LookupBook2到”Branch1”的触发器中。拖动Delay活动到”Branch2”的触发器中,并设置其属性Duration为System.TimeSpan.FromSeconds(5)。拖动WriteLine活动到每个”操作”段中,分别设置属性为“The service completed”和“The service timed-out”。完成后的流程设计如图10-25:
Figure 10-25. Workflow with timeout logic
按F5运行程序显示的结果和上次的一样,现在来测试一下超时,打开BookInventory2.xamlx文件,在”SendResponse”活动之前拖入Delay活动并将Duration属性设置为System.TimeSpan.FromSeconds(7)。这次5秒延迟后的显示结果发下所示:
The service timed-out
No items were found
Press ENTER to exit




























浙公网安备 33010602011771号