﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-玄铁剑-文章分类-WF</title><link>http://www.cnitblog.com/MartinYao/category/5462.html</link><description>成功的途径：抄，创造，研究，发明...</description><language>zh-cn</language><lastBuildDate>Mon, 26 Sep 2011 13:53:00 GMT</lastBuildDate><pubDate>Mon, 26 Sep 2011 13:53:00 GMT</pubDate><ttl>60</ttl><item><title>Windows Workflow Foundation 构建状态机</title><link>http://www.cnitblog.com/MartinYao/articles/42594.html</link><dc:creator>玄铁剑</dc:creator><author>玄铁剑</author><pubDate>Mon, 21 Apr 2008 13:25:00 GMT</pubDate><guid>http://www.cnitblog.com/MartinYao/articles/42594.html</guid><wfw:comment>http://www.cnitblog.com/MartinYao/comments/42594.html</wfw:comment><comments>http://www.cnitblog.com/MartinYao/articles/42594.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/MartinYao/comments/commentRss/42594.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/MartinYao/services/trackbacks/42594.html</trackback:ping><description><![CDATA[<div class=FeatureSmallHead>WF 操作指导</div>
<div class=FeatureHeadline>使用 Windows Workflow Foundation 构建状态机</div>
<div class=FeatureByLine>Keith Pijanowski</div>
<div><br>
<table>
    <tbody>
        <tr vAlign=top>
            <td><span class=CodeDownloadText><strong><font color=#003399 face=Arial>本文讨论: </font></strong></span>
            <ul>
                <li class=ListingBullet>顺序工作流和状态机工作流
                <li class=ListingBullet>何时使用状态机工作流
                <li class=ListingBullet>构建和设计状态机工作流
                <li class=ListingBullet>通过触发事件来运行工作流
                <li class=ListingBullet>通过传递数据来运行工作流 </li>
            </ul>
            </td>
            <td><strong><font color=#003399><font face=Arial><span class=CodeDownloadText>本文使用了以下技术: </span><br></font></font></strong>Windows Workflow Foundation、Visual Studio<br></td>
        </tr>
    </tbody>
</table>
<div class=ContentSeparator>
<div id=ctl00_ContentPlaceHolder1_cpe207052 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207052_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207052_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;目录 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207052_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S1"><u><font color=#0033cc>状态机理论</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S2"><u><font color=#0033cc>状态机和 Windows WF</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S3"><u><font color=#0033cc>与状态机工作流进行通信</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S4"><u><font color=#0033cc>数据交换和状态转换所需的事件</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S5"><u><font color=#0033cc>定义外部数据交换接口</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S6"><u><font color=#0033cc>添加 EventDriven 活动</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S7"><u><font color=#0033cc>引用外部数据交换接口</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S8"><u><font color=#0033cc>实现外部数据交换服务</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S9"><u><font color=#0033cc>向工作流运行时中添加外部数据交换服务</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S10"><u><font color=#0033cc>使用外部数据交换</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S11"><u><font color=#0033cc>使用状态机 API</font></u></a> <br><a href="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.aspx#S12"><u><font color=#0033cc>处理遗留问题</font></u></a> <br></div>
</div>
</div>
<div class=ArticleNormalPara>Microsoft<span class=superscript><font size=1>&#174;</font></span> .NET Framework 3.0 引入的新功能可用于以可视化方式和声明的方式构建由托管代码承载的工作流。开发人员可以使用现在的 .NET Framework 3.0 构建顺序工作流和状态机工作流。有关顺序工作流的文章已经很多了，因为顺序工作流非常类似于传统的编程技术。</div>
<div class=ArticleNormalPara>而状态机工作流代表的是对编程逻辑的另一种思考方法。如果能够正确地设计和实施，状态机工作流将与顺序工作流一样有用。而且，基于这种类型的工作流可以更方便地构建有人参与的工作流。因此，每个架构师和开发人员都应掌握状态机工作流的开发应用技能。</div>
<br>
<div id=S1 class=ArticleTypeTitle>状态机理论</div>
<div class=SidebarContainer>
<div class=SidebarHeadline>其他资源</div>
<div class=ArticleNormalPara>本文供下载的代码包含本文所示的所有工作流、代码图和代码段。本代码旨在提供状态机工作流的试验沙盒或试验台，因此一次只运行一个工作流实例。</div>
<div class=ArticleNormalPara>这是一个 Visual Studio 2005 解决方案，其中包含以下项目：<br><span class=ArticleInlineTitle><strong><font color=#003399 face=Arial>StateMachineLibrary </font></strong></span>包含此处提及的两个工作流。这两个工作流为 SimpleStateMachine 工作流和 SupplyFulfillment 工作流。<br><span class=ArticleInlineTitle><strong><font color=#003399 face=Arial>ConsoleHost </font></strong></span>是用于承载本文开头介绍的简单状态机工作流的控制台应用程序。<br><span class=ArticleInlineTitle><strong><font color=#003399 face=Arial>WinFormHost </font></strong></span>是用于承载 SupplyFulfillment 工作流的 Windows 窗体应用程序。ExternalDataExchange 就是用于在此项目中引发 SupplyFulfillment 工作流实例的事件。<br><span class=ArticleInlineTitle><strong><font color=#003399 face=Arial>ExternalDataExchange </font></strong></span>是包含 IEventService 接口、此接口的实现 (EventService) 和 SupplyFulfillmentArgs 类的类库。此服务允许将外部事件（和数据）发送到状态机工作流的当前状态。</div>
<div class=ArticleNormalPara>如果想要了解更多信息，以下资源将会很有帮助：
<ul>
    <li><a href="http://msdn2.microsoft.com/aa348821"><u><font color=#0033cc>使用 SqlTrackingService</font></u></a>
    <li><a href="http://msdn2.microsoft.com/aa349366"><u><font color=#0033cc>使用 SqlWorkflowPersistenceService</font></u></a>
    <li><a href="http://msdn.microsoft.com/msdnmag/issues/06/11/CuttingEdge"><u><font color=#0033cc>Windows 工作流活动概览</font></u></a>
    <li><a href="http://msdn.microsoft.com/msdnmag/issues/06/01/WindowsWorkflowFoundation"><u><font color=#0033cc>使用 Windows Workflow Foundation 的声明性模型简化开发</font></u></a>
    <li><a href="http://msdn.microsoft.com/msdnmag/issues/06/03/CuttingEdge"><u><font color=#0033cc>Windows Workflow Foundation</font></u></a>
    <li><a href="http://msdn.microsoft.com/msdnmag/issues/06/04/CuttingEdge"><u><font color=#0033cc>Windows Workflow Foundation（第 2 部分）</font></u></a></li>
</ul>
</div>
<br></div>
<div class=ArticleNormalPara>以状态机工作流的形式实现的业务逻辑，在实现方式上与以顺序工作流的形式实现的业务逻辑截然不同。与顺序工作流从一个活动到另一个活动的执行方式不同，状态机是从一个状态转换到另一个状态。可以将状态理解为界定明确的步骤、阶段、甚至是业务流程中的状态。由于状态可以指示工作流的完成情况以及以后必然发生的情况，因此状态非常有用。状态的这一特点使其特别适用于长时间运行的工作流。</div>
<div class=ArticleNormalPara>能否采用从某个起点按界定明确的步骤（或状态）移动到某个终点的方式来描述数据或逻辑，是判别业务流程是否适合状态机工作流的必备条件。请看下面对供货业务流程的说明，该业务流程可以采用状态机的方式来实现。</div>
<div class=ArticleNormalPara>
<ol>
    <li>负责处理客户订单的员工可能需要其他供应资源才能完成订单。对其他供应资源的请求应提交到中央系统进行处理。
    <li>提交请求之后，该请求将被分配到特定用户的工作队列进行审核。
    <li>分配请求之后，该请求可能被批准也可能被拒绝，具体取决于业务流程外部的判别标准。
    <li>请求被拒绝后，可能会被重新分配，也可能被取消，具体取决于业务流程外部的判别标准。重新分配的订单与已分配订单的处理方法相同。取消的订单将被视为已完成。
    <li>如果员工的请求被批准，则会定购所需资源。
    <li>收到订单后，则围绕该订单展开的业务流程被视为完成。 </li>
</ol>
</div>
<div class=ArticleNormalPara><strong>图 1</strong> 中显示了此业务流程中可被视为状态的主要步骤。</div>
<div class=ArticleImageSpacer><img alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig01.gif">
<div class=ArticleImageCaptionText>图 1<strong>&nbsp;Supply Fulfillment 工作流的逻辑设计&nbsp;</strong></div>
</div>
<div class=ArticleNormalPara>在使用 Windows<span class=superscript><font size=1>&#174;</font></span> Workflow Foundation (Windows WF) 构建此工作流之前，需要注意以下几个主要方面。首先，工作流只能有一个初始状态和一个完成状态：分别是 Submitted 和 Completed。状态机工作流的每个实例都以指定的初始状态开始，并在工作流达到指定的完成状态时结束。</div>
<div class=ArticleNormalPara>其次，状态转换是预定义的。也就是说，每个状态的定义中已经列出了下一个允许的状态。<strong>图 2</strong> 列出了各个状态和允许的转换。</div>
<div id=ctl00_ContentPlaceHolder1_cpe207053 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207053_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207053_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;Figure&nbsp;2&nbsp;Supply Fulfillment 允许的状态转换 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207053_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection><br>
<table class=charttable>
    <tbody>
        <tr>
            <th>状态</th>
            <th>允许的转换</th>
        </tr>
        <tr>
            <td vAlign=top>Submitted</td>
            <td vAlign=top>Assigned</td>
        </tr>
        <tr>
            <td vAlign=top>Assigned</td>
            <td vAlign=top>Approved 或 Rejected</td>
        </tr>
        <tr>
            <td vAlign=top>Approved</td>
            <td vAlign=top>Ordered</td>
        </tr>
        <tr>
            <td vAlign=top>Rejected</td>
            <td vAlign=top>Assigned 或 Completed</td>
        </tr>
        <tr>
            <td vAlign=top>Ordered</td>
            <td vAlign=top>Completed</td>
        </tr>
        <tr>
            <td vAlign=top>Completed</td>
            <td vAlign=top>（无）</td>
        </tr>
    </tbody>
</table>
</div>
</div>
</div>
<div class=ArticleNormalPara>第三，允许回溯。请注意，我们的要求规定可以重新分配被拒绝的请求。可以在逻辑设计中设置 Reassigned 状态，但这不是一种高效的设计方式。我们的要求规定重新分配的请求应与新分配请求的处理方法相同，因此无需在 Rejected 状态之外再单独创建一个分支来处理重新分配的请求。也就是说，将工作流转换回原来的状态，可以在工作流中更好地重用处理资源。</div>
<div class=ArticleNormalPara>第四，状态机工作流适合于长时间运行的流程。如果状态机工作流从其初始状态到其完成状态的时间非常短暂（以毫秒计算），中间没有等待外部输入的停顿，则使用顺序方法可以更高效地实现此工作流。</div>
<br>
<div id=S2 class=ArticleTypeTitle>状态机和 Windows WF</div>
<div class=ArticleNormalPara>在实现上面讨论的逻辑设计之前，我们先来了解一个简单的 Windows WF 状态机，以便对 WF 提供的所有工具有个大概了解。接下来，我将构建一个简单的状态机工作流，介绍 Visual Studio<span class=superscript><font size=1>&#174;</font></span> .NET 的设计体验和 .NET Framework 3.0 中的工具。请注意，此处的所有工作流和代码段均来自实际的 Visual Studio 解决方案，您可以在可从本文下载的打包代码中找到此解决方案。有关详细信息，请参阅&#8220;其他资源&#8221;侧栏。</div>
<div class=ArticleNormalPara>在从 <a href="http://go.microsoft.com/fwlink/?LinkId=105219"><u><font color=#0033cc>go.microsoft.com/fwlink/?LinkId=105219</font></u></a> 安装 Visual Studio 2005 extensions for .NET Framework 3.0 (Windows WF) 时，会向 Visual Studio .NET 2005 环境中添加几个新的项目模板（参见<strong>图 3</strong>）。请注意，如果您使用的是 Visual Studio 2008，所有这些模板都已包含在其核心产品中。选择了其中某个状态机项目模板后，将使用正确的引用和工作流文件（可用作状态机工作流的开发起点）建立 Visual Studio .NET 项目。</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig03_L.gif'; var small='cc163281.fig03.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig03.gif">
<div class=ArticleImageCaptionText>图 3<strong>&nbsp;Workflow Foundation 项目模版&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<div class=ArticleNormalPara>与顺序工作流设计器相比，状态机工作流设计器具有更多限制。对于大多数状态机而言，只需将 State 活动拖放到设计器中即可。（还可以向设计器中添加 EventDriven 活动，用于处理未与特定状态关联的事件。）State 活动表示状态机工作流中的步骤、阶段或状态。此活动用于表示初始状态和完成状态，以及两者之间可能发生的所有状态。</div>
<div class=ArticleNormalPara>构建状态机的第一步是，通过从工作流工具箱中拖动 State 活动并为每个状态分配一个有意义的名称，将所需的所有状态放到设计图面上。工作流状态也是通过&#8220;属性&#8221;对话框进行命名，其方法与在 Windows Forms 项目中命名 Windows 控件一样。<strong>图 4</strong> 是包含以下三个状态的简单状态机工作流示例：Submitted、Ordered 和 Completed。</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig04_L.gif'; var small='cc163281.fig04.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig04.gif">
<div class=ArticleImageCaptionText>图 4<strong>&nbsp;简单状态机工作流&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<div class=ArticleNormalPara>Workflow Foundation 需要知道初始状态和完成状态，以便于进行正确的运行时管理。因此，下一步就是通过工作流本身的&#8220;属性&#8221;对话框指定初始状态和完成状态。要查看此对话框，请首先确保在 Visual Studio .NET 中显示&#8220;属性&#8221;对话框，然后单击工作流的设计图面，或者右键单击工作流设计器并选择&#8220;属性&#8221;。<strong>图 4</strong> 右下角显示了状态机工作流的&#8220;属性&#8221;对话框。属性 InitialStateName 和 CompletedStateName 分别用于指定初始状态和完成状态。指定这些属性后，设计器将修改指定状态的图形说明。仔细查看<strong>图 4</strong>，您会发现初始状态上显示绿灯图标，而完成状态上显示红灯图标。</div>
<div class=ArticleNormalPara>State 活动是复合活动，这意味着它可以包含其他活动。同样还需要考虑一些限制。State 活动可以只包含一个 StateInitialization 活动、一个 StateFinalization 活动、一个或多个 EventDriven 活动，以及嵌套状态的一个或多个 State 活动。只有四个可以放入 State 活动的&#8220;开箱即用&#8221;活动。本文不讨论嵌套活动，下一节将介绍如何使用 EventDriven 活动。我们的简单状态机示例将只使用 StateInitialization 活动和 StateFinalization 活动。</div>
<div class=ArticleNormalPara>通过使用 StateInitialization 和 StateFinalization 活动进行实验，可以很好地了解如何使用状态机设计器。通过使用这两个活动，您还可以更好地理解如何控制状态机工作流的流动情况。</div>
<div class=ArticleNormalPara><strong>图 4</strong> 中简单示例的所有状态（Completed 状态除外）都具有 StateInitialization 活动和 StateFinalization 活动。实际上，将某个状态指定为完成状态之后，就不能再为其添加活动。因为，转换为 Completed 状态的操作就意味着工作流生命周期的结束。</div>
<div class=ArticleNormalPara>在程序控制权传递到某个状态活动并开始执行时，该状态活动将检查是否存在 StateInitialization 活动。如果存在，则执行该活动。同样地，某个状态活动在将控制权传递给另一个状态时，会检查是否存在 StateFinalization 活动。如果存在，则执行该活动。需要注意 StateInitialization 活动和 StateFinalization 活动也都是复合活动，这一点很重要。与 State 活动不同，对于这些活动可以包含的活动的限制非常少。也就是说，顺序工作流中可以使用的所有活动都可以放在这些活动中。Visual Studio 将提供不同的设计视图以将逻辑放入这些复合活动中。<strong>图 5</strong> 显示了双击<strong>图 4</strong> 中 Submitted 状态的 StateInitialization 活动后显示的设计视图。此时，Submitted 状态已具有 StateInitialization 活动（名为 SubmittedInitialization）。此设计视图的目的是允许将其他活动放入 StateInitialization 活动中。<strong>图 5</strong> 的示例中，SubmittedInitialization 活动中放入了 Code 活动和 SetState 活动。以下代码已放入 Code 活动的执行事件中：</div>
<div class=ArticleImageSpacer><img alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig05.gif">
<div class=ArticleImageCaptionText>图 5<strong>&nbsp;StateInitialization 活动的设计视图&nbsp;</strong></div>
</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl09_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl09');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl09 class=code space="preserve">private void codeSubmittedInitialization_ExecuteCode(object sender,
EventArgs e)
{
Console.WriteLine("The Submitted state has been initialized at " +
DateTime.Now.ToString() + ".");
}
</pre>
</div>
</div>
<div class=ArticleNormalPara>SetState 活动（<strong>图 5</strong> 中的 setStateOrdered）用于将控制权转换给 StateMachine 工作流中的另一个状态。无需代码即可使用此活动。<strong>图 6</strong> 显示了 setStateToOrdered 的&#8220;属性&#8221;对话框。必须将属性 TargetStateName 设置为 StateMachine 工作流中的有效状态。在执行此活动后，将立即执行当前状态的 StateFinalization 活动，控制权将转移到 Ordered 状态。在 SubmittedInitialization 活动中将不再执行其他活动。因此，必须确保该活动为最后一个活动。Submitted 状态的 StateFinalization 活动已配置了一个简单代码活动，其执行事件中包含以下代码：</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig06_L.gif'; var small='cc163281.fig06.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig06.gif">
<div class=ArticleImageCaptionText>图 6<strong>&nbsp;SetState 活动的属性&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl11_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl11');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl11 class=code space="preserve">  private void
codeSubmittedFinalization_ExecuteCode(
object sender, EventArgs e)
{
Console.WriteLine("The Submitted " +
"state has been finalized at " +
DateTime.Now.ToString()+".");
}
</pre>
</div>
</div>
<div class=ArticleNormalPara>请注意，不能将 SetState 活动放入 StateFinalization 活动中，这一点很重要。否则没有任何意义，因为如果这样做，则只有在执行 SetState 活动后才会执行 StateFinalization 活动。SetState 活动只能位于 StateInitialization 或 EventDriven 活动内。</div>
<div class=ArticleNormalPara>如果要返回设计器的初始设计视图（显示整个工作流），只需单击<strong>图 5</strong> 所示设计器左上角上显示的工作流名称即可。设计器的这个区域用于进行导航。单击&#8220;SimpleStateMachine&#8221;可将设计视图返回到显示工作流中所有状态的初始视图。如果您的状态机非常复杂并包含多级嵌套状态，设计器的导航区域可以准确地显示您在工作流中的位置。</div>
<div class=ArticleNormalPara>此时，状态机设计器将检测通过 setState 活动指定的所有状态转换，并在设计图面上绘制相应的转换箭头。您可以通过两种方法修改 SetState 活动的 TargetStateName 属性。第一种方法就是上面介绍的使用&#8220;属性&#8221;对话框。还可以通过将转换箭头的箭头尾部拖放到所需的 State 活动，直接从工作流的设计图面设置 TargetStateName 属性。</div>
<div class=ArticleNormalPara>简单状态机工作流（图 5 ）中的 Ordered 状态的建立方式与 Submitted 状态类似。也就是说，StateInitialization 和 StateFinalization 活动都使用 Code 活动向控制台写入消息。此外，在 StateInitialization 活动中使用 SetState 活动，将控制权转换给状态机工作流中的 Completed 状态。</div>
<div class=ArticleNormalPara>现在我们就可以使用该工作流了。用于将工作流运行时实例化，创建一个此工作流的实例，并从控制台应用程序启动工作流的代码如下所示：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl12_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl12');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl12 class=code space="preserve">static void Main(string[] args)
{
// Create the workflow runtime.
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(SimpleStateMachine));
instance.Start();
Console.ReadLine();
}
}
</pre>
</div>
</div>
<div class=ArticleNormalPara>执行此代码后，控制台输出窗口中将显示以下输出：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl13_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl13');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl13 class=code space="preserve">The Submitted state has been initialized at 8/6/2007 7:40:36 PM.
The Submitted state has been finalized at 8/6/2007 7:40:36 PM.
The Ordered state has been initialized at 8/6/2007 7:40:36 PM.
The Ordered state has been finalized at 8/6/2007 7:40:36 PM.
</pre>
</div>
</div>
<div class=ArticleNormalPara>请注意，本节介绍的工作流实现了我在本文开头介绍的 Supply Fulfillment 工作流的三个所需状态（Submitted、Ordered 和 Completed）。下面我将为此工作流添加其他状态（Assigned、Approved 和 Rejected），以便说明回溯以及可以转换为多个可能状态的状态。还将添加一些事件。</div>
<br>
<div id=S3 class=ArticleTypeTitle>与状态机工作流进行通信</div>
<div class=ArticleNormalPara>现在您已经了解了 WF 状态机的基本移动方式，接下来将讨论实际应用中的几点注意事项。在实际应用中，状态机运行的时间通常很长，其生命周期中的大部分时间都是暂停于某个状态，等待来自工作流外部的输入。使用 EventDriven 活动，可以暂停执行状态以等待外部信息。如前所述，EventDriven 活动是可以放入 State 活动的几个活动中的一个（其他两个是 StateInitialization 和 StateFinalization）。外部信息通过事件传递到 EventDriven 活动。具体来说，事件由外部数据服务引发，并由 EventDriven 活动接收。建立 EventDriven 活动可能有点复杂，以下步骤可以为您提供一些入门指导。</div>
<div class=ArticleNormalPara>
<ol>
    <li>确定数据交换和状态转换所需的事件。
    <li>定义外部数据交换接口。
    <li>为状态机工作流添加 EventDriven 活动。
    <li>引用外部数据交换接口。
    <li>实现外部数据交换接口。
    <li>将外部数据接口添加到工作流运行时。
    <li>使用外部数据交换来交换数据和转换状态。 </li>
</ol>
</div>
<div class=ArticleNormalPara><strong>图 7</strong> 显示了如何将上述步骤中提及的各个部分组合在一起。</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig07_L.gif'; var small='cc163281.fig07.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig07.gif">
<div class=ArticleImageCaptionText>图 7<strong>&nbsp;配置和使用状态机工作流&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<br>
<div id=S4 class=ArticleTypeTitle>数据交换和状态转换所需的事件</div>
<div class=ArticleNormalPara>第一步确定状态机工作流中每个状态所需的事件。状态机事件通常会导致工作流更改状态。因此，确定所需事件的最好方法是查看要求，找出指示状态转换的关键字。回顾一下<strong>图 1</strong>，在构建逻辑设计时我们需要查看相关要求来确定必需的状态和状态转换。此信息还可用来确定所需的事件。<strong>图 8</strong> 进一步扩展了<strong>图 2</strong> 中的数据，列出了 Supply Fulfillment 工作流中各个状态的状态转换情况。<strong>图 8</strong> 显示了工作流中的各个状态、各个状态要侦听的事件以及针对所列的各个事件要转换到的状态。如果工作流实例需要相关数据以便移动到一个新状态，则 Workflow Foundation 会允许将信息作为事件参数进行传递。</div>
<div id=ctl00_ContentPlaceHolder1_cpe207054 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207054_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207054_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;Figure&nbsp;8&nbsp;Supply Fulfillment 工作流事件 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207054_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection><br>
<table class=charttable>
    <tbody>
        <tr>
            <th>状态</th>
            <th>事件名称</th>
            <th>转换</th>
        </tr>
        <tr>
            <td vAlign=top>Submitted</td>
            <td vAlign=top>OnAssigned</td>
            <td vAlign=top>Assigned</td>
        </tr>
        <tr>
            <td vAlign=top>Assigned</td>
            <td vAlign=top>OnApproved</td>
            <td vAlign=top>Approved</td>
        </tr>
        <tr>
            <td vAlign=top>Assigned</td>
            <td vAlign=top>OnRejected</td>
            <td vAlign=top>Rejected</td>
        </tr>
        <tr>
            <td vAlign=top>Approved</td>
            <td vAlign=top>OnOrdered</td>
            <td vAlign=top>Ordered</td>
        </tr>
        <tr>
            <td vAlign=top>Rejected</td>
            <td vAlign=top>OnReassigned</td>
            <td vAlign=top>Assigned</td>
        </tr>
        <tr>
            <td vAlign=top>Rejected</td>
            <td vAlign=top>OnCanceled</td>
            <td vAlign=top>Completed</td>
        </tr>
        <tr>
            <td vAlign=top>Ordered</td>
            <td vAlign=top>OnOrderReceived</td>
            <td vAlign=top>Completed</td>
        </tr>
    </tbody>
</table>
</div>
</div>
</div>
<div class=ArticleNormalPara>请注意， Assigned 状态会侦听两个事件，而且是同时侦听。接收到的事件将决定状态转换。这是外部实体用于决定状态机工作流运行路径的方法。（Rejected 状态也会同时侦听两个事件。）</div>
<br>
<div id=S5 class=ArticleTypeTitle>定义外部数据交换接口</div>
<div class=ArticleNormalPara>下一步是在标准 .NET 接口中定义这些事件。<strong>图 9</strong> 显示了 Supply Fulfillment 工作流所需的接口，<strong>图 10</strong> 显示了自定义 EventArgs 类，该类用于在其中任一事件被引发时将数据传递到工作流实例。</div>
<div id=ctl00_ContentPlaceHolder1_cpe207055 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207055_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207055_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;Figure&nbsp;10&nbsp;自定义 EventArgs 类 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207055_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection>
<div id=ctl00_ContentPlaceHolder1_ctl20_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl20');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl20 class=code space="preserve">namespace ExternalDataExchange
{
[Serializable]
public class SupplyFulfillmentArgs : ExternalDataEventArgs
{
public SupplyFulfillmentArgs(System.Guid InstanceId) :
base(InstanceId) { }
private string assignedTo;
private string orderID;
public string AssignedTo
{
get { return assignedTo; }
set { assignedTo = value; }
}
public string OrderID
{
get { return orderID; }
set { orderID = value; }
}
}
}
</pre>
</div>
</div>
</div>
</div>
<div id=ctl00_ContentPlaceHolder1_cpe207056 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207056_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207056_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;Figure&nbsp;9&nbsp;外部数据服务接口 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207056_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection>
<div id=ctl00_ContentPlaceHolder1_ctl24_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl24');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl24 class=code space="preserve">namespace ExternalDataExchange
{
[ExternalDataExchange()]
public interface IEventService
{
event EventHandler&lt;SupplyFulfillmentArgs&gt; Assigned;
event EventHandler&lt;SupplyFulfillmentArgs&gt; Approved;
event EventHandler&lt;SupplyFulfillmentArgs&gt; Rejected;
event EventHandler&lt;SupplyFulfillmentArgs&gt; Reassigned;
event EventHandler&lt;SupplyFulfillmentArgs&gt; Canceled;
event EventHandler&lt;SupplyFulfillmentArgs&gt; Ordered;
event EventHandler&lt;SupplyFulfillmentArgs&gt; OrderReceived;
}
}
</pre>
</div>
</div>
</div>
</div>
<div class=ArticleNormalPara>关于此接口，有几点注意事项。首先是 ExternalDataExchange 属性的使用。与任何其他服务一样，用于实现标有 ExternalDataExchange 属性的接口的类可以添加到工作流运行时引擎中。（例如，持久性服务和跟踪服务都可以添加到工作流运行时中。）</div>
<div class=ArticleNormalPara>其次请注意，用作接口中所声明事件的参数的 SupplyFulfillmentArgs 必须从 ExternalDataEventArgs 继承。此外，ExternalDataEventArgs 需要将工作流实例 ID 传递到其构造函数，这样事件才能触发正确的工作流实例。最后，SupplyFulfillmentArgs 必须被标记为可序列化，以防工作流运行时需要将其序列化以便进行持久存储。</div>
<br>
<div id=S6 class=ArticleTypeTitle>添加 EventDriven 活动</div>
<div class=ArticleNormalPara>在建立外部数据交换接口之后，接下来是向状态机中添加 EventDriven 活动。与 StateInitialization 和 StateFinalization 活动类似，EventDriven 活动也是复合活动。因此，其目的就是能够包含其他活动。向 State 活动中添加 EventDriven 活动之后，我们就可以像针对 StateInitialization 和 StateFinalization 活动那样深入剖析 EventDriven 活动，从而定义其他逻辑。在向 EventDriven 活动中放入活动时，我们需要遵循几条规则，这一点与 StateInitialization 和 StateFinalization 活动不同。这是由于 EventDriven 活动继承自 Sequence 活动，因此具有以下限制：第一个子活动必须能够实现公共接口 IEventActivity。所有后续活动可以为任何类型。在本示例中，使用的是 HandleExternalEvent 活动。此活动可以接收工作流运行时外部的事件。还有两个活动实现了 IEventActivity 接口，它们是 Delay 活动和 WebServiceInput 活动。</div>
<div class=ArticleNormalPara>在 SupplyFulfillment 工作流中，有七个逻辑事件需要 EventDriven 活动。<strong>图 8</strong> 中包含将 EventDriven 活动正确放入相应的状态所需的信息。请注意，放入这些活动的逻辑将最终导致状态发生转换，这一点也很重要。<strong>图 11</strong> 显示了添加所有 EventDriven 活动之后 SupplyFulfillment 状态机的设计器视图。请注意，转换箭头源自 EventDriven 活动，终于 State 活动。<strong>图 12</strong> 显示了 OnAssigned EventDriven 活动中所包含的逻辑。</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig11_L.gif'; var small='cc163281.fig11.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig11.gif">
<div class=ArticleImageCaptionText>图 11<strong>&nbsp;状态机工作流中的 EventDriven 活动&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<div class=ArticleImageSpacer><img alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig12.gif">
<div class=ArticleImageCaptionText>图 12<strong>&nbsp;EventDriven 活动中的逻辑&nbsp;</strong></div>
</div>
<div class=ArticleNormalPara>从技术上来讲，已不再需要<strong>图 11</strong> 中显示的 StateInitialization 和 StateFinalization 活动。在此显示这两个活动，并为其设置控制台消息的目的只是为了进行演示。</div>
<div class=ArticleNormalPara>值得一提的是，<strong>图 11</strong> 中 Supply Fulfillment 工作流的图示说明与<strong>图 1</strong> 中通过 Microsoft Visio<span class=superscript><font size=1>&#174;</font></span> 创建的逻辑图一样，非常便于阅读。开发项目要便于开发人员、架构师和最终用户理解和验证，这是业务流程管理的核心原则。</div>
<br>
<div id=S7 class=ArticleTypeTitle>引用外部数据交换接口</div>
<div class=ArticleNormalPara>添加完 EventDriven 活动并按照与<strong>图 11</strong> 中类似的方法建立每个 EventDriven 活动的子活动之后，下一步是配置 HandleExternalEvent 活动。请记住，在建立 EventDriven 活动时，有一项构造规则是：第一个子活动必须是与 HandleExternalEvent 活动类似的活动。您可以从<strong>图 12</strong> 的逻辑中来了解，图中名为 OnAssigned 的 EventDriven 活动的第一个子活动为名为 handleAssigned 的 HandleExternalEvent 活动，其后是名为 SetStateAssigned 的 SetState 活动。</div>
<div class=ArticleNormalPara>HandleExternalEvent 活动将导致 StateMachine 工作流中断并等待处理由外部数据交换服务引发的事件。HandleExternalEvent 活动等待的特定事件在&#8220;属性&#8221;对话框中指定（如<strong>图 13</strong> 所示）。要设置的第一个属性是 InterfaceType 属性，该属性必须设置为标有 ExternalDataExchange 属性的接口。在 Supply Fullfilment 工作流中，此接口为<strong>图 9</strong> 中显示的 IEventService 接口。设置 InterfaceType 属性之后，可以指定 EventName 属性。此&#8220;属性&#8221;对话框将反映通过 InterfaceType 属性定义的接口，并为 EventName 属性提供下拉框。<strong>图 13</strong> 中显示的对话框对应于 Supply Fulfillment 工作流的 OnAssigned 事件。</div>
<div class=ArticleImageSpacer><img style="CURSOR: pointer" onmouseover="this.style.cursor='pointer';" onclick="var large='cc163281.fig13_L.gif'; var small='cc163281.fig13.gif'; var current=this.src;  if (current.indexOf(large) > 0) this.src=small; if (current.indexOf(small) > 0)this.src=large;" alt="" src="http://msdn2.microsoft.com/zh-cn/magazine/cc163281.fig13.gif">
<div class=ArticleImageCaptionText>图 13<strong>&nbsp;handleAssigned 属性&nbsp;</strong>(单击该图像获得较大视图)</div>
</div>
<div class=ArticleNormalPara>在关闭此对话框之前，还有一点需要注意。请记住，ExternalDataExchange.IEventService 接口中的所有事件都需要一个 SupplyFulfillmentArgs 参数。此参数可用于在引发外部事件且工作流接收到该事件时，将数据传递到工作流实例。有两种方法可以让工作流内的其他活动能够访问此数据。一种方法是使用<strong>图 13</strong> 所示的&#8220;e&#8221;属性。通过此属性，您可以指定工作流中用于保持由事件传递的参数的属性。在工作流属性中设置此事件参数后，此工作流中的其他活动就可以使用该参数。显然，指定的属性需要与事件参数具有相同的类型。在 SupplyFulfillment 工作流中，应为 SupplyFulfillmentArgs 类型。另一种方法是，使用 Invoked 属性在工作流中指定事件处理程序。在接收到外部事件之后会调用此事件处理程序，并为该事件处理程序传递一个 ExternalDataEventArgs 类型的参数，该参数可以转换为合适的类型 (SupplyFulfillmentArgs)。</div>
<div class=ArticleNormalPara>此时请注意，HandleExternalEvent 活动仅负责处理 ExternalDataExchange 接口，而不是接口的实际实现。</div>
<br>
<div id=S8 class=ArticleTypeTitle>实现外部数据交换服务</div>
<div class=ArticleNormalPara>为了成功地实现外部数据交换服务，您需要实现工作流中 HandleExternalEvent 活动引用的接口。特别是，必须实现 IEventService 接口。以下代码用于创建 EventService 类并实现该接口：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl28_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl28');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl28 class=code space="preserve">[Serializable]
public class EventService : IEventService
{
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Assigned;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Approved;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Rejected;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Reassigned;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Canceled;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; Ordered;
public event EventHandler&lt;SupplyFulfillmentArgs&gt; OrderReceived;
...
}
</pre>
</div>
</div>
<div class=ArticleNormalPara>由于事件只能从定义该事件的类中引发，我们需要添加帮助函数来帮助引发事件。此类中每个事件都需要一个帮助函数。以下代码显示了 Assigned 事件的帮助函数：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl29_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl29');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl29 class=code space="preserve">public void RaiseAssignedEvent(System.Guid instanceId, string
assignedTo)
{
// Check to see if event is defined
if (this.Assigned != null)
{
// Create the EventArgs for this event
LocalService.SupplyFulfillmentArgs args = new
LocalService.SupplyFulfillmentArgs(instanceId);
args.AssignedTo = assignedTo;
// Raise the event
this.Assigned(this, args);
}
}
</pre>
</div>
</div>
<div class=ArticleNormalPara>请注意，在引发事件时，将会向注册的所有委托传递两个参数。第一个参数是 sender。上述代码将传递 EventService 类的当前实例，为了将其实例作为 sender 传递，必须将其标为&#8220;serializable&#8221;。否则，将会收到提示事件无法发送的错误。以下是此错误消息的示例：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl30_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl30');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl30 class=code space="preserve">  Event "Assigned" on interface type
"ExternalDataExchange.IEventService"
for instance id "0f9e8545-39bf-48b6-b890-
c12dec11cb9f" cannot be delivered.
</pre>
</div>
</div>
<div class=ArticleNormalPara>第二个参数是 SupplyFulfillmentArgs 参数。实例化此类时，需要工作流实例标识符，即首次实例化工作流时生成的 Guid。工作流运行时将使用此 Guid 来确定接收事件的工作流实例的位置。但是，为了确保定位准确，必须将 EventService 类的实例作为外部服务添加到工作流运行时中。</div>
<br>
<div id=S9 class=ArticleTypeTitle>向工作流运行时中添加外部数据交换服务</div>
<div class=ArticleNormalPara>由于工作流运行时管理实例化工作流的所有执行和交互操作，此前刚创建的类必须作为外部数据交换服务添加到工作流运行时中。以下代码显示了如何将 EventService 类的实例作为 ExternalDataExchange 服务类添加到工作流运行时中：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl31_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl31');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl31 class=code space="preserve">// Create a new instance of the local service and host it in an
//ExternalDataExchangeService.
ExternalDataExchangeService externalDataSvc = new
ExternalDataExchangeService();
wr.AddService(externalDataSvc);
eventService = new EventService();
externalDataSvc.AddService(eventService);
</pre>
</div>
</div>
<div class=ArticleNormalPara>工作流运行时对于添加过程的要求非常严格。必须首先创建外部数据服务，并将其添加到工作流运行时，然后才能将 EventService 类添加到 ExternalDataExchangeService 类。如果在将 ExternalDataExchangeService 对象添加到工作流运行时之前尝试使用该对象，则会收到&#8220;WorkflowRuntime 容器未包含 ExternalDataExchangeService&#8221;错误。</div>
<div class=ArticleNormalPara>添加完成后，由 EventService 类的实例引发的事件将能够找到正确的工作流实例和工作流中正确的 HandleExternalEvent 活动。</div>
<br>
<div id=S10 class=ArticleTypeTitle>使用外部数据交换</div>
<div class=ArticleNormalPara>现在，所有必备组件都已建立完毕，可以使用外部数据交换来交换数据和转换工作流的状态了。使用外部数据交换，就如同调用上述 EventService 类中建立的帮助函数一样简单。以下代码段显示了如何使用帮助函数将工作流状态从 Submitted 转换成 Assigned。请注意，工作流实例 ID 和&#8220;assignTo&#8221;会传递到函数中。这两个参数将被打包到 SupplyFulfillmentArgs 类的实例中，以便能够通过 Assigned 事件将它们传递到工作流。如上所述，工作流运行时使用 workflowInstanceID 参数，将 Assigned 事件引发到正确的工作流实例。assignTo 参数（&#8220;Joe&#8221;）将被直接发送到工作流执行过程中要使用的工作流：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl32_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl32');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl32 class=code space="preserve">eventService.RaiseAssignedEvent(workflowInstanceId, "Joe");
</pre>
</div>
</div>
<div class=ArticleNormalPara>请注意，在任何给定的点，正在运行的状态机工作流只侦听当前 State 活动中的事件，这一点很重要。如果引发任何其他事件，调用代码将收到异常。例如，实例化 SupplyFullfilment 工作流后，该工作流应处于初始状态（Submitted 状态），这意味着工作流等待引发 Assigned 事件。如果调用了引发其他事件（Approved、Rejected、Reassigned、Canceled、Ordered 和 OrderReceived）的任何其他帮助函数，您将收到表明无法发送事件的错误消息。如果工作流处于 Approved 之外的任何其他状态，当调用 Ordered 事件时，将显示如下例所示的错误消息：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl33_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl33');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl33 class=code space="preserve">Event "Ordered" on interface type "ExternalDataExchange.IEventService" for instance id "59657ed0-1532-42c5-8abe-6523bf121fff" cannot be delivered.
</pre>
</div>
</div>
<div class=ArticleNormalPara>正如您看到的那样，只要事件发送出错，就会显示这一非常空泛的错误消息，但是消息内容非常简单，不能帮助准确确定错误原因。</div>
<div class=ArticleNormalPara>显然，这引出了诸如如何编写可靠的工作流代码等问题。具体来说，如何让您的代码知道状态机实例的当前状态，以便可以通过编程方式确保将正确的事件发送到工作流？幸好，使用状态机工作流的代码可以利用多个有用的 API 来编写更加可靠的工作流代码。</div>
<br>
<div id=S11 class=ArticleTypeTitle>使用状态机 API</div>
<div class=ArticleNormalPara>WF 随附了一个名为 StateMachineWorkflowInstance 的实用程序类，专用于管理状态机工作流的实例。<strong>图 14</strong> 简要介绍了 StateMachineWorkflowInstance 类的构造函数、属性和方法。</div>
<div id=ctl00_ContentPlaceHolder1_cpe207057 class=MTPS_CollapsibleRegion>
<div id=ctl00_ContentPlaceHolder1_cpe207057_h class=CollapseRegionLink><img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; VERTICAL-ALIGN: middle; BORDER-RIGHT-WIDTH: 0px" id=ctl00_ContentPlaceHolder1_cpe207057_i src="http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/plus.gif">&nbsp;Figure&nbsp;14&nbsp;StateMachineWorkflowInstance 的构造函数、属性和方法 </div>
<div style="OVERFLOW: hidden; WIDTH: auto; DISPLAY: none; HEIGHT: 0px" id=ctl00_ContentPlaceHolder1_cpe207057_c class=MTPS_CollapsibleSection oldDisplay="block">
<div style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; DISPLAY: block" class=MTPS_CollapsibleSection><br>
<table class=charttable>
    <tbody>
        <tr>
            <th vAlign=top colSpan=2>公共构造函数</th>
        </tr>
        <tr>
            <td vAlign=top>名称</td>
            <td vAlign=top>说明</td>
        </tr>
        <tr>
            <td vAlign=top>StateMachineWorkflowInstance</td>
            <td vAlign=top>构造函数名称。此函数需要两个参数。一个是工作流运行时实例。第二个是 Guid，表示此类的属性和函数将要查询或管理的状态机实例。</td>
        </tr>
        <tr>
            <th vAlign=top colSpan=2>公共属性</th>
        </tr>
        <tr>
            <td vAlign=top>名称</td>
            <td vAlign=top>说明</td>
        </tr>
        <tr>
            <td vAlign=top>CurrentState</td>
            <td vAlign=top>只读。返回当前状态的 StateActivity 对象。</td>
        </tr>
        <tr>
            <td vAlign=top>CurrentStateName</td>
            <td vAlign=top>只读。返回表示当前状态活动名称的字符串。</td>
        </tr>
        <tr>
            <td vAlign=top>InstanceID</td>
            <td vAlign=top>只读。返回唯一标识工作流运行时内的状态机工作流的 Guid。</td>
        </tr>
        <tr>
            <td vAlign=top>PossibleStateTransitions</td>
            <td vAlign=top>只读。返回表示从当前状态可能出现的状态转换的 ReadOnlyCollection&lt;字符串&gt;。</td>
        </tr>
        <tr>
            <td vAlign=top>StateHistory</td>
            <td vAlign=top>只读。返回当前工作流已经发生的状态转换的 ReadOnlyCollection&lt;字符串&gt;。此属性需要将跟踪服务添加到工作流运行时中。</td>
        </tr>
        <tr>
            <td vAlign=top>StateMachineWorkflow</td>
            <td vAlign=top>只读。返回 StateMachineWorkflowActivity 类型。仅为工作流定义的副本，而不是实时实例树，因此决不应将此属性用于检索运行时值。</td>
        </tr>
        <tr>
            <td vAlign=top>States</td>
            <td vAlign=top>只读。返回当前工作流中定义的所有状态的 ReadOnlyCollection&lt;字符串&gt; 集合。与 StateMachineWorkflow 属性类似，此属性只包含定义，而没有实时数据。</td>
        </tr>
        <tr>
            <td vAlign=top>WorkflowInstance</td>
            <td vAlign=top>只读。返回表示当前实例的当前 WorkflowInstance 类型。此属性提供对实时实例树的访问。</td>
        </tr>
        <tr>
            <th vAlign=top colSpan=2>公共方法</th>
        </tr>
        <tr>
            <td vAlign=top>名称</td>
            <td vAlign=top>说明</td>
        </tr>
        <tr>
            <td vAlign=top>EnqueueItem</td>
            <td vAlign=top>将消息发布到状态机工作流的队列。通常不需要。前面的部分中介绍的 HandleExternalEvent 活动使用不容易出错的编程模型对消息进行排队。</td>
        </tr>
        <tr>
            <td vAlign=top>SetState</td>
            <td vAlign=top>提供向通过其唯一参数指定的状态的转换。可用于执行向工作流中任何状态的转换，即便指定的状态在工作流正常执行的情况下不会发生此状态转换。</td>
        </tr>
    </tbody>
</table>
</div>
</div>
</div>
<div class=ArticleNormalPara>如果使用此类，请注意以下警告信息。首先，事件驱动的状态机（如 SupplyFulfillment 工作流）本质上是异步执行的。因此，如果在对状态机实例引发事件之后立刻查询<strong>图 14</strong> 中显示的公共属性，则查询到的属性将不能准确反映工作流的状态。事件驱动的工作流在处理完当前事件之后将处于空闲状态，并等待下一个事件的到来。因此，可以通过工作流运行时的 OnWorkflowIdled 事件来查询使用这些属性的状态机实例。</div>
<div class=ArticleNormalPara>其次，StateHistory 属性需要将跟踪服务添加到工作流运行时中。如果不添加跟踪服务就使用此属性，您将收到以下错误：</div>
<div class=ArticleNormalPara>
<div id=ctl00_ContentPlaceHolder1_ctl36_ class=code>
<div class=CodeSnippetTitleBar>
<div class=CodeDisplayLanguage></div>
<div class=CopyCodeButton><a class=copyCode href="javascript:CopyCode('ctl00_ContentPlaceHolder1_ctl36');"><img border=0 align=middle src="http://i.msdn.microsoft.com/Platform/Controls/CodeSnippet/resources/copy_off.gif" height=9><font color=#0000ff> 复制代码</font></a></div>
</div>
<pre id=ctl00_ContentPlaceHolder1_ctl36 class=code space="preserve">'StateHistory' property requires the 'System.Workflow.Runtime.Tracking.SqlTrackingService' service.
</pre>
</div>
</div>
<div class=ArticleNormalPara>最后，在向最终用户公开 SetState 方法的功能时须谨慎。此方法可以导致工作流转换为任何状态，最适用于管理员或经理处理非标准情形。</div>
<br>
<div id=S12 class=ArticleTypeTitle>处理遗留问题</div>
<div class=ArticleNormalPara>讲解完状态机理论、设计时体验和构建可用状态机所需的基本活动后，还有几个重要问题需要考虑。首先，如果状态机在服务器环境中运行，则必须在执行时优化系统资源，并且不会受系统重启的影响。此外，最好能够跟踪并保存历史数据，这样可以根据跟踪报告优化业务流程。例如，如果使用本文中的 Supply Fulfillment 工作流来运行一个小型业务，最好能够知道每个工作流从 Ordered 状态到 Completed 状态所花费的时间。如果某个特定供应商经常违反服务级别协议，则可通过删除违规供应商来优化获取新的供应资源的业务流程。</div>
<div class=ArticleNormalPara>应予以进一步研究的另一个主题是有人参与的工作流。基于状态机可以更方便地构建有人参与的工作流。如果将状态机用于管理有人参与的业务流程，在构建用户界面的时候必须谨慎。用户应清楚通过状态机执行的路径，并且只能为用户提供与当前状态相关的选项。</div>
<br>
<div class=ContentSeparator>
<div class=AuthorBio><strong>Keith Pijanowski</strong> 是 Microsoft 开发人员和平台推广团队的平台战略顾问，旨在帮助客户在各种业务问题中应用新理念和新技术。他还经常担任新泽西州和纽约地区举办的 Microsoft 活动的发言人。您可以通过 <a href="http://www.keithpij.com/"><u><font color=#0033cc>www.KeithPij.com</font></u></a> 与他联系。</div>
</div>
</div>
</div>
<img src ="http://www.cnitblog.com/MartinYao/aggbug/42594.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/MartinYao/" target="_blank">玄铁剑</a> 2008-04-21 21:25 <a href="http://www.cnitblog.com/MartinYao/articles/42594.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>