在这个例子中,我们的目标就是解决前面提到的种种问题和限制。我们实现一个自定义的工作流管理类以及一个相关的工作流实例包装类。这些类将封装很多代码,用来host或者是与工作流运行时进行交互。实现管理类之后,我们开发一个宿主控制台程序,来执行上一个例子中的测试工作流。
实现工作流实例包装
工作流管理类应该包装起来,以便于其它的工程复用。我们创建一个Empty Workflow Project工程,命名为Nocturne.Workflow.Hosting。这是强命名,我们将在以后多次用到。一个常被复用的模块应该以这种命名方式存在于我们的机器里。
添加一个普通的C#类,名为WorkflowInstanceWrapper,然后添加下面的代码。说明已经包含在注释当中。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Runtime;
namespace Nocturne.Workflow.Hosting
{
/// <summary>
/// 工作流实例的容器
/// </summary>
[Serializable]
public class WorkflowInstanceWrapper
{
private WorkflowInstance _workflowInstance;
private ManualResetEvent _waitHandle = new ManualResetEvent(false);
private Dictionary<string, object> _outputParameters = new Dictionary<string, object>();
private Exception _exception;
private string _reasonSuspended = string.Empty;
public WorkflowInstanceWrapper(WorkflowInstance instance)
{
_workflowInstance = instance;
}
#region 公开属性与方法
/// <summary>
/// 获取工作流实例的 ID
/// </summary>
public Guid Id
{
get
{
if (_workflowInstance != null)
{
return _workflowInstance.InstanceId;
}
else
{
return Guid.Empty;
}
}
}
/// <summary>
/// 输出参数的集合
/// </summary>
public Dictionary<string, object> OutputParameters
{
get { return _outputParameters; }
set { _outputParameters = value; }
}
/// <summary>
/// 一个等待句柄。宿主应用程序可以使用它,挂起
/// 进程直到工作流完成。
/// </summary>
public ManualResetEvent WaitHandle
{
get { return _waitHandle; }
set { _waitHandle = value; }
}
/// <summary>
/// 工作流可能抛出的异常对象
/// </summary>
public Exception Exception
{
get { return _exception; }
set { _exception = value; }
}
/// <summary>
/// 工作流挂起的原因
/// </summary>
public string ReasonSuspended
{
get { return _reasonSuspended; }
set { _reasonSuspended = value; }
}
/// <summary>
/// 真实的工作流实例
/// </summary>
public WorkflowInstance WorkflowInstance
{
get { return _workflowInstance; }
}
/// <summary>
/// 工作流完成的信号,宿主应用程序可以停止等待
/// </summary>
public void StopWaiting()
{
_waitHandle.Set();
}
#endregion
}
}
实现工作流管理类
接下来我们来实现工作流管理类。它的目的就在于处理与WorkflowRuntime实例的主要交互行为,并不是代替WorkflowRuntime类,常规的任务比如创建和启动工作流都由此类来处理。从这一点上来说,很像是三层程序结构模型里的“数据访问层”。
该类同样包含了WorkflowRuntime的事件处理句柄。当一个工作流完成或中断的时候,这个处理代码会在集合中找到WorkflowInstanceWrapper,设置输出参数、异常,或是挂起原因。这就可以同时保存工作流的各种结果,交由宿主程序处理。
添加一个WorkflowRuntimeManager类,代码如下:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Runtime;
namespace Nocturne.Workflow.Hosting
{
public class WorkflowRuntimeManager : IDisposable
{
// 定义两个实例变量,_workflowRuntime变量保存着到WorkflowRuntime实例的引用
// _workflows变量是一个以Guid为键值的WorkflowInstanceWrapper的字典。
private WorkflowRuntime _workflowRuntime;
private Dictionary<Guid, WorkflowInstanceWrapper> _workflows = new Dictionary<Guid, WorkflowInstanceWrapper>();
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="instance"></param>
public WorkflowRuntimeManager(WorkflowRuntime instance)
{
_workflowRuntime = instance;
if (instance == null)
{
throw new NullReferenceException("需要一个非空的 WorkflowRuntime 实例");
}
// 提交所有的工作流事件
SubscribeToEvents(instance);
}
/// <summary>
/// 创建并启动一个工作流。
/// </summary>
/// <param name="workflowType">待启动的工作流类型</param>
/// <param name="parameters">入口参数</param>
/// <returns>包装在一个WorkflowInstanceWrapper里的工作流实例</returns>
public WorkflowInstanceWrapper StartWorkflow(Type workflowType, Dictionary<string, object> parameters)
{
WorkflowInstance instance = _workflowRuntime.CreateWorkflow(workflowType, parameters);
WorkflowInstanceWrapper wrapper = AddWorkflowInstance(instance);
instance.Start();
return wrapper;
}
#region 公共属性和事件
/// <summary>
/// 获取WorkflowRuntime实例
/// </summary>
public WorkflowRuntime WorkflowRuntime
{
get { return _workflowRuntime; }
}
/// <summary>
/// 获取工作流实例包装的一个字典
/// </summary>
public Dictionary<Guid, WorkflowInstanceWrapper> Workflows
{
get { return _workflows; }
}
/// <summary>
/// 记录该类的日志信息的事件
/// </summary>
public event EventHandler<WorkflowLogEventArgs> MessageEvent;
#endregion
#region 工作流集合管理
/// <summary>
/// 从工作流字典中移除某个实例
/// </summary>
/// <param name="workflowId"></param>
public void ClearWorkflow(Guid workflowId)
{
if (_workflows.ContainsKey(workflowId))
{
_workflows.Remove(workflowId);
}
}
/// <summary>
/// 从字典中清除所有的工作流
/// </summary>
public void ClearAllWorkflows()
{
_workflows.Clear();
}
/// <summary>
/// 向字典中添加一个新的工作流实例
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
private WorkflowInstanceWrapper AddWorkflowInstance(WorkflowInstance instance)
{
WorkflowInstanceWrapper wrapper = null;
if (!_workflows.ContainsKey(instance.InstanceId))
{
wrapper = new WorkflowInstanceWrapper(instance);
_workflows.Add(wrapper.Id, wrapper);
}
return wrapper;
}
/// <summary>
/// 依据Guid查找工作流实例
/// </summary>
/// <param name="workflowId"></param>
/// <returns></returns>
public WorkflowInstanceWrapper FindWorkflowInstance(Guid workflowId)
{
WorkflowInstanceWrapper result = null;
if (_workflows.ContainsKey(workflowId))
{
result = _workflows[workflowId];
}
return result;
}
/// <summary>
/// 等待所有的工作流实例完成
/// </summary>
/// <param name="msecondsTimeout"></param>
public void WaitAll(int msecondsTimeout)
{
if (_workflows.Count > 0)
{
WaitHandle[] handles = new WaitHandle[_workflows.Count];
int index = 0;
foreach (WorkflowInstanceWrapper wrapper in _workflows.Values)
{
handles[index] = wrapper.WaitHandle;
index++;
}
WaitHandle.WaitAll(handles, msecondsTimeout, false);
}
}
#endregion
#region IDisposable Members
/// <summary>
/// 销毁工作流运行时
/// </summary>
public void Dispose()
{
if (_workflowRuntime != null)
{
_workflowRuntime.StopRuntime();
_workflowRuntime.Dispose();
}
ClearAllWorkflows();
}
#endregion
#region 工作流事件处理
/// <summary>
/// 订阅我们所有关心的事件
/// </summary>
/// <param name="runtime"></param>
private void SubscribeToEvents(WorkflowRuntime runtime)
{
runtime.Started += new EventHandler<WorkflowRuntimeEventArgs>(runtime_Started);
runtime.Stopped += new EventHandler<WorkflowRuntimeEventArgs>(runtime_Stopped);
runtime.WorkflowAborted += new EventHandler<WorkflowEventArgs>(runtime_WorkflowAborted);
runtime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);
runtime.WorkflowCreated += new EventHandler<WorkflowEventArgs>(runtime_WorkflowCreated);
runtime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(runtime_WorkflowIdled);
runtime.WorkflowLoaded += new EventHandler<WorkflowEventArgs>(runtime_WorkflowLoaded);
runtime.WorkflowPersisted += new EventHandler<WorkflowEventArgs>(runtime_WorkflowPersisted);
runtime.WorkflowResumed += new EventHandler<WorkflowEventArgs>(runtime_WorkflowResumed);
runtime.WorkflowStarted += new EventHandler<WorkflowEventArgs>(runtime_WorkflowStarted);
runtime.WorkflowSuspended += new EventHandler<WorkflowSuspendedEventArgs>(runtime_WorkflowSuspended);
runtime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);
runtime.WorkflowUnloaded += new EventHandler<WorkflowEventArgs>(runtime_WorkflowUnloaded);
}
void runtime_Started(object sender, WorkflowRuntimeEventArgs e)
{
LogStatus(Guid.Empty, "Started");
}
void runtime_Stopped(object sender, WorkflowRuntimeEventArgs e)
{
LogStatus(Guid.Empty, "Stopped");
}
void runtime_WorkflowCreated(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowCreated");
}
void runtime_WorkflowStarted(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowStarted");
}
void runtime_WorkflowIdled(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowIdled");
}
void runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowCompleted");
WorkflowInstanceWrapper wrapper = FindWorkflowInstance(e.WorkflowInstance.InstanceId);
if (wrapper != null)
{
wrapper.OutputParameters = e.OutputParameters;
wrapper.StopWaiting();
}
}
void runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowTerminated");
WorkflowInstanceWrapper wrapper = FindWorkflowInstance(e.WorkflowInstance.InstanceId);
if (wrapper != null)
{
wrapper.Exception = e.Exception;
wrapper.StopWaiting();
}
}
void runtime_WorkflowSuspended(object sender, WorkflowSuspendedEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowSuspended");
WorkflowInstanceWrapper wrapper = FindWorkflowInstance(e.WorkflowInstance.InstanceId);
if (wrapper != null)
{
wrapper.ReasonSuspended = e.Error;
}
}
void runtime_WorkflowResumed(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowResumed");
}
void runtime_WorkflowPersisted(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowPersisted");
}
void runtime_WorkflowLoaded(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowLoaded");
}
void runtime_WorkflowAborted(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowAborted");
WorkflowInstanceWrapper wrapper = FindWorkflowInstance(e.WorkflowInstance.InstanceId);
if (wrapper != null)
{
wrapper.StopWaiting();
}
}
void runtime_WorkflowUnloaded(object sender, WorkflowEventArgs e)
{
LogStatus(e.WorkflowInstance.InstanceId, "WorkflowUnloaded");
}
private void LogStatus(Guid instanceId, String msg)
{
if (MessageEvent != null)
{
String formattedMsg;
if (instanceId == Guid.Empty)
{
formattedMsg = String.Format("Runtime - {0}", msg);
}
else
{
formattedMsg = String.Format("{0} - {1}", instanceId, msg);
}
// 引发事件
MessageEvent(this, new WorkflowLogEventArgs(formattedMsg));
}
}
#endregion
}
/// <summary>
/// 在管理类中用来记录日志信息的EventArgs
/// </summary>
public class WorkflowLogEventArgs : EventArgs
{
private string _msg = string.Empty;
public WorkflowLogEventArgs(string msg)
{
_msg = msg;
}
public string Message
{
get { return _msg; }
}
}
}
这其中运用到了事件、委托等.NET概念,如果有不是很清楚的地方,可以暂时略过。但作为进阶的必备知识,还是借机多多复习为好~~~
使用Workflow Manager类
为了展示如何去使用这个管理类,我们可以实现另外一个控制台应用程序,来执行我们前面提到的那个例子。创建一个新的工程,选择Sequantial Workflow Console Application模板,命名为ConsoleHostingManaged。我们还需要向此工程添加两个引用,一是SharedWorkflows,它包含了测试用的工作流;另一个就是Nocturne.Workflow.Hosting工程,它包含了我们刚刚开发的工作流管理类。创建完毕之后,删掉Workflow1.cs。
在演示使用管理类来host工作流之外,这个应用程序还展示了如何去装载一个核心工作流服务。我们执行测试的代码将被放置在一个单独的类里,而Program.cs将仅仅包含少量的初始化代码。
添加一个新的普通类WorkflowTest,其完整内容如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using Nocturne.Workflow.Hosting;
namespace ConsoleHostingManaged
{
/// <summary>
/// 使用自定义封装类来host工作流
/// </summary>
class WorkflowTest
{
/// Run 方法将在 Main 方法启动之后被执行,该方法创建一个 WorkflowRuntimeManager 的实例而启动,
/// 使用 using 的目的在于一旦离开这个语句块,就可以自动调用 Dispose 方法来销毁对象,前面实现
/// IDisposable 接口的意义就在于此。
///
/// WorkflowRuntimeManager 构造函数被传入一个 WorkflowRuntime 类的实例。这就是我们设计管理类
/// 的目的,从外部构造 WorkflowRuntime,以此来提供附加的过占星。
///
/// 一个事件句柄被作为 WorkflowRuntimeManager.MessageEvent 添加。此事件在处理 WorkflowRuntime
/// 事件的时候会被出发。这段代码仅仅是简单的将信息输出到屏幕上
/// <summary>
/// 启动工作流
/// </summary>
public static void Run()
{
Console.WriteLine("使用 persistence 服务运行测试");
using (WorkflowRuntimeManager manager = new WorkflowRuntimeManager(new WorkflowRuntime()))
{
// 添加一个事件句柄记录日志
manager.MessageEvent += delegate(object sender, WorkflowLogEventArgs e)
{
Console.WriteLine(e.Message);
};
// 创建完 WorkflowRuntime 及其包装之后,我们采用私有的 AddServices 方法向工作流运行
// 时添加服务。
AddServices(manager.WorkflowRuntime);
// 启动工作流运行时。注意这一步并非必须,如果忽略这条语句,工作流运行时同样会自动运
// 行。但是所有的核心服务必须在启动运行时引擎之前声明,如果我们在调用这条语句之后
// (或者被自动调用了)才添加核心服务(也就是上面那句话),编译器会抛出异常。
manager.WorkflowRuntime.StartRuntime();
// 当核心服务添加到运行时引擎之后,就可以运行工作流了。Dictionary 就是用来传送参数的
// 桥梁
Dictionary<string, object> wfArguments = new Dictionary<string, object>();
wfArguments.Add("InputString", "One");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1), wfArguments);
// 启动另一个实例。
wfArguments.Clear();
wfArguments.Add("InputString", "two");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1),wfArguments);
wfArguments.Clear();
wfArguments.Add("InputString","three");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1),wfArguments);
// 等待所有工作流实例完成
manager.WaitAll(15000);
// 显示所有工作流实例的结果。由于这些工作流是存储在Dictionary里的,因此可以
// 使用 foreach 进行迭代
foreach(WorkflowInstanceWrapper wrapper in manager.Workflows.Values)
{
if (wrapper.OutputParameters.ContainsKey("Result"))
{
Console.WriteLine(wrapper.OutputParameters["Result"]);
}
else
{
if (wrapper.Exception!=null)
{
Console.WriteLine("{0} - 异常: {1}",wrapper.Id,wrapper.Exception.Message);
}
}
}
manager.ClearAllWorkflows();
}
}
/// <summary>
/// 添加运行时引擎需要的服务
/// </summary>
/// <param name="instance"></param>
private static void AddServices(WorkflowRuntime instance)
{
string connStringPersistence = string.Format("Initial Catalog={0}; Data Source={1}; Integrated Security={2};", "WorkflowPersistence", @"localhost\SQLEXPRESS", "SSPI");
instance.AddService(new SqlWorkflowPersistenceService(connStringPersistence, true, new TimeSpan(0, 2, 0), new TimeSpan(0, 0, 5)));
}
}
}
在最后的部分可以看到,我们用到的是SqlWorkflowPersistenceService,这就需要我们事先安装过SQL Server。在这里我们以SQL Server 2005 Express为例,在数据库中建立WorkflowPersistence所需的基本表结构。
l 建立数据库。
进入命令行,执行
sqlcmd –S localhost\SQLEXPRESS –E –Q “create database WorkflowPersistence” |
当然,如果你的SQL Server不是默认配置,请酌情修改加粗的数据库名称
l 建立数据表以及存储过程。
在命令行下切换路径到%systemroot%\Microsoft.Net\Framework\v3.0\Windows Workflow Foundation\SQL\EN,执行(如果是中文版的,则粗体部分应是CN)
sqlcmd -S localhost\SQLEXPRESS -E -d WorkflowPersistence -i SqlPersistenceService_Schema.sql sqlcmd -S localhost\SQLEXPRESS -E -d WorkflowPersistence -i SqlPersistenceService_Logic.sql |
尽量不要修改数据库的名字,否则还需要修改WorkflowTest.cs和App.Config文件。
SqlWorkflowPersistenceService接受下面的参数:
l ConnectionString:这是SQL服务器的连接字符串,在这里就不多做介绍了;
l UnloadOnIdle:一个布尔值,决定工作流在空闲的时候是否从数据库中卸装;
l InstanceOwnershipDuration:一个TimeSpan类型,只是工作流运行时的实例持续其所有者身份多长时间。主要用在有很多的工作流工作在相同的persistence存储空间时;
l LoadingInterval:也是一个TimeSpan,决定了persistence服务在工作流重新装载之前有多久的空闲时间。
运行测试
剩下来我们需要做的,就是向Main方法添加代码,来调用WorkflowTest类的Run方法。代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
namespace ConsoleHostingManaged
{
/// <summary>
/// 使用自定义封装类测试工作流
/// </summary>
public class Program
{
static void Main(string[] args)
{
WorkflowTest.Run();
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
}
}
}
运行之,看看它的结果。
在这个结果中,我们可以看到Runtime Started事件最先接收到,对应着StartRuntime方法。在这之后,创建了三个工作流。如果我们顺藤摸瓜,可以发现事件是按照下面的顺序触发的。
1. Created:工作流实例被创建;
2. Started:工作流实例开始执行;
3. Idled:工作流成为非活动进程。注意到我故意使用DelayActivity产生了延时,此时工作流就处于空闲状态;
4. Persisted:因为装载了persistence服务,工作流在空闲状态时会被persist到数据库中;
5. Unloaded:即UnloadOnIdle时限触发工作流从内存中卸载;
6. Loaded:在DelayActivity完成之后,工作流将被persistence服务装载回内存中;
7. Persisted:该时间标志着工作流从数据库中被移除;
8. Completed:工作流最终完成。
使用App.Config配置运行时
前面的示例演示了如何在代码中向工作流运行时添加核心服务。如果我们愿意,也可以采用应用程序配置文件(App.Config)来完成。这样做的好处就在于如果我们需要修改服务配置的时候,无需重新编译。
.NET应用程序配置文件是一个保存有多项配置的XML文件,具体的使用就不在这里详述。记着一点,配置文件的文件名是严格要求的,比如一个应用程序MyApp.exe,它的配置文件就必须为MyApp.exe.config。ASP.NET程序则统一使用Web.Config文件。
OK,现在开始。向ConsoleHostingManaged工程添加一个新的Item,将其类型选择为Application Configuration File,并且使用缺省的命名。完整内容如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="WorkflowRuntime"
type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection,
System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</configSections>
<WorkflowRuntime Name="ConsoleHostingManaged">
<CommonParameters>
<!--向所有服务添加公共属性-->
<add name="ConnectionString"
value="Initial Catalog=WorkflowPersistence;
Data Source=localhost\SQLEXPRESS;
Integrated Security=SSPI;" />
</CommonParameters>
<Services>
<!--添加核心服务-->
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,
System.Workflow.Runtime, Version=3.0.00000.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
UnloadOnIdle="true" LoadIntervalSeconds="5" />
</Services>
</WorkflowRuntime>
</configuration>
仅仅有了这个文件还不行,代码也需要进行一些修改。首先向工程添加一个引用,System.Configuration程序集。然后做一份WorkflowTest.cs的副本,重命名为WorkflowAppConfigTest.cs,记得将类名也修改一下。修改后的代码如下所示:
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using Nocturne.Workflow.Hosting;
/// 必须添加 System.Configuration 程序集的引用,这样程序才能从 App.Config 中
/// 读取配置信息
namespace ConsoleHostingManaged
{
/// <summary>
/// 使用自定义封装类来host工作流,以及 App.Config 作为配置来源
/// </summary>
class WorkflowAppConfigTest
{
public static void Run()
{
Console.WriteLine("使用 App.Config 运行测试");
using (WorkflowRuntimeManager manager = new WorkflowRuntimeManager(new WorkflowRuntime("WorkflowRuntime")))
{
// 添加一个事件句柄记录日志
manager.MessageEvent += delegate(object sender, WorkflowLogEventArgs e)
{
Console.WriteLine(e.Message);
};
// 启动工作流运行时。
manager.WorkflowRuntime.StartRuntime();
// Dictionary 就是用来传送参数的桥梁
Dictionary<string, object> wfArguments = new Dictionary<string, object>();
wfArguments.Add("InputString", "One");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1), wfArguments);
// 启动另一个实例。
wfArguments.Clear();
wfArguments.Add("InputString", "two");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1),wfArguments);
wfArguments.Clear();
wfArguments.Add("InputString","three");
manager.StartWorkflow(typeof(SharedWorkflows.Workflow1),wfArguments);
// 等待所有工作流实例完成
manager.WaitAll(15000);
// 显示所有工作流实例的结果。
foreach(WorkflowInstanceWrapper wrapper in manager.Workflows.Values)
{
if (wrapper.OutputParameters.ContainsKey("Result"))
{
Console.WriteLine(wrapper.OutputParameters["Result"]);
}
else
{
if (wrapper.Exception!=null)
{
Console.WriteLine("{0} - 异常: {1}",wrapper.Id,wrapper.Exception.Message);
}
}
}
manager.ClearAllWorkflows();
}
}
}
}
最重要的改动就是代码中粗体的部分。这一次我们使用的是WorkflowRuntime类的一个重载版本的构造函数,可以接受配置区域的名字。WorkflowRuntime字符串对应着我们在App.Config中添加的那个部分。AddServices方法就被移除了,因为它已经没有利用价值了……
修改Program.cs,将WorkflowTest.Run修改为WorkflowAppConfigText.Run即可。下面就是运行结果,除了Guid值有变化,其余部分都是相同的。Persistence服务的全部动作都可以从这份记录中看出来。