VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Objective-C编程 >
  • c#教程之WF中加载工作流模式

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

在 WF 中加载工作流模型目录

  模型的 CLR 类型

  将 XAML 用于工作流

  工作流加载

  比较建模方法

  总结

  可通过多种方式对 Windows® Workflow Foundation (WF) 中的工作流建模。开发人员对活动的层次结构进行建模,这些活动定义了要在工作流中执行的控制流和程序语句。(有关 Windows WF 声明性特性的详细信息,请参阅 Don Box 和 Dharma Shukla 撰写的“WinFX 工作流:使用 Windows Workflow Foundation 的声明性模型简化开发”,网址为:msdn2.microsoft.com/magazine/cc163661.aspx)。

  工作流模型或定义是工具将创建和修改的产物,并且为工作流的运行时实例提供模板。通过各种方法对工作流进行建模这一灵活性是一项强大的功能,适用于目标为各种用户群的建模工具。但是,应使用哪种格式来对工作流进行建模呢?答案依旧不是那么简单。在本专栏中,我将介绍如何应用两种常见的模型以及运行时处理它们的方式。此外,我还将介绍运行时如何为 Microsoft® .NET Framework 类型和可扩展应用程序标记语言 (XAML) 创建新的工作流实例,以及可如何自定义该过程以适应您的需求。

  模型的 CLR 类型

  对工作流进行建模的一种方法是使用以 .NET 为目标的代码。创建一个将代表工作流的类,并在该类的构造函数中构建活动的层次结构。通常情况下,所编写的类将从一些复合活动(如 SequenceActivity)派生,然后在构造函数中将活动添加为子类。图 1 显示了将 SequenceActivity 用作基类以及将简单的 WriteLine 活动用作子类的一个工作流模型。

 

在 WF 中加载工作流模型Figure1使用 C# 定义工作流

public class WorkflowInCode : SequenceActivity
{
  public WorkflowInCode()
  {
    WriteLineActivity writeLine1 = new WriteLineActivity();
    writeLine1.Name = "writeLine1";
    writeLine1.OutputText = "Hello";
    WriteLineActivity writeLine2 = new WriteLineActivity();
    writeLine2.Name = "writeLine2";
    writeLine2.OutputText = "MSDN Reader";
    this.Activities.Add(writeLine1);
    this.Activities.Add(writeLine2);
  }
}

  该模型的机制和工具使用类似于 Windows 窗体的设计方式。使用一个可视设计器来对工作流层次结构进行建模,即将活动拖放到模型中;而设计器在后台生成代码来重新创建结构。它是 Windows WF 的 Visual Studio® 工具所支持的一个主要模型。工作流的构造函数调用 InitializeComponent 方法(它位于可视设计图画所生成的设计器文件中)。

  图 1 建立了一个非常简单的活动层次结构模型;除创建层次结构以外,我创建的类并无其他代码。也可使用该 .NET 建模机制对活动绑定、事件处理程序以及更为复杂的活动交互进行建模。例如,如果工作流包含一个 ReadLine 活动和一个 WriteLine 活动,可绑定它们之间的属性,也可为活动引起的事件创建事件处理程序。图 2 显示了包括这些更改的更新工作流定义。

 

在 WF 中加载工作流模型Figure2更复杂的工作流代码模型

public class WorkflowInCode : SequenceActivity
{
 public WorkflowInCode()
 {
  ReadLineActivity readLine1 = new ReadLineActivity();
  readLine1.Name = "readLine1";
  readLine1.Invoked += new EventHandler(readLine1_Invoked);
  WriteLineActivity writeLine1 = new WriteLineActivity();
  ActivityBind textBind = new ActivityBind("readLine1", "InputText");
  writeLine1.Name = "writeLine1";
  writeLine1.SetBinding(WriteLineActivity.OutputTextProperty,
    textBind);
  this.Activities.Add(readLine1);
  this.Activities.Add(writeLine1);
 }
 void readLine1_Invoked(object sender, EventArgs e)
 {
  ReadLineActivity readLine = sender as ReadLineActivity;
  Console.WriteLine("Event handler in workflow read "{0}"
    from readline.",
   readLine.InputText);
 }
}

  在图 2 显示的代码中,工作流类包含代表工作流部件的代码,但这个部件与层次不相关。在编译工作流定义时,ReadLine 活动中 Invoked 事件的事件处理程序也会包括在程序集中。

  使用代码对工作流进行建模时,将模型编译到 .NET 程序集中,并在运行时实例化 CLR 类来创建工作流层次结构。如果要启动以此方式建模的工作流,需请求运行时根据此类为您创建一个实例,如下所示:

 

WorkflowRuntime runtime = new WorkflowRuntime();
WorkflowInstance instance = runtime.CreateWorkflow(typeof(WorkflowInCode));
instance.Start();

  由于将把所有信息编译到 CLR 类中,因此在加载类型时,运行时可轻松解析对事件处理程序和工作流定义中其他代码的引用。稍后,我将介绍如何实际加载这些类型以及使用此建模技术替代 XML 的意义。

  将 XAML 用于工作流

  更吸引人的工作流建模方式是使用 XAML(用于代表 CLR 对象层次结构的一种 XML 语言)。XAML 常常与 Windows Presentation Foundation (WPF) 相关,它在其中代表在屏幕中绘制的以下可视化对象的层次结构:窗口、按钮、菜单等等。在 Windows WF 中,XAML 代表形成工作流的各个活动的层次结构。以下 XML 表达了之前使用代码建模的那个简单工作流:

<SequenceActivity xmlns="http://schemas.microsoft.com/winfx/2006
             /xaml/workflow"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:msdn="clr-namespace:MSDN.CustomActivities;Assembly=
       CustomActivities">
 <msdn:WriteLineActivity x:Name="writeLine1" OutputText="Hello" />
 <msdn:WriteLineActivity x:Name="writeLine2" OutputText="MSDN Reader" />
</SequenceActivity>

  关于此 XAML 需要注意的第一件事是:由于使用 XML 来定义 CLR 类型的结构,因此我需要提供两者之间的一些映射。在构建层次结构时,WorkflowRuntime 必须能够使自定义 WriteLine 活动(以 XAML 表示)匹配某种 .NET 类型。

 

  在之前的 XML 中,XML 命名空间声明将 msdn 前缀映射为 CustomActivities 程序集中的 MSDN.CustomActivities CLR 命名空间。在处理和反序列化 XAML 以创建对象层次结构时,将使用此信息来解析 .NET 类型。在创建 .NET 对象时,XAML 元素中的属性将转换成其中的属性值,从而完全初始化对象。

  通过使用 XAML,还可管理活动绑定、关联事件处理程序以及执行更为复杂的关系或交互建模操作。如果使用“标记扩展”,则可绑定属性或事件处理程序。以下代码代表包含 ReadLine 和 WriteLine 活动以及其间存在一个绑定的工作流:

<SequenceActivity xmlns="http://schemas.microsoft.com/winfx/2006/
             xaml/workflow"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:msdn="clr-namespace:MSDN.CustomActivities;Assembly=
       CustomActivities">
 <msdn:ReadLineActivity x:Name="readLine1" InputText="" />
 <msdn:WriteLineActivity x:Name="writeLine2"
  OutputText="{ActivityBind readLine1 Path=InputText}" />
</SequenceActivity>

  WriteLine 活动的 OutputText 属性具有一个 ActivityBind 扩展。.在将工作流反序列化成对象时,它会将提供的文本变成一个 ActivityBind 类实例。

  使用 XAML 定义工作流时,可在编译时或运行时将 XML 转换成 CLR 活动层次结构。如果选择编译 XAML 工作流定义,需使用 WorkflowCompiler 类或 wfc.exe 命令行编译器,它们均允许您生成在 CLR 类中包含工作流定义的 .NET 程序集。还需向根活动添加 Class 属性以指出在编译工作流时要创建的类。

 

  编译 XAML 定义的结果与使用代码创建定义的结果完全相同:是一个可用于创建工作流的 .NET 类型。实际的区别在于工具创建模型的方式以及在运行时使用模型的方式。稍后我会简要介绍这两种情况中的权衡。

  如果在运行时构建层次结构,则会将 XAML 加载到 XmlReader 中,然后要求 WorkflowRuntime 根据 XAML 定义创建 WorkflowInstance。之前,我使用 WorkflowRuntime 类的 CreateWorkflow 方法根据其 CLR 类型创建了实例。该方法具有多个重载版本,包括接受包含工作流定义的 XmlReader 的版本。以下代码显示了这个简单却功能强大的更改,它将直接从 XAML 文件加载定义:

WorkflowInstance instance = runtime.CreateWorkflow(
XmlTextReader.Create("....WorkflowInXML.xml"));
instance.Start();

  正是 CreateWorkflow 将模型或定义转换成运行时可用的一个实例。稍后我会详细介绍这一转换,但首先我想详细阐述一下在 XAML 中创建工作流模型的相关信息。

  看一下示例 XAML 工作流定义,您会看到根 SequenceActivity 元素与 System.Workflow.Activities CLR 命名空间之间并无映射。此活动已成为 .NET Framework 3.0 的一部分,在此处用作层次结构中的根活动。

  此类模型的其中一个好处是模型本身不必与类型的一个解释或实现关联。由于已通过命名空间映射在此 XAML 模型中包括了 WriteLine 活动的 CLR 类型,因此已关联了活动类的具体实现。此外,如果具有不同版本的活动,可通过用于引用程序集的强名称针对工作流模型关联该活动的具体版本。我需要的是一种从 CLR 类型信息分离模型的抽象,并且它还要能将此标记反序列化成活动层次结构。

  使用 XML 命名空间和 CLR 命名空间之间的映射层来实现此分离。Windows WF 允许主机代码定义用于实现映射的程序集。在之前的示例中,XAML 中的 SequenceActivity 元素位于默认的 XML 命名空间中。在 XAML 的第一行中,默认命名空间为“http://schemas.microsoft.com/winfx/2006/xaml/workflow”。

 

  等式的另一边以某种方式将此 XML 命名空间映射为 CLR 命名空间,其实现方式为将自定义属性添加到在其中定义活动的程序集中。System.Workflow.Activities.dll 程序集包含一个属性,它将 XAML 中出现的 XML 命名空间映射为 CLR 命名空间 System.Workflow.Activities。我可以使用自定义活动实现相同的功能。我将以下属性添加到活动项目中的 assemblyinfo.cs 文件:

using System.Workflow.ComponentModel.Serialization;
[assembly:XmlnsDefinition("http://msdnmagazine/foundations/workflow",
 "MSDN.CustomActivities")]

  然后,我更改 XAML 文件中的 XML 命名空间以匹配此属性,如下所示:

xmlns:msdn="http://msdnmagazine/foundations/workflow"

  此时,XAML 文件中的 XML 命名空间与 CLR 程序集或命名空间并无直接关联。我还通过程序集中的 CLR 属性定义了 XML 到 CLR 命名空间的映射。当工作流运行时尝试反序列化 XAML 时,它需要了解哪些程序集将检查自定义属性。它使用 TypeProvider 类来实现此功能;此类实现 ITypeProvider 接口,并且是解析类型时所需程序集的容器。在示例中,我在主机应用程序中创建 TypeProvider 类的实例,将活动程序集添加到 TypeProvider,然后将 TypeProvider 作为服务添加到 WorkflowRuntime:

using (WorkflowRuntime runtime = new WorkflowRuntime())
{
 TypeProvider provider = new TypeProvider(runtime);
 provider.AddAssemblyReference("CustomActivities.dll");
 runtime.AddService(provider);
 //start workflow
 ...
}

  使用 XML 创建一个模型,然后通过使用我希望用于主机的程序集配置 TypeProvider,将到 CLR 类型的映射推迟到运行时。这样会提供一个灵活的模型,并且可控制运行时用于创建实例的 CLR 活动类型。反序列化工作流时,将用到 WorkflowMarkupSerializer 类,它使用 TypeProvider 将 XML 名称解析成 CLR 类型。图 3 显示了加载程序(接下来会介绍)WorkflowMarkupSerializer 和 TypeProvider 之间的交互。

 

在 WF 中加载工作流模型

  图 3使用 XAML 创建工作流

  工作流加载

  创建工作流定义(作为 CLR 类或作为 XAML 标记)后,运行时需要能够获取该定义并创建工作流实例。许多开发人员都认为是 WorkflowRuntime 负责加载工作流实例,因为它是他们用于创建实例的类。但是,WorkflowRuntime 是使用运行时服务来实际加载工作流的。基类 WorkflowLoaderService 定义工作流加载程序服务必须实现的抽象方法。以下是此类的定义:

public abstract class WorkflowLoaderService
{
 protected internal abstract Activity CreateInstance(Type workflowType);
 protected internal abstract Activity CreateInstance(XmlReader
  workflowDefinitionReader, XmlReader rulesReader);
}

  正如您所看到的,WorkflowLoaderService 取得包含工作流定义的 CLR 类型或 XmlReader,并根据它创建一个 Activity 层次结构。运行时还包含未向其加入其他加载程序服务时它将使用的 DefaultWorkflowLoaderService。在此默认加载程序中,当根据类型信息创建工作流时,服务仅会针对该类型调用 Activator.CreateInstance 并返回该对象。

  如果要求 DefaultWorkflowLoaderService 根据 XmlReader 创建工作流,它会尝试使用 WorkflowMarkupSerializer 类将 XAML 反序列化成活动层次结构。如果反序列化失败,将捕获所有验证错误并抛出异常。

  默认加载程序适用于多种情况(尤其是使用 XAML 或 .NET 工作流定义时),但有时您会希望创建一个自定义工作流加载程序来增强或替换默认行为。在创建自定义加载程序时,可从 WorkflowLoaderService 抽象类或 DefaultWorkflowLoaderService 派生,具体取决于您希望重用的功能数量。

 

  许多开发人员希望将自己的 XAML 工作流存储在数据库中。其途径是更新编写或部署工具以将 XAML 存储在数据库表中。存储 XAML 之后,可在主机应用程序内将 XAML 加载到 XmlReader 中,然后调用创建工作流,但自定义加载程序服务集中了该逻辑并使其可在主机间重用。

  在创建自定义工作流加载程序服务时,存在一些限制。首先,由于创建工作流方法和 WorkflowLoaderService 都仅有根据 CLR 类型或 XmlReader 创建工作流的方法,因此自定义加载程序必须能够从 CLR 类型或 XML 文件获取所需信息。出于安全考虑,运行时将验证加载程序服务并未创建与指定类型不符的其他工作流。假设我定义了一个接口,然后希望实现逻辑以便在将实现该接口的类型传到加载程序时,可针对该接口调用一个方法来获取工作流类型。从而得到更多的动态代码驱动的工作流。但是它不会起作用,因为传到创建工作流调用的类型并不匹配返回工作流的类型。

  因此会使 XmlReader 产生路由,从而具有更多灵活性。由于在加载程序创建工作流类型之前,运行时并不了解有关它的任何信息,因此工作流加载程序可在适当的时候解释 XML 并返回代表工作流的根活动。我通常的做法是创建一个 XML 文件格式,让它包含创建工作流所需的所有信息。例如,我将向您展示如何创建一个从 SQL Server® 数据库加载工作流的自定义加载程序服务。有关工作流的信息将存储在一个简单的 XML 文件中(可将此文件封装到 XmlReader 之内并传递给创建工作流方法)。图 4 显示了运行时、加载程序服务、数据库以及 WorkflowMarkupSerializer 之间的交互。

在 WF 中加载工作流模型

 

  图 4从数据库加载自定义工作流

  第一个决策是如何将工作流存储在数据库中。通常是将模型存储为列中的 XAML 文档。在这种情况下,第一个问题是将 XAML 存储为 SQL Server 2005 的一个 SQL 文本类型还是存储为一个 XML 类型。如果希望能够查询或更新数据库中的 XAML,可使用 XML 类型,但对于本示例而言,我仅需要一个存储 XAML 的地方,因此使用 ntext 数据类型。我还添加了一些元数据列和一个 ntext 列来存储工作流规则的序列化表示。大多数工作流都包含一些规则,如果仅控制复合活动执行(如 IfElseActivity),则自定义加载程序解决方案还应包括规则管理功能。

  我选择将整个工作流模型作为 XAML 文件存储在数据库中,但实际存在许多可选方案。一个简单的示例是将工作流片段作为 XAML 存储在数据库中,然后使用一组这样的片断来组成完整的工作流定义。这会创建 XML 格式的可重用工作流逻辑,与创建自定义复合活动(可编译成二进制格式)不同。另一种方法是将工作流定义存储为关系数据。例如,可先将工作流中的单个步骤组成一张表(定义基本活动),然后使用一张工作流表来将这些活动链接在一起。

  工作流的存储模型取决于管理模型及其不同部件的要求。在创建自定义加载程序时,您需要做的就是正确组合该数据,以创建代表模型化工作流的活动层次结构。

  下一步是确定从数据库引用工作流的 XML 文档的格式。我选择根据名称和版本号来选择工作流,并创建一个非常简单的 XML 模型。我将把以下示例的 XML 文档加载到 XmlReader 中,然后将其传递给创建工作流调用:

<Workflow xmlns="http://msdnmagazine/foundations/workflow" 
 Name="HelloWorld" Version="1.0" />

  可使用各种标识符来选择正确的工作流,但是我发现使用常见的名称和版本号可更加轻松地选择或标识工作流。

 

在 WF 中加载工作流模型Figure1使用 C# 定义工作流

public class WorkflowInCode : SequenceActivity
{
  public WorkflowInCode()
  {
    WriteLineActivity writeLine1 = new WriteLineActivity();
    writeLine1.Name = "writeLine1";
    writeLine1.OutputText = "Hello";
    WriteLineActivity writeLine2 = new WriteLineActivity();
    writeLine2.Name = "writeLine2";
    writeLine2.OutputText = "MSDN Reader";
    this.Activities.Add(writeLine1);
    this.Activities.Add(writeLine2);
  }
}

  该模型的机制和工具使用类似于 Windows 窗体的设计方式。使用一个可视设计器来对工作流层次结构进行建模,即将活动拖放到模型中;而设计器在后台生成代码来重新创建结构。它是 Windows WF 的 Visual Studio® 工具所支持的一个主要模型。工作流的构造函数调用 InitializeComponent 方法(它位于可视设计图画所生成的设计器文件中)。

  图 1 建立了一个非常简单的活动层次结构模型;除创建层次结构以外,我创建的类并无其他代码。也可使用该 .NET 建模机制对活动绑定、事件处理程序以及更为复杂的活动交互进行建模。例如,如果工作流包含一个 ReadLine 活动和一个 WriteLine 活动,可绑定它们之间的属性,也可为活动引起的事件创建事件处理程序。图 2 显示了包括这些更改的更新工作流定义


相关教程