第十章 工作流Web服务

工作流可以寄宿在一个Web服务中,它提供了一个理想的方式来将工作流程(非工作流客户端)解决方案发布为Web应用程序。Web服务接收到一个请求然后执行处理并返回响应,这自然地切换到你上两章所使用的Receive和Send活动,因为这些活动与WCF已经集成在一起了,所以你能轻松地创建WCF服务。

创建工作流服务

启动Visual Studio(VS)2010,选择WCF->WCF工作流服务应用程序,输入BookInventory作为项目名称,Chapter10为解决方案名称,如图10-1所示:

clip_image002

Figure 10-1. Creating a WCF Workflow Service Application

项目模板默认创建了包含Receive和SendReplay活动的Sequence的工作流,如图10-2所示:

clip_image004

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。

配置ReceiveSendReply

在工作流设计器,单击设计器底部左边的变量控件,你会发现,模板为您创建了两个变量。handle变量是用于关联同样的实例发送请求的响应,data变量是传入的数据。选择data变量然后按Delete键删除它。接下来创建两个新的变量,第一个名为变量search,变量类型在如图10-3弹出的对话框中选择BookInventory程序集中的BookSearch类。

clip_image006

Figure 10-3. Selecting the BookSearch data type

第二个名为result的变量,变量类型选择BookInfoList。完成的变量清单如图10-4所示:

clip_image008

Figure 10-4. Variables defined for the workflow

在工作流设计器上的“ReceiveRequest”活动中有一个【查看消息…】的链接,点击弹出对话框后定义传入消息(或点击“ReceiveRequest”活动属性窗口中的Content属性)。输入消息定义可以有两种方式:消息和参数。本章的后面我再讲解参数的定义方式,现在我们选择参数单选按钮。

在消息数据输入框中输入search ,它指定了进来的消息应当存储到search变量中,消息类型选择BookInventory.BookSearch,弹出的对话框如图10-5所示:

clip_image010

Figure 10-5. Specifying the incoming message

“ReceiveRequest”的属性窗口如图10-6所示:

clip_image012

Figure 10-6. The Receive activity Properties window

选择“SendResponse”活动并点击【查看消息…】的链接,同样,请确保消息单选按钮被选中,消息数据框输入result,消息类型选择BookInfoList类。

创建PerformLookup活动

对于这个项目,你要创建一个自定义的活动来执行查找,只要简单返回一引硬编码的数据。在实际的解决方案中,它可能会对数据库的检索请求数据执行查询,在解决方案资源管理器,右键单击该项目BookInventory并选择添加➤新建项,在添加新项对话框中选择WorkFlow分类下的代码活动,并输入类文件名称为PerformLookup.cs。如图10-7所示:

clip_image014

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)。

clip_image016

Figure 10-8. Toolbox with custom PerformLookup activity

拖动PerformLookup活动到“ReceiveRequest”和“SendResponse”活动之间。该工作流程看起来应该如图10-9所示:

clip_image018

Figure 10-9. Completed service workflow

选择PerformLookup活动,并在其属性窗口中的BookList属性中输入result,在Search属性中输入search。

测试服务

按F5调试服务,因为这是一个Web服务,Visual Studio会自动启动的WCF测试客户端,它加载了Web服务和发现服务提供的方法并显示在左边的中,如图10-10所示:

clip_image020

Figure 10-10. The initial WCF Test Client window

双击LookupBook()方法,在窗口右边面板中显示含有类和属性的集合,输入Anthor,ISBN,Title信息后点击【调用】,你能看到如图10-11所示:

clip_image022

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所示:

clip_image024

Figure 10-12. Creating a WCF Workflow Service

在工作流设计器,单击设计器底部左边的变量控件,在变量列表中显示和第一个服务相同的初始变量。删除data并创建一个新的名为result的变量,变量类型选择ArrayOf<T>,将出现一个选择类型的对话框。

clip_image026

在下拉框中选择【浏览类型…】会弹出“浏览并选择.Net类型”对话框,然后选择BookInventory程序集中的BookInfo类。添加三个字符串变量author,title,和isbn,定义好的变量清单如图10-13所示:

clip_image028

Figure 10-13. The variables list

在属性窗口中,将ServiceContractName属性中的IService改为Book,OperationName设置为LookupBook2.。

警告:必须确保每个服务的CanCreateInstance属性为true。

选择“ReceiveRequest”活动,然后点击【查看消息】链接,这时选择参数按钮,点击【添加新参数】输入Author参数并赋值为author,接着添加第二个参数Titel并赋值为title,最后添加第三个参数ISBN并赋值为isbn,参数定义完成后如图10-14所示:

clip_image030

Figure 10-14. The ReceiveRequest parameter list

点击“SendResponse”活动中的【查看消息】链接,在弹出对话框中点击【添加新参数】输入Result参数,在下拉框中将类型选择为BookInventory.BookInfo[](因为你将要使用result值的类型为BookInventory.BookInfo[])。如图10-15所示:

clip_image032

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所示:

clip_image034

Figure 10-16. Properties window of the PerformLookup2 activity

 

测试服务

将BookInventory2.xamlx设置为起始页,然后按F5启动调试。将显示WCF测试客户端,双击LookupBook2()方法并在请求列表中输入信息,然后点击【调用】按钮。将显示如图10-17所示:

clip_image036

Figure 10-17. WCF Test Client

响应结果格式与第一个有点不同,但功能上基础相同。在图中,我展开的第二条记录显示状态已经“CheckOut”,在第二个服务中你没有创建服务契约,你只是定义了服务的传递和返回,至于服务契约则是自动生成的。

警告:在定义Receive/SendReply对时,你可以选择任何消息和参数。但是,你不能混淆这两种选择,如果对Receive活动使用参数,则SendReply活动不能使用消息。此外,当使用参数时,参数类型不能使用含有MessageContract特性的参数。如果你违反以上规则的任何一个,程序将会在运行期出错。

 

创建客户端工作流

现在来创建一个调用Web服务的客户端工作流。在解决方案” Chapter10”上右击选择添加➤新建项目➤Workflow中的工作流控制台应用程序,输入名称BookLookup,如图10-18所示:

clip_image038

Figure 10-18. Adding a workflow console application

从解决方案资源管理器中右击BookLookup,然后选择【添加服务引用】,将弹出如图10-19所示:

clip_image040

Figure 10-19. Finding the available services

选择【解决方案中的服务】,将在服务列表中显示你在BookInventory项目中创建的两个服务:

clip_image042

Figure 10-20. Selecting the desired service

你可以展开并看到每个服务中提供的方法,选择第二个服务(BookInventory2.xamlx)并点击【确定】,你将会看到如图10-12所示提示:

clip_image044

Figure 10-21. Operation completed dialog

它提示你服务引用已添加到项目中,按F6重新生成解决方案。打开Window1.xaml文件,你将在工具箱中看到工作流服务显示在工具箱中,如图10-22所示:

clip_image046

Figure 10-22. The updated Toolbox, with the service wrapper

在工具箱中,BookLookup.ServiceReference1.Activities命名空间包含了服务中对应每个方法的自定义活动。图中所示只有一个LookupBook2活动,在解决方案中将BookLookup项目设置为起始项目。

定义工作流

拖动LookupBook2活动到工作流中,现在你需要去设置工作流的搜索标准参数和返回结果。点击左下角参数控件,新增三个命名为Title,Author,ISBN的字符串输入变量和一个命名为BookList且类型为Array Of [T]输出参数,在如下图显示的对话框中

clip_image048

选择【浏览类型…】,然后在弹出的窗口BookLookup.ServiceReference1名称空间下选择BookInfo类。定义完成的参数列表如图10-23所示:

clip_image050

Figure 10-23. The workflow arguments

选择“LookupBook2”活动,在其属性窗口中将属性值设置为如图10-24所示:

clip_image052

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你将看到如下的输出结果:

clip_image054

使用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:

clip_image056

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

posted @ 2011-05-14 18:15  南方老牛  阅读(693)  评论(0)    收藏  举报