1、创建一个新的宿主应用程序1.就像你在前一章做的一样,打开 Visual Studio 创建一个新应用程序项目。但是,不是要创建一个基于控制台的应用程序,而是创建一个 Windows 应用程序,名称为WorkflowPersister。下面的步骤在第二章中已经描述过:包含“添加工作流 assembly 引用”、“宿主工作流运行时”、“ 创建 WorkflowRuntime 工厂对象”,“启动工作流运行时” ,“停止工作流运行时”,“使用工作流运行时工厂对象”,“ 处理工作流运行时事件” 过程。最后,添加一个 app.config 文件(可参考前一章中的“添加 SqlTrackingService
2、 到你的工作流中” ,可不要忘记添加 System.Configuration 的引用)。2.现在向 app.config 文件中添加恰当的数据库连接字符串(数据库为WorkflowStore)。3.当你创建了 WorkflowPersister 项目时,Visual Studio 显示了 Windows Forms 视图设计器。在 Windows Forms 视图设计器中把鼠标移到工具箱上,选择一个 Button 控件,并把它拖放到设计器的界面上。4.我们将为这个按钮设置一些富有意义的文字属性,以便于我们知道我们点击的是什么。选中这个按钮,然后在 Visual Studio 的属性面板中选择
3、该按钮的 Text 属性,把该属性的值设置为“Start Workflow”。5.为该按钮添加 Click 事件的处理程序,具体代码将在后面的步骤中添加。6.修改按钮的位置和大小,如下图所示:7.重复步骤 3 至步骤 5,再添加两个按钮,一个的 text 属性为“Unload Workflow”,另一个的 text 属性为 “Load Workflow”。如下图所示:8.现在就为测试我们的工作流创建好了用户界面,该是为我们将执行的应用程序添加事件处理代码的时候了。当应用程序加载时我们需要初始化一些东西,做这些工作的一个很合适的地方是在主应用程序窗体中的 Load 事件处理程序。9.在该事件处理
4、程序(处理方法)中输入下面的代码: _runtime = WorkflowFactory.GetWorkflowRuntime();_runtime.WorkflowCompleted +=new EventHandler(Runtime_WorkflowCompleted);_runtime.WorkflowTerminated +=new EventHandler(Runtime_WorkflowTerminated);10。在 Form1 类中声明下面名称为_runtime 的字段:protected WorkflowRuntime _runtime = null;protected W
5、orkflowInstance _instance = null;11.添加 System.Workflow.Runtime、System.Workflow.ComponentModel 和System.Workflow.Activity 三个工作流组件的引用(可参考前面章节),然后在该代码文件中添加下面的命名空间:using System.Workflow.Runtime;12.尽管我们现在有了一个应用程序来宿主工作流运行时,但它实际上没做任何事。为完成些功能,我们需向按钮的事件处理中添加一些代码。先向 button1_Click 中添加下面的代码:button2.Enabled = tru
6、e;button1.Enabled = false;_instance = _runtime.CreateWorkflow(typeof(PersistedWorkflow.Workflow1);_instance.Start();这些代码使“Start Workflow”按钮禁用,而让“Unload Workflow”按钮可用,然后启动了一个新的工作流实例。13.下一步,找到“Unload WorkflowInstance”按钮的事件处理:button2_Click,然后添加下面的代码。这里,我们使用 WorkflowInstance.Unload 方法来卸载工作流实例并把它写入数据库。在工
7、作流实例卸载后,我们让“Load Workflow”按钮可用。注意假如我们在卸载工作流实例时产生异常,“Load Workflow”按钮是不可使用的。这样做的意义是:假如卸载请求失败,也就不用再加载。button2.Enabled = false;try_instance.Unload();button3.Enabled = true; / trycatch (Exception ex)MessageBox.Show(String.Format(“Exception while unloading workflow“ +“ instance: 0“,ex.Message); / catch12
8、3 备注:牢记 WorkflowInstance.Unload 是同步的,虽然我在本章前面已经阐述过,但这点很重要。这意味着线程试图卸载工作流实例时将会被阻塞(暂停),直到操作完成后为止(不管卸载实例时是成功还是失败)。在这种情况下,可准确执行我想要的行为(指卸载),因为我不想反复查看实例是否被卸载。但有时,你想在卸载时不会被阻塞,就应使用前面说过的 Workflowinstance.TryUnload。稍后,在你添加完最后一批代码并运行应用程序时,当你单击“Unload Workflow”时密切观察,你会看到应用程序会简短地被冻结,因为它正等待工作流卸载。14.现在回到我们关注的工作流事件处
9、理上: Runtime_WorkflowCompleted 和Runtime_WorkflowTerminated。这两个事件处理实际上完成相同的动作,那就是重置应用程序以便为另一个工作流实例的执行做好准备。在“button2”的“click”事件处理方法(该方法包含代码我们已在前面的步骤中添加)的下面添加下面这些方法:void Runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)WorkflowCompleted();void Runtime_WorkflowTerminated(object sende
10、r, WorkflowTerminatedEventArgs e)WorkflowCompleted();15.当然,我们现在还要创建 “WorkflowCompleted”方法。假如你熟悉 Windows 编程,你可能知道 Windows 一直以来存在着的限制问题。这个限制简单的说就是你不能在创建窗口控件的线程外的任何线程中改变该窗口控件的状态。因此假如你想改变某个控件的text,你必须在创建该控件的同一线程上指定该控件的 text,使用任何其它线程最有可能导致的结果是造成你的应用程序崩溃。因此我们即将加入的代码对你来说可能很搞笑,但所有要做的这些的真正目的是确信我们是在原来的创建按钮的线程
11、中来使按钮能用和禁用。(事件处理几乎总是在不同的线程上进行调用。)假如我们只是在按钮自身的事件处理中使其可用,应用程序可能还是能工作,但它更大的可能是崩溃和挂起。简单地把下面的代码复制并放到源文件 Form1 类的尾部,应用程序才会正确的工作。private delegate void WorkflowCompletedDelegate();private void WorkflowCompleted()if (this.InvokeRequired)/ Wrong thread, so switch to the UI threadWorkflowCompletedDelegate d =
12、delegate() WorkflowCompleted(); ;this.Invoke(d); / ifelsebutton1.Enabled = true;button2.Enabled = false;button3.Enabled = false; / else 16.在创建我们将执行的工作流之前我们需要最后做一件事,那就是要修改WorkflowFactory 类。假如你从第五章(“ 为你的工作流添加跟踪服务”)以来准确地遵循了所有的步骤来创建和修改 WorkflowFactory 的话,你实际上创建了一个为工作流运行时提供跟踪服务的工厂对象。我们将对该代码进行轻微的调整,把 SqlT
13、rackingService 服务改为 SqlWorkingPersistenceService,并且改变声明命名空间的语句(把System.Workflow.Runtime.Tracking 改为 System.Workflow.Runtime.Hosting)。打开WorkflowFactory.cs 文件进行编辑。17.用下面的代码来替换声明的 System.Workflow.Runtime.Tracking 命名空间。using System.Workflow.Runtime.Hosting;using System.Configuration;18。最后为运行时添加持久化服务,方法是
14、在创建工作流运行时对象后添加下面的代码:string conn = ConfigurationManager.ConnectionStrings“StorageDatabase“.ConnectionString;_workflowRuntime.AddService(new SqlWorkflowPersistenceService(conn);注意:因为我们在代码中创建的工作流实例的类型是 PersistedWorkflow.Workflow1类型(在第 12 步中),因此我们的宿主应用程序编译并执行会出错,我们将在下面的一节解决。这里我们就有了一个 Windows 图形用户界面和宿主应用
15、程序,我们使用它们来承载我们的工作流。谈到工作流,我们不是要创建并执行它吗?其实,这在下面进行讲解。创建一个新的可卸载的工作流1.像前面一章一样,我们又将在我们的项目中创建一个新的顺序工作流库。在 Visual Studio 中激活 WorkflowPersister 应用程序,然后点击“文件” 菜单,选择“添加”,当子菜单弹出后,选择“新建项目”。从“添加新项目” 的对话框中添加一个“ 顺序工作流库”的项目,项目名称为“PersistedWorkflow”。2.在应用程序解决方案中创建并添加一个新项目后,将呈现该工作流的视图设计器界面。从工具箱中拖拽一个“Code”活动到设计器界面上。在 V
16、isual Studio 属性面板上设置“Code”活动的“ExecuteCode”属性为 PreUnload 然后按下回车键。3.然后 Visual Studio 将自动切换到该工作流的源代码文件中,向刚刚插入的PreUnload 方法添加下面的代码:_started = DateTime.Now;System.Diagnostics.Trace.WriteLine(String.Format(“* Workflow 0 started: 1“,WorkflowInstanceId.ToString(),_started.ToString(“MM/dd/yyyy hh:mm:ss.fff“)
17、;System.Threading.Thread.Sleep(10000); / 10 seconds4.为了计算工作流消耗的时间(下面的步骤中我们将看到,该时间至少在两个 Code活动执行的时间之间),我在一个名称为“_started”字段中保存了启动时间。在你的源文件中构造函数的上面添加该字段:private DateTime _started = DateTime.MinValue;5.现在切换到设计视图,添加第二个 Code 活动。该活动的 ExecuteCode 属性设置为“PostUnload”,并自动生成该方法。你将看到的设计器界面如下:6.再次切换回工作流的源代码文件中,向 P
18、ostUnload 方法中添加下面必要的代码: DateTime ended = DateTime.Now;TimeSpan duration = ended.Subtract(_started);System.Diagnostics.Trace.WriteLine(String.Format(“* Workflow 0 completed: 1, duration: 2“,WorkflowInstanceId.ToString(),ended.ToString(“MM/dd/yyyy hh:mm:ss.fff“),duration.ToString();7.最后一步是添加一个项目级的引用把该
19、工作流项目引用到我们的主应用程序中,具体步骤可参考前面的章节。备注:现在你或许想运行该程序,但请等等!假如你运行该应用程序,然后点击“Start Workflow”按钮而没有点击“Unload Workflow”按钮的话,该应用程序不会出现运行错误。因为一旦工作流被卸载,但我们还没有添加代码来重新加载这个已被持久化(卸载)的工作流实例。因此在下一节添加代码前,你不应单击“Unload Workflow”按钮。这样做的目的是在工作流开始执行时,在第一个 Code 活动中让它休眠(sleep )10秒钟。在此期间,你可点击“Unload Workflow”按钮来卸载该工作流。在这 10 秒届满后,该工作流将被卸载并持久化到数据库中。这些事一旦发生,你就可喝杯咖啡、吃吃棒棒糖或者其它任何事休息休息:你的工作流已被保存到数据库中,正等待再次加载。让我们看看这又是怎么工作的。