首页 > 编程开发 > Objective-C编程 >
-
WF从入门到精通之声明式工作流
学习完本章,你将掌握:
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来展示该按钮的输出效果。
<?xmlversion="1.0"?>
<Buttonxmlns="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的工作流中使用下面这个活动:
publicclassMyActivity:Activity
{
}
假设该类在MyAssembly程序集的MyNamespace命名空间中,则把它引进基于XAML的工作流中去的代码就应该和列表16-2中展示的一样。
列表 16-2使用XmlnsDefinition特性的例子
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.Workflow.ComponentModel;
usingSystem.Workflow.Activities;
[assembly:XmlnsDefinition("urn:MyXmlNamespace","MyNamespace")]
namespaceMyNamespace
{
publicclassMyActivity:Activity
{
//Notaveryactiveactivity!
}
}
引用这个类的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内容到该文件中,然后进行保存。
<SequentialWorkflowActivityx:Name="Workflow1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<SequenceActivityx:Name="sequenceActivity1">
<DelayActivityTimeoutDuration="00:00:05"x:Name="delayActivity1"/>
</SequenceActivity>
</SequentialWorkflowActivity>
6.打开Program.cs文件进行编辑。在已存在的using语句末尾添加下面的using语句。
usingSystem.Xml;
7.查看代码,找到下面这行代码:
Console.WriteLine("Waitingforworkflowcompletion.");
8.在你上一步骤找到的代码下面添加下面的代码,以便能去调用你刚才创建的基于XAML的工作流。
//LoadtheXAML-basedworkflow
XmlTextReaderrdr=newXmlTextReader("Workflow1.xml");
9.在你刚添加的代码下添加如下这些创建工作流实例的代码:
//Createtheworkflowinstance.
WorkflowInstanceinstance=
workflowRuntime.CreateWorkflow(rdr);
//Starttheworkflowinstance.
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文件中并进行保存:
<SequentialWorkflowActivityx:Name="Workflow1"x:Class="Workflow1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<SequenceActivityx:Name="sequenceActivity1">
<DelayActivityTimeoutDuration="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 WorkflowChapter16CompiledXmlWorkflowCompiledXmlWorkflow,然后按下回车键改变相对目录。在该目录中将能直接访问到该Workflow1.xoml文件。
7.在命令提示符中输入"C:Program FilesMicrosoft SDKsWindowsv6.0AbinWfc.exe" workflow1.xoml,然后按下回车键。当然,这是假想Windows SDK是安装到你C盘的Program Files目录中的。假如你把Windows SDK安装到了其它地方,则需要修改上面的目录。
8.工作流编译器应该执行并无错误发生。当它的编译过程完成后,它会生成一个动态链接库workflow1.dll,它的目录位置和Workflow1.xoml所在的目录位置是一样的。你现在需要引用这个动态链接库,因为它包含了你在第3步创建的XML文件中定义的工作流。为此,在解决方案资源管理器中的CompiledXmlWorkflow项目上点击右键,然后选择“添加引用”。当“添加引用”对话框弹出后,选择“浏览”选项卡,然后从列表中选中workflow1.dll,最后点击“确定”。
9.该工作流现在就完成了,我们现在需要完成我们的主应用程序。打开Program.cs文件进行编辑,查看该文件的源代码,找到下面这行代码:
Console.WriteLine("Waitingforworkflowcompletion.");
10.在上面这行代码下添加如下这些代码:
//Createtheworkflowinstance.
WorkflowInstanceinstance=
workflowRuntime.CreateWorkflow(typeof(Workflow1));
//Starttheworkflowinstance.
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异常,这是最可能出现的问题。
声明式工作流——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来展示该按钮的输出效果。
<?xmlversion="1.0"?>
<Buttonxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"Margin="36"Foreground="Blue"FontSize="36pt">
Hello,World!
</Button>