保存和加载 Workflow Foundation 4 活动






4.71/5 (3投票s)
成功保存和加载 WF4 活动所需的所有要素。
引言
在我的一个项目中,我需要用户可编辑的工作流。 本文的重点不是如何让用户编辑工作流,即如何托管设计器。 本文的重点是看似简单的任务:加载一个 XAML 文件,该文件包含由 Visual Studio 设计器创建格式的 WF4 活动定义,并在将活动保存回 XAML 时创建该格式。
这比预期的要困难,因此我考虑分享我的结果。 请注意,这是我的第一篇文章,因此欢迎提出建设性的批评。
实现
从 XAML 加载活动以及将活动保存到 XAML 的最简单方法是使用类ActivityXamlServices
。 查看文档没有显示
Save
方法,但有几个 Load
方法,所以让我们从后者开始。
从 XAML 加载活动
加载活动实际上非常简单
var activity = ActivityXamlServices.Load(reader);
reader
是一个 TextReader
,例如 StreamReader
或 StringReader
的实例。
据我所知,当加载由 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
。 从 DynamicActivity
到 ActivityBuilder
的方式实际上非常简单,因为这两个类都具有非常相似的布局。 我们只是新建一个 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));
这一行有两个目的
- 它在 XAML 文件中创建一个非常重要的魔法字符串
<mva:VisualBasic.Settings> Assembly references and imported namespaces for internal implementation </mva:VisualBasic.Settings>
没有这个,我们的活动所组成的活动无法访问我们工作流的输入和输出参数。
- 它将所有命名空间导入从我们的
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 加载活动时,Value
为 null
。 然后,当执行活动时,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 文件将所有这些功能封装在一个很好的小助手类中。