65.9K
CodeProject 正在变化。 阅读更多。
Home

保存和加载 Workflow Foundation 4 活动

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (3投票s)

2012年7月27日

CPOL

3分钟阅读

viewsIcon

34340

downloadIcon

666

成功保存和加载 WF4 活动所需的所有要素。

引言

在我的一个项目中,我需要用户可编辑的工作流。 本文的重点不是如何让用户编辑工作流,即如何托管设计器。 本文的重点是看似简单的任务:加载一个 XAML 文件,该文件包含由 Visual Studio 设计器创建格式的 WF4 活动定义,并在将活动保存回 XAML 时创建该格式。   

这比预期的要困难,因此我考虑分享我的结果。 请注意,这是我的第一篇文章,因此欢迎提出建设性的批评。

实现

从 XAML 加载活动以及将活动保存到 XAML 的最简单方法是使用类ActivityXamlServices。 查看文档没有显示 Save 方法,但有几个 Load 方法,所以让我们从后者开始。 

从 XAML 加载活动

加载活动实际上非常简单

var activity = ActivityXamlServices.Load(reader);

reader 是一个 TextReader,例如 StreamReaderStringReader 的实例。

据我所知,当加载由 VS 设计器创建或稍后我们将要实现的 Save 方法创建的 XAML 时,这总是返回 DynamicActivity 类型的实例。

对于加载和保存的往返过程,拥有一个 DynamicActivity 非常重要,更通用的 Activity 对我们没有帮助。 因此,我尝试将 Load 的结果强制转换为 DynamicActivity,如果失败则抛出异常

var activity = ActivityXamlServices.Load(reader) as DynamicActivity;
if (activity == null)
    throw new InvalidDataException("The XAML doesn't represent a DynamicActivity.");

将 DynamicActivity 保存到 XAML

为了获得与 VS 设计器创建的相同的 XAML,必须拥有 ActivityBuilder 的实例。 根据您的操作方式,保存普通的 Activity 会引发异常或导致 XAML 采用与我们需要的格式不同的格式。 

从 DynamicActivity 创建 ActivityBuilder

因此,为了将我们的活动保存到 XAML,我们需要一个 ActivityBuilder,但我们所拥有的是一个 DynamicActivity。 从 DynamicActivityActivityBuilder 的方式实际上非常简单,因为这两个类都具有非常相似的布局。 我们只是新建一个 ActivityBuilder 并将它的属性赋值为我们的 DynamicActivity 的值

var activityBuilder = new ActivityBuilder();
 
activityBuilder.Implementation = dynamicActivity.Implementation != null ?
                                     dynamicActivity.Implementation() : null;
activityBuilder.Name = dynamicActivity.Name;
 
foreach (var item in dynamicActivity.Attributes)
    activityBuilder.Attributes.Add(item);
 
foreach (var item in dynamicActivity.Constraints)
    activityBuilder.Constraints.Add(item);
 
foreach (var item in dynamicActivity.Properties)
{
    var property = new DynamicActivityProperty
                   {
                       Name = item.Name,
                       Type = item.Type,
                       Value = null
                   };

    foreach (var attribute in item.Attributes)
        property.Attributes.Add(attribute);
 
    activityBuilder.Properties.Add(property);
}
 
VisualBasic.SetSettings(activityBuilder, VisualBasic.GetSettings(dynamicActivity));
 
return activityBuilder;  

此代码是 Winfried Lötzsch 关于重新托管 WF 设计器的优秀文章中介绍的代码的扩展版本。 在大多数情况下,这很简单。

但是我的这个版本的代码有两个重要的区别。 第一个区别是这一行

VisualBasic.SetSettings(activityBuilder, VisualBasic.GetSettings(dynamicActivity));  

这一行有两个目的

  1. 它在 XAML 文件中创建一个非常重要的魔法字符串
    <mva:VisualBasic.Settings>
        Assembly references and imported namespaces for internal implementation
    </mva:VisualBasic.Settings>  

    没有这个,我们的活动所组成的活动无法访问我们工作流的输入和输出参数。

  2. 它将所有命名空间导入从我们的 DynamicActivity 复制到新的 ActivityBuilder
    如果省略此项,我们稍后将用于将活动保存到 XAML 的例程会尝试从活动中使用的类型推断所需的命名空间。 它通常在这项任务上做得很好,但在工作流表达式中使用的扩展方法方面会失败。
    这会导致在稍后加载和调用 XAML 时出现编译器错误。

第二个区别是我如何复制属性

foreach (var item in dynamicActivity.Properties)
{
    var property = new DynamicActivityProperty
                   {
                       Name = item.Name,
                       Type = item.Type,
                       Value = null
                   };

    foreach (var attribute in item.Attributes)
        property.Attributes.Add(attribute);
 
    activityBuilder.Properties.Add(property);
} 

我不仅仅是从 DynamicActivity 添加实例,而是创建 DynamicActivityProperty 的新实例。 在大多数情况下,我将旧实例的值分配给新实例 - 但有一个重要的区别:我将 Value 分配为 null

当从 XAML 加载活动时,Valuenull。 然后,当执行活动时,Value 将由 WF 引擎分配。 现在的问题是,分配的 Value 将导致生成 XAML 的根标签上的属性。 然后,当尝试使用上述方法加载 XAML 时,这些属性会导致异常。

就是这样,现在我们有了一个可以保存的 ActivityBuilder 实例。

保存 ActivityBuilder

保存不像加载那么简单,但仍然不太难

var stringBuilder = new StringBuilder();
var xamlXmlWriter = new XamlXmlWriter(new StringWriter(stringBuilder), new XamlSchemaContext());
var builderWriter = ActivityXamlServices.CreateBuilderWriter(xamlXmlWriter);

XamlServices.Save(builderWriter, activityBuilder);
var xaml = stringBuilder.ToString(); 

结论

一旦你知道要注意什么,它实际上非常简单。 附加的 .cs 文件将所有这些功能封装在一个很好的小助手类中。

© . All rights reserved.