学习完本章,你将掌握:
1.理解过程式(imperative)工作流模型和声明式(declarative)工作流模型之间的主要区别
2.创建声明式工作流
3.使用XAML XML词汇来创建工作流
4.调入基于XAML的工作流并执行
许多开发者或许并不知道WF既能用基于过程化的定义来执行工作流(使用工作流视图设计器)也能用基于声明式的定义来执行工作流(工作流使用XML来进行定义)。
每一种风格都有优点。当你使用我们贯穿本书已使用过的技术来创建你的工作流应用程序的时候,工作流模型实际上是被编译进了一个可执行的程序集中。其优点是加载、执行工作流的速度快。
但这种风格也缺乏灵活性。尽管有为WF加入动态能力的办法(这不包括在本书中),但通常你的工作流仍然要由你去编译它们。假如你的业务处理逻辑变化了的话,除非你为工作流中的判定使用了声明性规则(在第12章“策略和规则”中讨论过),否则的话你将不得不修改你的工作流定义,重新进行编译和部署,此外还伴随着去执行所有相关的测试和验证工作。
但是,工作流运行时有能力接受几乎任何形式的工作流定义。你以前还不得不去写一些代码来把你提供的工作流定义转化成工作流运行时能够执行的模型。事实上,这些正是WF处理基于XML的工作流定义所要做的事。
正如你可能期望的,把你的工作流记录进一个XML格式的文件中,这能让你很容易地修改和重新部署。这就不用在Microsoft Visual Studio中重新编译你的工作流,你可简单地使用任何XML编辑器(甚至是Windows中的“Notepad”记事本程序)来修改基于XML的工作流定义并把它创建的工作流模型提供给工作流运行时。你甚至能有两全其美的办法:通过使用WF的工作流编译器来编译你的XML工作流定义。我们将在本章探讨这些内容。
声明式工作流——XML标记
首先,.NET 3.0中的声明式应用程序(它包括WPF)的定义有着悠久的历史。WPF开始提供声明式编程的能力,它既可完全地进行声明化也可进行部分声明。你可完全地使用像XML应用程序标记语言或者XAML(读作“zammel”)中的XML标记词汇来封装你的应用程序。或者,通过使用特殊的基于XAML的结构,你能把你应用程序的某部分编译进程序集中并通过XAML来把它调入执行。你甚至能写下C#代码并把它嵌入到你的XAML定义中,或者把你的C#代码放进代码后置文件中,以便稍后执行它。
备注:你不能找到比Charles Petzold的最新著作:“应用程序 = 代码 + 标记”(“Application = Code + Markup”,2006年微软印刷)更好的在XAML和WPF方面的专著了。假如你对XAML论题的详细细节感兴趣的话,强烈建议你重新看看本书的第19章。
做下面的这个实验其实只是为了好玩。在你的系统中创建一个新的文本文件,把它命名为Button.xaml。把列表16-1中的代码输入到该文件中并进行保存,然后双击该文件。因为你必须安装.NET 3.0组件才能创建工作流应用程序,因此其实你也已经完成了对.xaml类型文件的注册工作。Windows知道把XAML文件加载到你的Web浏览器并显示它。尽管它仅仅只有一个按钮,但这是一个完整的WPF应用程序,虽然它很简单。图16-1显示了使用Window XP中的IE 7.0来展示该按钮的输出效果。
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Margin="36" Foreground="Blue" FontSize="36pt">
Hello,World!
</Button>
列表16-1 展示一个按钮的基于XAML的应用程序示例
图16-1 正在运行的基于XAML的按钮
WF团队也把这些理念一起融入到了WF中。虽然WF的XML遵循XAML命名空间的约定,但包含和WF相关的XML的文件名通常都使用.xoml文件扩展名来进行命名。这种做法能让自动化工具把该文件解释为一个工作流文件而不是一个外观(presentation)文件。事实上,我们将在本章中使用的一个工具,它叫工作流编译器(wfc.exe),在创建基于XAML的工作流时它要求使用.xoml类型的文件。
尽管列表16-1不是对工作流的声明,但要仔细看看你看到的XML。注意该XML元素的名称和.NET WPF类中支持的名称是相同的,在本例子中它是Button。还值得一提的是该按钮的属性将由XAML文件进行解释,它们是:FontSize,Margin和Foreground。通过改变这些属性或者添加其它属性,我们能非常容易地修改该按钮的特性。
基于工作流的XAML文件也有同样的特点。XML元素的名称代表了像CodeActivity或者IfElseActivity之类的活动类型。和你可能期望的一样,每一个元素能够包含它们的属性以及它们的值。至于工作流的结构,组合活动将会有子XML元素,而基本活动则没有。
声明命名空间与命名空间组织
XML通常对命名空间非常敏感,XAML也不例外。有几个命名空间是关键的,这些包括和工作流相关的命名空间和与.NET自身相联系的命名空间。
你的XAML文件必须包括的主要的命名空间是http://schemas.microsoft.com/winfx/2006/xaml,它通常使用x前缀。在XML文件中,命名空间的声明看起来和下面这些相似:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
基于工作流的XAML文件也必须包括工作流的命名空间:http://schemas.microsoft.com/winfx/2006/xaml/workflow。如果有命名空间的前缀的话,约定它为wf,但在基于工作流的XAML文件中你通常为工作流的命名空间指定默认的命名空间(也就是省略它的前缀):
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/workflow
它是一个罕见的工作流,因为它不需要访问.NET运行时,但这些是怎样在XML文件中做到的呢?哦,其实该文件并不调用任何和.NET运行时相关的东西——工作流运行时会为你做这些。该XML文件仅仅需要识别出它需要的.NET公共语言运行时(CLR)的组件,这些东西要么由.NET本身来提供,要么是你用基于XML的工作流所包含的自定义对象。为了识别出这些行为,可使用一个特定的句法规则来创建一个XML命名空间。
XAML将使用的CLR命名空间可通过使用两个关键字来创建:clr-namespace和assembly=。例如,如果你想在你的基于XAML的工作流中使用Console.WriteLine,你就需要为.NET的命名空间System创建如下的一个命名空间:
xmlns:sys="clr-namespace:System;assembly=System"
但是当你在基于XAML的工作流中创建你想使用的程序集时,仅仅创建程序集,为它指定命名空间,然后在工作流中包含它们是不够的。工作流运行时将加载你指定的程序集,但是它需要使用一个附加的特性:XmlnsDefinition特性,它自动加载对象并执行。
例如,考虑你想在你的基于XAML的工作流中使用下面这个活动:
{
}
假设该类在MyAssembly程序集的MyNamespace命名空间中,则把它引进基于XAML的工作流中去的代码就应该和列表16-2中展示的一样。
列表 16-2 使用XmlnsDefinition特性的例子
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
[assembly:XmlnsDefinition("urn:MyXmlNamespace","MyNamespace")]
namespace MyNamespace
{
public class MyActivity : Activity
{
// Not a very active activity!
}
}
引用这个类的XML如下:
xmlns:ns0="urn:MyXmlNamespace"
命名空间使用的字符串并不重要,这也许令人惊奇。重要的是在声明命名空间的时候,在XmlnsDefinition中声明的命名空间字符串要和在XML文件中使用的命名空间字符串相同。在XML中使用的命名空间必须是唯一的。也就是说,它们必须和XML文件中已经使用的现有的命名空间有所不同。我们将在接下来的一节对这进行更多的体会。
备注:假如你对XML命名空间比较生疏,则下面的网址或许对你有所帮助:http://msdn.microsoft.com/XML/Understanding/Fundamentals/default.aspx?pull=/library/en-us/dnxml/html/xml_namespaces.asp。
创建并执行基于XAML的工作流
在XML文件中定义的工作流能以两种方式执行:通过工作流运行时直接执行或者编译为程序集。对于直接执行XML文件中包含的工作流来说,首先把XML加载进XmlTextReader后,只需简单地调用使用XmlTextReader作为参数的工作流运行时的CreateWorkflow方法即可去执行该工作流。编译XML文件涉及到工作流编译器wfc.exe的使用。我们通过使用你在图16-2中看到的这个简单的工作流来看看这两种情形。
图16-2 基于XAML的工作流定义实验所用的简单工作流
创建一个直接执行XML的新工作流应用程序
1.下载本章源代码,打开DirectXml-Workflow目录中的解决方案。
2.在我们对Program.cs文件进行修改以便执行我们的工作流前,我们先创建该工作流本身。在Visual Studio的解决方案资源管理器中右键单击DirectXmlWorkflow项目,依次选择“添加”、“新建项”。当“新建项”对话框出现后,从列表中选择“XML 文件”并把它命名为“Workflow1.xml”。最后点击“添加”。
备注:Visual Studio将创建一个扩展名为.xml的文件。这时还不能把它改为.xoml,其原因是让该XML文件能在工作流视图设计器中进行编辑。.xoml文件扩展名是一个约定,对于WF工作流运行时来说这不是必须的,因此我们实际上只是把Visual Studio作为一个简单的XML编辑器来使用(其实这里可使用任何XML编辑器)。当应用程序执行时要确保该XML文件已被复制到该应用程序的执行路径下以便代码能找到该文件。
3.在解决方案资源管理器中选中该Workflow1.xml文件以便在属性面板中激活它的文件属性。
4.修改Workflow1.xml文件的“复制到输出目录”属性选项,把它从“不复制”修改为“如果较新则复制”。
5.添加下面的XML内容到该文件中,然后进行保存。
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<SequenceActivity x:Name="sequenceActivity1">
<DelayActivity TimeoutDuration="00:00:05" x:Name="delayActivity1" />
</SequenceActivity>
</SequentialWorkflowActivity>
6.打开Program.cs文件进行编辑。在已存在的using语句末尾添加下面的using语句。
7.查看代码,找到下面这行代码:
8.在你上一步骤找到的代码下面添加下面的代码,以便能去调用你刚才创建的基于XAML的工作流。
XmlTextReader rdr = new XmlTextReader("Workflow1.xml");
9.在你刚添加的代码下添加如下这些创建工作流实例的代码:
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(rdr);
// Start the workflow instance.
instance.Start();
10.编译该解决方案,纠正任何编译错误。
11.按下Ctrl+F5或者F5键执行该应用程序,你将看到下面的执行结果。
你能以这种方式创建一些相当复杂的工作流。但是,它只局限于以下几个方面。首先,尽管有在被调用的工作流内部执行C#代码的机制,但如果没特别要求的话,最好还是创建自定义的程序集来容纳你想执行的代码。其次,如果没有首先创建一个自定义的根活动(root activity)来接受你输入的参数的话,你就不能把参数传到该工作流中。对于这些限制,或许将来的某些时候会有所改变,但对于现在来说就只能这样。在本章的晚些时候我们将创建一个自定义的程序集来进行演示。
但是,有一个中间的步骤是把你的基于XAML的工作流编译进一个程序集中,以便你能进行引用并执行。为了编译该基于XAML的XML文件,我们将使用工作流的编译器:wfc.exe。
备注:想了解wfc.exe的更多知识的话,可看看:msdn2.microsoft.com/en-us/library/ms734733.aspx。
创建执行一个对XML进行了编译的新工作流应用程序
1.下载本章源代码,打开CompiledXmlWorkflow目录中的解决方案。
2.和前一个示例中的第2个步骤一样,添加一个新的Workflow1.xml文件。因为你将使用工作流编译器来编译该工作流,因此你不需要改变该文件的编译器设置(也就是说不需要前一节的步骤3和步骤4)。
3.把下面的XML添加到Workflow1.xml文件中并进行保存:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<SequenceActivity x:Name="sequenceActivity1">
<DelayActivity TimeoutDuration="00:00:05" x:Name="delayActivity1" />
</SequenceActivity>
</SequentialWorkflowActivity>
尽管这些可能看起来和你在前一个示例中添加的XML相似,但有一个细微的差别:x:Class属性。工作流编译器需要它,因为当编译工作流时编译器将需要这个新类的名称。
4.在解决方案资源管理器中把该文件的扩展名从.xml修改为.xoml。假如Visual Studio发出一个关于修改文件类型的警告提示的话,忽略它并尽情点击“是”。wfc.exe工具不接受以.xml作为扩展名的XML文件,它们必须使用.xoml作为文件扩展名。从“文件”菜单中选择“保存 Workflow1.xoml”或者按下Ctrl+S来保存该文件。
5.在Windows中打开命令提示符cmd窗口。
6.在命令提示符中输入:cd \Workflow\Chapter16\CompiledXmlWorkflow\CompiledXmlWorkflow,然后按下回车键改变相对目录。在该目录中将能直接访问到该Workflow1.xoml文件。
7.在命令提示符中输入"C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\Wfc.exe" workflow1.xoml,然后按下回车键。当然,这是假想Windows SDK是安装到你C盘的Program Files目录中的。假如你把Windows SDK安装到了其它地方,则需要修改上面的目录。
8.工作流编译器应该执行并无错误发生。当它的编译过程完成后,它会生成一个动态链接库workflow1.dll,它的目录位置和Workflow1.xoml所在的目录位置是一样的。你现在需要引用这个动态链接库,因为它包含了你在第3步创建的XML文件中定义的工作流。为此,在解决方案资源管理器中的CompiledXmlWorkflow项目上点击右键,然后选择“添加引用”。当“添加引用”对话框弹出后,选择“浏览”选项卡,然后从列表中选中workflow1.dll,最后点击“确定”。
9.该工作流现在就完成了,我们现在需要完成我们的主应用程序。打开Program.cs文件进行编辑,查看该文件的源代码,找到下面这行代码:
10.在上面这行代码下添加如下这些代码:
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(Workflow1));
// Start the workflow instance.
instance.Start();
11.编译本解决方案,纠正任何出现的编译错误。
12.按下Ctrl+F5或者F5执行该应用程序,你将看到如下的执行结果。
你也可以创建一个代码后置文件并把包含的代码编译进你的工作流程序集中,尽管本示例我们没有使用它。按照约定,假如该XML文件使用.xoml作为文件扩展名,该代码后置文件就使用.xoml.cs。wfc.exe工具能接受一系列的.xoml文件以及与它们相关联的.xoml.cs文件,它将把所有这些文件都编译进一个单独的工作流程序集中,前提是编译过程中没有错误。
虽然从表面上看这是一个小细节,但是这个细节将妨碍你进行工作流的编译。第一个示例中的工作流和第二个示例中的工作流之间的唯一差异是要向标记中附加x:Class属性。缺少x:Class属性的基于XAML的工作流对于直接执行来说只能作为候选。仅仅是添加x:Class属性就意味着你必须使用wfc.exe来对基于XAML的工作流进行编译。假如你试图直接执行你的基于XML的工作流的话,其结果是将产生一个WorkflowValidationFailedException异常,这是最可能出现的问题。
编译你的基于XAML的工作流的实例中最有意义的一件事发生在你需要把初始化的参数传入到你的工作流的时候。直接执行基于XAML的工作流不能够接受初始化的参数。为了演示这些,我们完全回到第一章“WF简介”中,然后重新创建一个邮政编码验证的示例应用程序。但是,我们将在XML中宿主该工作流而不是直接用C#代码的方式。我们将使用一个代码后置文件,因为我们还有条件要去进行判定,还有代码要去执行。
备注:你能直接使用x:Code元素来把代码直接放进XML的标记中。假如你对这些感兴趣的话,可看看msdn2.microsoft.com/en-gb/library/ms750494.aspx。
创建一个编译了XML的接受初始化参数的新工作流应用程序
1.在已经下载的本章源代码中打开PCodeXaml目录中的解决方案。
2.和前面的步骤一样,添加一个新的Workflow1.xml文件。你不需要改变该文件的编译器设置。事实上,一旦基于XAML的工作流完成并编译后,我们将从项目中移除这个文件(也包括它的代码后置文件),以便防止疏忽而导致编译器产生警告信息。
3.向Workflow1.xml文件中添加下面的XML内容并保存它:
<IfElseActivity x:Name="ifElseActivity1">
<IfElseBranchActivity x:Name="ifElseBranchActivity1">
<IfElseBranchActivity.Condition>
<CodeCondition Condition="EvaluatePostalCode" />
</IfElseBranchActivity.Condition>
<CodeActivity x:Name="codeActivity1" ExecuteCode="PostalCodeValid" />
</IfElseBranchActivity>
<IfElseBranchActivity x:Name="ifElseBranchActivity2">
<CodeActivity x:Name="codeActivity2" ExecuteCode="PostalCodeInvalid" />
</IfElseBranchActivity>
</IfElseActivity>
</SequentialWorkflowActivity>
4.在解决方案资源管理器中把Workflow1.xml的文件扩展名从.xml修改为.xoml并保存该文件。
5.接下来添加代码后置文件。在解决方案资源管理器中的PCodeXaml项目的名称上单击鼠标右键,选择“添加”,然后选择“类”。在“名称”中输入Workflow1.xoml.cs,最后点击“添加”。
6.向文件中添加下面的代码,完全替换Visual Studio为你添加的内容。然后保存这个你刚刚创建的文件。
备注:这些代码应该看起来比较熟悉。我是完全从第1章中的PCodeFlow应用程序和工作流中的原始代码中复制粘贴过来的。
7.选择“文件”菜单中的“全部保存”,或者按下Ctrl+Shift+S。
8.打开一个命令提示符窗口,在命令提示符中输入:cd \Workflow\Chapter16\PCodeXaml\PCodeXaml,然后按下Enter改变当前的工作目录。
9.在命令提示符中输入:"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\Wfc.exe" workflow1.xoml workflow1.xoml.cs,然后按下回车键。
10.工作流编译器会把XML和C#文件都汇集到一起。当它完成编译后,它会再次生成一个名称为workflow1.dll的动态链接库,你现在应对它进行引用。在解决方案资源管理器中的PCodeXaml项目的名称上单击右键,然后选择“添加引用”。在“添加引用”对话框弹出后,选择“浏览”选项卡,从列表中选中workflow1.dll,最后点击“确定”。
11.回到主应用程序,选中Program.cs文件进行修改。在该文件中找到下面这行代码:
12.在刚刚找到的代码下添加如下这些代码:
Dictionary<string, object> parms = new Dictionary<string, object>();
parms.Add("PostalCode", args.Length > 0 ? args[0] : "");
// Create the workflow instance.
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(Workflow1), parms);
// Start the workflow instance.
instance.Start();
13.这个项目有个小问题。你引用了workflow1.dll程序集,但是在代码后置文件中也存在有创建该Workflow1类的代码。这就造成了存在两个名称都为Workflow1的类实例的冲突。因为你想在已编译的程序集中进行类的定义,因此需要在解决方案资源管理器中右键点击Workflow1.xoml和Workflow.xoml.cs文件,然后选择“从项目中排除”来移除它们。
14.编译该解决方案,纠正任何可能出现的错误。
15.为了测试该应用程序,我们将使用命令提示符窗口。把当前目录更改到Debug目录(假如你编译应用程序的模式使用的是Release模式的话,则要更改到Release)。为此,在命令提示符中输入:cd bin\Debug然后按下Enter回车键(假如你以Release模式编译的话需要把Debug改为Release)。
16.在命令提示符中,输入PCodeXaml 12345并按下回车键。你会看到下面这些应用程序的输出结果:
17。为了试试让程序输出否定的结果,在命令提示符中输入PCodeXaml 1234x并按下Enter回车键。你会得到下面的输出结果:
在本章我们将创建最后一个示例,它涉及到一个让我们能对XmlnsDefinition进行演练的自定义的类
创建一个新的带有自定义活动的基于XAML的工作流应用程序
1.在已经下载的本章源代码中打开XmlnsDefFlow目录中的解决方案。
2.添加一个新的名称为Workflow1.xml的文件。但是你不需要更改该文件的编译器设置或者对它进行重新命名。
3.在Workflow1.xml文件中添加下面的XML内容,然后进行保存:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"
xmlns:custom="urn:PrintMessage">
<custom:PrintMessageActivity x:Name="printMessageActivity1"
custom:Message="Hello from the custom assembly!"/>
</SequentialWorkflowActivity>
4.在解决方案资源管理器中把该文件的扩展名从.xml改为.xoml,然后忽略弹出的警告信息,最后保存该文件。工作流现在就完成了,但注意要在XML标记中引用PrintMessageActivity,它是你需要去创建的一个新的自定义活动。为此,在解决方案资源管理器中添加一个新项目,从Workflow项目类型中选择“工作流程活动库”,在项目的名称中输入XmlnsDefLib。
5.Visual Studio会创建一个名称为Activity1的工作流活动。在解决方案资源管理器中把Activity1.cs文件的名称重命名为PrintMessageActivity.cs。
6.在代码编辑视图中打开该活动的源文件准备进行编辑。
7.该活动当前派生于SequenceActivity。现需要修改该活动派生的基类为Activity。该类定义应如下所示:
8.在构造器的下面添加如下这些代码:
Execute(ActivityExecutionContext executionContext)
{
// Print message to screen
Console.WriteLine(this.Message);
return ActivityExecutionStatus.Closed;
}
public static DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(System.String),
typeof(XmlnsDefLib.PrintMessageActivity));
public string Message
{
get
{
return ((System.String)(base.GetValue(
XmlnsDefLib.PrintMessageActivity.MessageProperty)));
}
set
{
base.SetValue(
XmlnsDefLib.PrintMessageActivity.MessageProperty, value);
}
}
9.现在添加XmlnsDefinition特性。就在命名空间声明的前面,插入这些代码:
备注:假如你正在一个大应用程序中使用该活动,或者要分发给客户或者其它外部用户使用,可以使用包含你公司名称的命名空间URI、产品组、项目或者其它典型的唯一的值来避免命名空间的歧义。当使用XML工作时,这通常被认为是最佳实践。
10.编译XmlnsDefLib项目以便生成一个能被工作流编译器引用的DLL。
11.尽管我们将执行的是一个基于XAML的工作流,但工作流运行时仍然需要去访问你刚刚创建的PrintMessage活动。因此,需要为XmlnsDefFlow项目添加对XmlnsDefLib的项目级引用。
12.和前面的示例应用程序一样,打开一个命令提示符窗口。
13.在命令提示符中输入cd \Workflow\Chapter16\XmlnsDefFlow\XmlnsDefFlow,然后按下Enter回车键来改变当前的工作目录。Workflow1.xoml文件现在就可在该目录下直接进行访问。
14.在目录提示符中输入"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\Wfc.exe" workflow1.xoml /r:..\XmlnsDefLib\bin\Debug\XmlnsDefLib.dll,然后按下Enter回车键。和前面一样,如果你把Windows SDK安装到了其它地方,你需要使用你所安装的目录去执行wfc.exe,并且,假如你以Release的方式来编译XmlnsDefLib项目的话,你也需要使用Release来替换Debug。
15.工作流编译器执行时应该不会出现错误。它会在和Workflow1.xoml的相同目录下生成一个动态链接库workflow1.dll。你现在需要在你的主应用程序中引用该库,因为它包含了你在第三步创建的XML文件中所定义的工作流。
16.随着工作流的完成,我们要回到主应用程序中。在解决方案资源管理器中选中Program.cx文件,假如它没有打开的话需要打开它。查看该文件,找到下面这行代码:
17.在你刚刚找到的代码下添加如下这些代码:
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(Workflow1));
// Start the workflow instance.
instance.Start();
18.编译本解决方案,纠正任何出现的编译错误。
19.按下Ctrl+F5或者F5执行该应用程序。你将看到如下的这些输出结果。
备注:即使我们这里创建了四个应用程序,我也没有完全清楚地说明声明式工作流定义方面的所有相关内容。假如你对这些感兴趣的话,你可在MSDN上找到大量的论文:msdn.microsoft.com/msdnmag/issues/06/01/windowsworkflowfoundation/。
本章源代码:里面包含本章的练习项目和完整代码
下一篇:WF从入门到精通(第十七章):关联及本地主机通信