wwf and mvc
简介
通过使用WWF,你可以创建基于处理器流的工作流并且把它们部署在任何类型的.net应用程序中。此外,本文还讨论了ASP.NET开发者面对的一些特有的问题-这些问题可能通过使用工作流得到解决,如维持状态和页面导航等。
在2005年9月,微软在它的一年两次的专业开发者会议上公开了Windows Workflow Foundation(WWF,Windows工作流基础)。作为WinFX API的支柱之一,WWF提供给开发者一个普通框架-在其上开发过程驱动的和以工作流为中心的应用程序。
当前,有些组织力图把整个商业过程自动化;他们的标准答案就是集合一队开发者来开发相应的代码。尽管这种方式对于这些组织带来良好的作用,然而也有一些固有的问题。为了深入理解这一问题,你需要理解一个工作流的基本特征。
一个工作流本质是一种方法-用来归档包含在完成一个单元的工作中的活动。典型地,在处理过程中,工作"流"流过一项或更多活动。这些活动可以通过机器或人工来实现,并且有可能象在一个互联网应用程序定义页面顺序一样得简单,也有可能象管理必须为任何数目的人都要看到、更改并同意的文件或产品一样得复杂。
因为如此多的工作流必须考虑到人工参预,所以可能需要花费很长工期才能完成,时间可能为几小时到数月或更长。例如,参预在该过程中的人可能无法找到,不在本地或忙于另外的任务;因此,工作流必须在所有非活动期间能够把自身持续性存储。而且,通过编码独立实现的过程可能对非技术人员难于理解而对开发者却难于更改。这一点和其它一些因素正是例如WindowsWF等通用工作流框架的目标-其目的就在于使创建、改变和管理工作流更容易-这是通过向它们提供一个可视化接口或通过定义一组普通API来实现的。
你可以把WWF工作流放置在任何类型的.NET应用程序中-包括Windows表单程序,控制台应用程序,Windows服务和ASP.NET Web应用程序。每种类型都需要专门的考虑。尽管一些现有示例已经足够说明如何把工作流宿主到Windows表单程序和控制台应用程序中,但是本文将集中于讨论ASP.NET开发者的问题-他们希望把工作流集成到自己的应用程序中。
作者注:本文所提供的代码是以Windows WF Beta 1和Visual Studio 2005 Beta 2 为工具创建的。你可以在www.windowsworkflow.net找到有关安装Windows WF的信息。尽管本文讨论了Windows WF的一些基础问题,但是还有其它一些这方面的可用资源。我假定读者至少了解一点Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是从一个高层次上讨论Windows WF。
一、 Windows WF和MVC模式
在开发一个ASP.NET应用程序时,你可能使用WWF的一个普通的方法是实现一种模型-视图-控制器(MVC)方法。实质上,MVC的目标是把描述层、应用程序逻辑和应用程序流逻辑分离开来。
搞清楚这个将十分有益于一个ASP.NET应用程序的开发,请考虑一个帮助桌面票工作流的场所。假定有一个商业用户通过填写一个ASP.NET Web表单并点击一个提交按钮来启动该工作流。接下来,服务器就会通知一个使用Windows表单应用程序和帮助桌面的雇员--"有新票可用了"。该帮助桌面雇员然后将在这一问题上工作,并在最后关闭该票。如果使用Windows WF来开发这个工作流情形,那么所有的处理逻辑和流程可以被包含在工作流本身,而该ASP.NET应用程序将完全不需要了解这一逻辑。
这种场所提供了一些稳固的证据-把描述与逻辑相分离是一件好事情。因为这个处理帮助桌面请求的过程是非常普通的,如果使用C#或VB.NET代码在若干不同的.NET应用程序中实现这一逻辑,那么你将会冒着重复编码的危险甚至更坏的情形--用完全不同的代码导致同样的商业处理过程的不同实现。但是如果你使用WWF来实现这一过程,那么需要这一过程的应用程序开发者将仅需在一处修改这些步骤-工作流本身-而不必担心这样会改变应用程序逻辑。代码复制和在哪里实现该过程可以通过Windows WF的使用来加以缓和。
当使用Windows WF在ASP.NET中实现MVC架构时,开发者应该尝试构建独立于应用程序的工作流-而该工作流仍然宿主于该应用程序中。这将有助于保持逻辑独立于描述并且保持在该Web应用程序中的工作步骤顺序和页面流之间的高度独立性。
一个WWF开发新手可能试图用一固定数目的活动以某种顺序去开发一个工作流,然后开发一组ASP.NET Web表单--这些表单以与之相同的顺序从一个表单流向另一个表单。很遗憾,尽管这看上去挺符合逻辑,但是实际上这是非常不具有生产效率的,因为你将会再次实现这个工作流逻辑。Web页面X不需要知道是否它需要转到页面Y或页面Z来正确地实现该工作流步骤。代之的是,该工作流(模型)应该告诉ASP.NET(控制器)下一步该干什么;然后ASP.NET应该决定要显示哪个页面。这样,每个页面几乎不需要了解整个过程;它仅需要知道怎样完成一个不同的活动并且让该工作流来关心页面是如何从一处流向另一处的。这种分离在开发者处理页面流时带来了一种极大的灵活性。例如,如果你决定改变该页面显示顺序,那么你可以从工作流中容易地实现这一点,而不需要改变该ASP.NET应用程序中的一行代码。
二、 一个简单的工作流MVC实例
为了说明这一思想,我将向你展示一个简单ASP.NET应用程序和工作流。这个过度简化的工作流描述了一个进度-收集一些来自于一外部应用程序的私人信息,然后显示它。步骤如下:
1. 调用一个方法--这意味着请求一个人的名字;该工作流使用了InvokeMethod活动(见图1)。
2. 等待直到一个事件被激发--这意味着收到一个名字;在这一步中,该工作流使用了EventSink活动。
3. 使用一类似调用,从宿主获得一个电子邮件地址。
4. 等待一个事件意味着收到一个地址。
5. 在收到名字和电子邮件以后,该工作流启动一个InvokeMethod活动来发送个人资料到调用者应用程序。在一种真实世界情形,这最后一步并不很重要。更可能的是,你将调用一个Web服务来发送数据到另外的系统,或把它放进一数据库。
图1.示例工作流:这个工作流描述了隐含在示例ASP.NET应用程序中的过程
为了在ASP.NET中实现这个工作流,你需要一个页面来收集人名,一个页面来收集电子邮件地址和一个页面来显示个人资料。记住,数据登录表单应该丝毫不知道之前或之后所发生的一切。对于显示页面也是如此。然而,该ASP.NET应用程序必须了解要把哪个页面显示给用户;这正是引入控制器的目的之所在。这个示例使用一个Http处理器来实现该解决方案。这个称为WorkflowController的定制的处理器负责下列任务:
·获得到工作流运行时刻的一个参考。
·获得一个到已有的或启动一个新的工作流实例的参考(这依赖于是否已启动一个工作流实例)。
·建立控制器和工作流之间的通讯。
·处理来自该工作流的事件。
·告诉ASP.NET需要显示哪个页面,这依赖于现在正执行该工作流中的哪一层。
你已看到,这个定制的处理器实质上负责处理所有的与WWF和页面控制相关的工作--让单个的ASP.NET页面对在后台正在进行的动作保持"缄默"。Web表单需要担心的唯一的事情是执行手头特定的任务并且把必要的数据传递到控制器。
默认地,WWF以一个异步的模型工作。这意味着,当一个应用程序宿主启动一个工作流实例时,控制立即返回到该宿主,而该工作流继续在另一个线程上执行。这在一个Windows表单应用程序中可能是很有用的-其中十分期盼用户接口的连续响应性。通过使用这个异步的模型,工作流可以在后台执行而该用户可以继续操作该应用程序。然而,在一个Web应用程序中,可能不期望这种类型的行为,因为在服务器完成一个单元的工作后控制通常将只返回到用户。这正是Windows WF的可扩展性的体现。在Windows WF中,开发者可以利用或创建"运行时刻服务"来监控甚至修改该工作流运行时刻。该示例包括:
·持续性服务-存储执行和空闲时间之间的工作流状态
·追踪服务-输出有关工作流执行的信息到某种媒体
·事务服务-帮助维持工作流执行过程中的数据完整性
另外,线程服务让开发者控制工作流实例的执行方式。如前面所讨论的,工作流运行时刻默认地将在一个独立于宿主的线程上异步地运行实例。但是由于这很可能不是ASP.NET所期望的,所以你需要交换默认工作流线程服务。幸运的是,微软已经为此提供了一种解决方案--ASPNetThreadingService。为了实现这一变化,你或者可以手工编码方式把ASPNetThreadingService添加到工作流运行时刻服务,或在web.config文件中完成这一任务。本文中的示例应用程序使用了配置方式。在web.config(见列表1)的工作流运行时刻/服务节中,添加类似下列的这一行:
<add type=
"System.Workflow.Runtime.Hosting.ASPNetThreadingService,
System.Workflow.Runtime, Version=3.0.00000.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
三、 实现控制器逻辑
接下来,你需要以一个Http处理器来实现控制器逻辑。为了构建该控制器,所有你需要做的是创建一个称为WorkflowController处理器的类-它实现IHttp处理器接口。到目前为止,你还没有看到有关Windows WF的任何特别的东西-这是特别针对于ASP.net的功能(请继续往下读)。
在这个WorkflowController处理器类中,名为ProcessRequest的IHttp处理器接口方法处理一个来自于该ASP.NET应用程序的Web请求。这里,你需要获得到一个静态的工作流运行时刻实例的一个参考,为该工作流建立事件处理器,并且启动工作流的执行。在启动一个工作流实例之前,你需要检查是否该请求的查询串值包含一个代表一个工作流实例ID的GUID。如果存在这个ID,你就知道已经有一个实例正在运行,这样你可以得到一个到该实例的参考并继续执行。如果不存在这个ID,你需要通过调用工作流运行时刻Start Workflow方法来创建一个新的实例并且开始执行过程。
在启动一个实例后,事件处理器将管理进出工作流的通讯。因为本文的目的不是讨论本地通讯服务,所以在此我不会详细讨论这个主题,而是分析其高级的实现技术,并再次讨论这在一个ASP.NET应用程序中是如果工作的。为了便利通讯处理,你将需要若干.NET接口--用于描述出/入该工作流和宿主的信息。你会在本文所附WorkflowClassLibrary工程源码中找到这一些。你还会找到一些实现这些接口的类以及实现工作流机制所必须的相应功能。
让我们再简单地看一下web.config文件。注意,在早些时候讨论的ASPNetThreadingService元素附近,我们使用了三个元素来描述通讯服务类:
<add type="Workflow.RuntimeServices.GetNameService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.GetEmailService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.SendDataService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
这里还有一个元素,它指导工作流运行时刻库来使用SqlStatePersistenceService。这种另外的服务把一个工作流的状态持续性存储到一个在页面请求之间的SQL服务器数据库之中。你必须提前手工地创建这个数据库,但是微软提供了SQL脚本来做这件事情。你将会在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\Windows Workflow Foundation\SQL文件夹下找到它们。就象模型服务一样,你可以编程地添加这些服务,但是你也可以在配置中实现它,这将会大大降低代码的编写量并且提供灵活性-甚至在代码生产之后。而且在web.config中有一行,它添加一个HttpModule-它支持在ASP.NET中的Windows WF运行时刻库;还有一行用于设置更早些时候讨论的Http处理器控制器。如你所见,在这个配置中存在许多的东西。
作为结论,Windows WF为开发者在其上开发基于工作流的应用程序提供了一个极其易用和可扩展的框架。实现商业过程已经并将继续成为一种重要的应用程序技术。除非你是一个技术服务公司或ISV,否则软件是一定要提供商业及其运行过程的。通过使用如Windows WF这样的工具,开发者可以使得开发过程容易且灵活。
通过使用WWF,你可以创建基于处理器流的工作流并且把它们部署在任何类型的.net应用程序中。此外,本文还讨论了ASP.NET开发者面对的一些特有的问题-这些问题可能通过使用工作流得到解决,如维持状态和页面导航等。
在2005年9月,微软在它的一年两次的专业开发者会议上公开了Windows Workflow Foundation(WWF,Windows工作流基础)。作为WinFX API的支柱之一,WWF提供给开发者一个普通框架-在其上开发过程驱动的和以工作流为中心的应用程序。
当前,有些组织力图把整个商业过程自动化;他们的标准答案就是集合一队开发者来开发相应的代码。尽管这种方式对于这些组织带来良好的作用,然而也有一些固有的问题。为了深入理解这一问题,你需要理解一个工作流的基本特征。
一个工作流本质是一种方法-用来归档包含在完成一个单元的工作中的活动。典型地,在处理过程中,工作"流"流过一项或更多活动。这些活动可以通过机器或人工来实现,并且有可能象在一个互联网应用程序定义页面顺序一样得简单,也有可能象管理必须为任何数目的人都要看到、更改并同意的文件或产品一样得复杂。
因为如此多的工作流必须考虑到人工参预,所以可能需要花费很长工期才能完成,时间可能为几小时到数月或更长。例如,参预在该过程中的人可能无法找到,不在本地或忙于另外的任务;因此,工作流必须在所有非活动期间能够把自身持续性存储。而且,通过编码独立实现的过程可能对非技术人员难于理解而对开发者却难于更改。这一点和其它一些因素正是例如WindowsWF等通用工作流框架的目标-其目的就在于使创建、改变和管理工作流更容易-这是通过向它们提供一个可视化接口或通过定义一组普通API来实现的。
你可以把WWF工作流放置在任何类型的.NET应用程序中-包括Windows表单程序,控制台应用程序,Windows服务和ASP.NET Web应用程序。每种类型都需要专门的考虑。尽管一些现有示例已经足够说明如何把工作流宿主到Windows表单程序和控制台应用程序中,但是本文将集中于讨论ASP.NET开发者的问题-他们希望把工作流集成到自己的应用程序中。
作者注:本文所提供的代码是以Windows WF Beta 1和Visual Studio 2005 Beta 2 为工具创建的。你可以在www.windowsworkflow.net找到有关安装Windows WF的信息。尽管本文讨论了Windows WF的一些基础问题,但是还有其它一些这方面的可用资源。我假定读者至少了解一点Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是从一个高层次上讨论Windows WF。
一、 Windows WF和MVC模式
在开发一个ASP.NET应用程序时,你可能使用WWF的一个普通的方法是实现一种模型-视图-控制器(MVC)方法。实质上,MVC的目标是把描述层、应用程序逻辑和应用程序流逻辑分离开来。
搞清楚这个将十分有益于一个ASP.NET应用程序的开发,请考虑一个帮助桌面票工作流的场所。假定有一个商业用户通过填写一个ASP.NET Web表单并点击一个提交按钮来启动该工作流。接下来,服务器就会通知一个使用Windows表单应用程序和帮助桌面的雇员--"有新票可用了"。该帮助桌面雇员然后将在这一问题上工作,并在最后关闭该票。如果使用Windows WF来开发这个工作流情形,那么所有的处理逻辑和流程可以被包含在工作流本身,而该ASP.NET应用程序将完全不需要了解这一逻辑。
这种场所提供了一些稳固的证据-把描述与逻辑相分离是一件好事情。因为这个处理帮助桌面请求的过程是非常普通的,如果使用C#或VB.NET代码在若干不同的.NET应用程序中实现这一逻辑,那么你将会冒着重复编码的危险甚至更坏的情形--用完全不同的代码导致同样的商业处理过程的不同实现。但是如果你使用WWF来实现这一过程,那么需要这一过程的应用程序开发者将仅需在一处修改这些步骤-工作流本身-而不必担心这样会改变应用程序逻辑。代码复制和在哪里实现该过程可以通过Windows WF的使用来加以缓和。
当使用Windows WF在ASP.NET中实现MVC架构时,开发者应该尝试构建独立于应用程序的工作流-而该工作流仍然宿主于该应用程序中。这将有助于保持逻辑独立于描述并且保持在该Web应用程序中的工作步骤顺序和页面流之间的高度独立性。
一个WWF开发新手可能试图用一固定数目的活动以某种顺序去开发一个工作流,然后开发一组ASP.NET Web表单--这些表单以与之相同的顺序从一个表单流向另一个表单。很遗憾,尽管这看上去挺符合逻辑,但是实际上这是非常不具有生产效率的,因为你将会再次实现这个工作流逻辑。Web页面X不需要知道是否它需要转到页面Y或页面Z来正确地实现该工作流步骤。代之的是,该工作流(模型)应该告诉ASP.NET(控制器)下一步该干什么;然后ASP.NET应该决定要显示哪个页面。这样,每个页面几乎不需要了解整个过程;它仅需要知道怎样完成一个不同的活动并且让该工作流来关心页面是如何从一处流向另一处的。这种分离在开发者处理页面流时带来了一种极大的灵活性。例如,如果你决定改变该页面显示顺序,那么你可以从工作流中容易地实现这一点,而不需要改变该ASP.NET应用程序中的一行代码。
二、 一个简单的工作流MVC实例
为了说明这一思想,我将向你展示一个简单ASP.NET应用程序和工作流。这个过度简化的工作流描述了一个进度-收集一些来自于一外部应用程序的私人信息,然后显示它。步骤如下:
1. 调用一个方法--这意味着请求一个人的名字;该工作流使用了InvokeMethod活动(见图1)。
2. 等待直到一个事件被激发--这意味着收到一个名字;在这一步中,该工作流使用了EventSink活动。
3. 使用一类似调用,从宿主获得一个电子邮件地址。
4. 等待一个事件意味着收到一个地址。
5. 在收到名字和电子邮件以后,该工作流启动一个InvokeMethod活动来发送个人资料到调用者应用程序。在一种真实世界情形,这最后一步并不很重要。更可能的是,你将调用一个Web服务来发送数据到另外的系统,或把它放进一数据库。
图1.示例工作流:这个工作流描述了隐含在示例ASP.NET应用程序中的过程
为了在ASP.NET中实现这个工作流,你需要一个页面来收集人名,一个页面来收集电子邮件地址和一个页面来显示个人资料。记住,数据登录表单应该丝毫不知道之前或之后所发生的一切。对于显示页面也是如此。然而,该ASP.NET应用程序必须了解要把哪个页面显示给用户;这正是引入控制器的目的之所在。这个示例使用一个Http处理器来实现该解决方案。这个称为WorkflowController的定制的处理器负责下列任务:
·获得到工作流运行时刻的一个参考。
·获得一个到已有的或启动一个新的工作流实例的参考(这依赖于是否已启动一个工作流实例)。
·建立控制器和工作流之间的通讯。
·处理来自该工作流的事件。
·告诉ASP.NET需要显示哪个页面,这依赖于现在正执行该工作流中的哪一层。
你已看到,这个定制的处理器实质上负责处理所有的与WWF和页面控制相关的工作--让单个的ASP.NET页面对在后台正在进行的动作保持"缄默"。Web表单需要担心的唯一的事情是执行手头特定的任务并且把必要的数据传递到控制器。
默认地,WWF以一个异步的模型工作。这意味着,当一个应用程序宿主启动一个工作流实例时,控制立即返回到该宿主,而该工作流继续在另一个线程上执行。这在一个Windows表单应用程序中可能是很有用的-其中十分期盼用户接口的连续响应性。通过使用这个异步的模型,工作流可以在后台执行而该用户可以继续操作该应用程序。然而,在一个Web应用程序中,可能不期望这种类型的行为,因为在服务器完成一个单元的工作后控制通常将只返回到用户。这正是Windows WF的可扩展性的体现。在Windows WF中,开发者可以利用或创建"运行时刻服务"来监控甚至修改该工作流运行时刻。该示例包括:
·持续性服务-存储执行和空闲时间之间的工作流状态
·追踪服务-输出有关工作流执行的信息到某种媒体
·事务服务-帮助维持工作流执行过程中的数据完整性
另外,线程服务让开发者控制工作流实例的执行方式。如前面所讨论的,工作流运行时刻默认地将在一个独立于宿主的线程上异步地运行实例。但是由于这很可能不是ASP.NET所期望的,所以你需要交换默认工作流线程服务。幸运的是,微软已经为此提供了一种解决方案--ASPNetThreadingService。为了实现这一变化,你或者可以手工编码方式把ASPNetThreadingService添加到工作流运行时刻服务,或在web.config文件中完成这一任务。本文中的示例应用程序使用了配置方式。在web.config(见列表1)的工作流运行时刻/服务节中,添加类似下列的这一行:
<add type=
"System.Workflow.Runtime.Hosting.ASPNetThreadingService,
System.Workflow.Runtime, Version=3.0.00000.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
三、 实现控制器逻辑
接下来,你需要以一个Http处理器来实现控制器逻辑。为了构建该控制器,所有你需要做的是创建一个称为WorkflowController处理器的类-它实现IHttp处理器接口。到目前为止,你还没有看到有关Windows WF的任何特别的东西-这是特别针对于ASP.net的功能(请继续往下读)。
在这个WorkflowController处理器类中,名为ProcessRequest的IHttp处理器接口方法处理一个来自于该ASP.NET应用程序的Web请求。这里,你需要获得到一个静态的工作流运行时刻实例的一个参考,为该工作流建立事件处理器,并且启动工作流的执行。在启动一个工作流实例之前,你需要检查是否该请求的查询串值包含一个代表一个工作流实例ID的GUID。如果存在这个ID,你就知道已经有一个实例正在运行,这样你可以得到一个到该实例的参考并继续执行。如果不存在这个ID,你需要通过调用工作流运行时刻Start Workflow方法来创建一个新的实例并且开始执行过程。
在启动一个实例后,事件处理器将管理进出工作流的通讯。因为本文的目的不是讨论本地通讯服务,所以在此我不会详细讨论这个主题,而是分析其高级的实现技术,并再次讨论这在一个ASP.NET应用程序中是如果工作的。为了便利通讯处理,你将需要若干.NET接口--用于描述出/入该工作流和宿主的信息。你会在本文所附WorkflowClassLibrary工程源码中找到这一些。你还会找到一些实现这些接口的类以及实现工作流机制所必须的相应功能。
让我们再简单地看一下web.config文件。注意,在早些时候讨论的ASPNetThreadingService元素附近,我们使用了三个元素来描述通讯服务类:
<add type="Workflow.RuntimeServices.GetNameService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.GetEmailService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.SendDataService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
这里还有一个元素,它指导工作流运行时刻库来使用SqlStatePersistenceService。这种另外的服务把一个工作流的状态持续性存储到一个在页面请求之间的SQL服务器数据库之中。你必须提前手工地创建这个数据库,但是微软提供了SQL脚本来做这件事情。你将会在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\Windows Workflow Foundation\SQL文件夹下找到它们。就象模型服务一样,你可以编程地添加这些服务,但是你也可以在配置中实现它,这将会大大降低代码的编写量并且提供灵活性-甚至在代码生产之后。而且在web.config中有一行,它添加一个HttpModule-它支持在ASP.NET中的Windows WF运行时刻库;还有一行用于设置更早些时候讨论的Http处理器控制器。如你所见,在这个配置中存在许多的东西。
作为结论,Windows WF为开发者在其上开发基于工作流的应用程序提供了一个极其易用和可扩展的框架。实现商业过程已经并将继续成为一种重要的应用程序技术。除非你是一个技术服务公司或ISV,否则软件是一定要提供商业及其运行过程的。通过使用如Windows WF这样的工具,开发者可以使得开发过程容易且灵活。