WCF揭秘学习笔记(5):WF定制活动

  WF(Windows Workflow
Foundation,Windows工作流基础)为.NET提供了一种基于模型的、声明方式的过程执行引擎,它改变了传统的通过一行行编写代码来开发服务功能的方式。

  WF包含三个核心组件:活动框架(activity framework)、运行时环境(runtime
environment)、工作流设计器(workflow designer)。

WF不是什么

  工作流这个词在软件开发领域和相关社区里已经被“滥用”了。所以弄清楚WF在这些流行的工作流概念中到底指的是哪一种就非常重要。

  1. WF不是服务器,虽然可以将工作流功能集中起来然后通过服务器暴露给其他应用程序使用。

  2. WF不是一种BPM(Business Process
Management,业务流程管理)工具,虽然可以将WF作为工作流执行引擎来创建BPM工具。

  3.
WCF并不直接面向业务分析人员,虽然可以使用可重新承载的设计器向业务分析人员提供创建自己的工作流的工功能。WF非常灵活,它允许将此功能集成到分析人员熟悉的环境中。如果现有的设计器不能满足要求,还可以创建定制的工作流设计器。

  4. WF不是企业应用集成工具,虽然可以将第三方的系统功能封装在活动中,然后将它们加到工作流里。

  5. WF不是一个玩具。这可以在具有大量服务器集群的企业级规模环境里高性能地运作。它可以胜任企业级应用,SharePoint
Server就是一个很好的应用实例。而WF本身并不是一个企业级应用,它只是一组开发套件。

  WF不仅可以用在基于服务器的应用程序里,它还可以用在WinForm应用中执行从服务协调到用户界面定制的任何应用程序逻辑。也可以用在Web应用程序中以管理流程状态。总之,任何可以编写.NET代码的地方都可使用它作为提供逻辑的代码。

活动

  活动(activity)是WF的基本构建模块。从复杂的执行逻辑到执行一个对SQL数据库的更新,这些操作都被封装到一个个独立的工作单元,
也就是活动中。活动是所有直接或间接继承自System.Workflow.ComponentModel.Activity的类。活动具有两个方面的行
为:运行时行为(runtime behavior)和设计时行为(design-time behavior)。

  运行时行为是活动在工作流中使用时执行的代码。它可能是访问一个Web服务或执行一块代码,也可能是协调子活动的执行。一个常被问及的问题
是:WF的性能怎样?在活动级别,答案很简单:活动可以和在.NET程序集里相同代码的执行速度一样快。因为活动只是一个已经编译好的.NET程序集,它
包含从activity继承的类。管理活动的生命周期需要额外的开支。

  当包含活动的工作流被创建时,该活动就会被初始化并停留在Initialized(初始)状态。也就是说,当CreateInstance方法
被调用且数据流实例被创建时,所有的活动都将被初始化。服务在被调度执行时,其状态会变成Executing(执行)。如果一切正常,活动会进入到
Closed(关闭)状态,也就是它的任务已完成,可以休息了。

  活动可能会遇到错误,从而进入Faulting状态而被关闭。活动也可能被其他的活动取消掉,从而进入Canceling状态,然后再进入
Closed状态。最后,如果活动为其执行定义了回滚或补偿操作,活动就会从Closed状态激活并进入到Compensating状态。由于某些原因,
一个错误或一个取消处理都需要调用补偿逻辑。

  另外,活动的设计体验对创建工作流是非常重要的。活动可能需要在设计窗口里有些个性化的表示,目的是为了告诉开发者可以一目了然地知道活动正在做什么。

开箱即用活动

  随WF一起发布的活动通常被称为开箱即用活动(out of the box
activity)。它们是一组基本的活动,可用来创建简单的工作流和组建新的活动。下表给出了每个开箱即用活动的简单介绍:

创建定制服务

  工作流开发者可能经常需要创建定制活动。从封装经常使用的方法到创建能为一种新的执行模式建模的复合活动,开发者在开始一个工作流项目时需要首先思考他需要什么样的活动。随着时间的推移,这些活动可被复用或被用来组建更高层次的活动,最后就像处理现在的普通对象一样。

基础

  创建定制活动的最基本方法就是从System.Workflow.ComponentModel.Activity继承一个类。这将为活动创建所有的基本机制,除了活动实现的实际逻辑。可通过重载Execute方法来实现实际的逻辑。如下所示:

publicclass BasicActivity :
Activity
{
public
BasicActivity()
{
this.Name
="BasicActivity";
}
protectedoverride
ActivityExecutionStatus Execute(ActivityExecutionContext
executionContext)
{
Console.WriteLine("Basic
Activity");
return
ActivityExecutionStatus.Closed;
}
}

  Execute方法执行活动的操作并需同时通知运行时它的状态。在这里,它返回Closed状态,表示该活动已完成它的工作。一个更加复杂的模
式是:返回Executing状态,同时等待一个需要运行较长时间的工作完成。当这个较长时间的工作完成时,活动将通知运行时它已经完成了。在等待时,工
作流实例可能停顿下来较长时间。

  我们经常需要控制一些与活动相关的参数,它们会影响活动所提供的有用功能。最简单的方法就是为活动类添加属性,如下所示:

publicstring TextToPrint
{
get { return textToPrint; }
set { textToPring ="value"; }
}
protectedoverride
ActivityExecutionStatus Execute(ActivityExecutionContext
executionContext)
{
Console.WriteLine("Text to print: {0}", TextToPrint);
return
ActivityExecutionStatus.Closed;
}

  当使用XAML声明式地创建工作流时,这些属性会被作为活动的特性(attribute),如下所示:

<SequentialWorkflowActivity
x:Class="SampleWFApplication.Workflow1"
x:Name="Workflow1"
xmlns:ns0="clr-namespace:SampleWFApplication"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
<ns0:BasicActivity
TextToPrint="Hello
World"

x:Name="basicActivity1"/>
</SequentialWorkflowActivity>

  这样就允许在设计器里设定属性的值从而定制活动的行为。但这还是静态的,属性被限制为设计时设定的值。当创建的工作流需要评估传入的定制对象时
呢?这可以通过使用依赖属性(dependency
property)来实现。依赖属性和之前介绍的.NET标准属性类似,但在声明和用户法上有所不同。VS中有内建的代码来创建它们,如下所示:

publicstatic DependencyProperty OrderAmountProperty
=
System.Workflow.ComponentModel.
DependencyProperty.Register("OrderAmount", typeof(int),
typeof(BasicActivity));
[Description("This
property holds the amount of the order")]
[Category("Order Details")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
publicint OrderAmount
{
get
{
return ((int)(base.GetValue(BasicActivity.OrderAmountProperty)));

}
set

{
base.SetValue(BasicActivity.OrderAmountProperty,
value);
}
}

  这个稍长的声明里不仅有属性的声明,而且还包含了一个静态DependencyProperty变量的声明。
DependencyProperty是一种添加到DependencyObject(Activity就继承自DependencyObject)的特
殊属性类型。DependencyProperty不同于传统属性,它可以支持三种特殊的使用方式:

  1. 活动绑定。

  2. 元数据,只在设计时赋值,在运行时不能更改。

  3. 附加属性(attached property),动态地为活动添加属性。

  使用依赖属性的最普通场景是支持活动绑定。使用依赖属性的好处是可以添加新的设计时行为。将活动拖到设计窗口后,观察它的属性窗口,会发现在刚长声明的属性旁边新增一个new图标。单击该图标会弹出一个新的Bind(绑定)对话框。

  Bind对话框允许将属性的值与工作流中的另外一个值动态绑定。这个值可以是在工作流创建时传入该工作流的一个属性,也可以是另外一个活动的属
性。在设计时,活动被告知到哪儿去查找依赖属性的值。通过选择工作流中同一类型(可以是一个定制类型)的另外一个值,一个绑定表达式会出现在属性窗口里作
为这个依赖属性的值。表达式与下面的式子类似的:

  Activity=Workflow1, Path=WorkflowOrderAmount

  式子的第一部分表示源(这里是父工作流),然后是活动可以使用的属性。式子里可以使用点操作符,如果希望被绑定的值是属性的下面几层,它也可以被访问。依赖属性的值还可以直接写在代码里。

  Bind对话框里有一个Bind to a New
Member(绑定到一个新成员)标签页。这个窗口可以使一个活动的依赖属性“提升”(promote)为窗口活动(即包含活动的活动)的一个成员。

组合

  第二种创建活动的方法是复合(composition),这也是VS的默认方法。以下代码在VS中新建一个活动类的定义:

publicpartialclass
ACompositeActivity : SequenceActivity

  它继承自SequenceActivity。SequenceAcitivity是创建顺序执行工作流的基类,它的Execute方法负责将包
含的活动依次调度执行。通过继承,可保留我们希望的这种模式。这将使活动的开发者通过拖拽其他活动到新的活动里来创建一个新的活动,也就是从现有的活动创
建新的活动。这是一个创建新工作单元的强大工具。这意味着,如果有一组已经实现且足够强大的基本活动(如对现有API的封装)的集合,就可使用它们快速创
建更高层次的新功能单元。

  考虑下面的活动:

  1. SendEmail

  2. LookupManager

  3. WaitForResponseToEmail

  使用这些活动及一些开箱即用活动提供的结构化活动,可以创建任意复杂的标准活动,然后新建的活动就可以作为批准活动被任意的工作流使用。

定制复合活动

  我们还可以创建另外一类活动,即定制复合活动(custom composite
activity)。复合活动包含子活动,它的Execute方法负责调度执行这些子活动。在开箱即用活动中,Sequence、While和Parallel都是复合活动的例子。

  复合活动执行时,会执行它的子活动并订阅这些子活动的完成事件。活动会返回Executing状态,向运行时指明它要继续执行下一个被调度的活
动。通过接收子活动的完成事件,复合活动可以继续调度执行其他的活动或判断是否可以结束。当所有的活动都结束了或复合活动决定结束时,它会返回
Closed状态,表明它已经结束了。工作流会确保在允许一个活动关闭前,它的所有子活动都关闭了。

活动通信

  工作流并不是在完全孤立的环境里执行的,它们经常需要和宿主程序交互以发送消息给宿主或从宿主接收消息。两个开箱即用活动被设计成支持这样的交
互:Handle-ExtenalEvent和CallExternalMethod。这些活动通过宿主和工作流间共享的契约通信。该契约的实现作为本地
服务提供给运行时ExternalDataExchangeService。

  让我们来看一个基于员工调查应用场景的例子。首先创建一个在宿主和工作流间共享的契约,并为其添加ExtenalDataChange特性:

using
System;
using
System.Workflow.ComponentModel;
using
System.Workflow.Activities;

namespace ExternalEventSample
{

[ExternalDataExchange()]
publicinterface ISurveyResponseService

{
void
SurveyEmployee(string
employee, string surveyQuestion);
event EventHandler<SurveyEventArgs> SurveyCompleted;
}

[Serializable]
publicclass SurveyEventArgs : ExternalDataEventArgs

{
privatestring
employee;
publicstring Employee
{
get { return employee; }
set { employee = value; }
}
privatestring
surveyResponse;
publicstring SurveyResponse
{
get
{ return
surveyResponse; }
set
{ surveyResponse = value; }
}

public
SurveyEventArgs(Guid instanceId, string employee, string surveyResponse) : base(instanceId)
{
this.employee
=
employee;
this.surveyResponse = surveyResponse;
}

}
}

  该接口定义了一个事件、一个定制的事件参数类和一个公共方法。我们再提供一个接口的实现:

using
System;

namespace
ExternalEventSample
{
class
SurveyResponseService : ISurveyResponseService
{
publicvoid SurveyEmployee(string employee, string surveyQuestion)
{
//here
we would notify and display the survey
Console.WriteLine("Hey
{0}, what do you think of {1}?", employee, surveyQuestion);

}
publicevent EventHandler<SurveyEventArgs> SurveyCompleted;
publicvoid
CompleteSurvey(Guid instanceId, string employee, string surveyResponse)
{
// the
host will call this method when it wants to raisethe event into the
workflow.
// Note that the workflow instance id needs
to be passed in.

EventHandler<SurveyEventArgs> surveyCompleted =this.SurveyCompleted;
if(surveyCompleted !=null)

{
surveyCompleted(null, new
SurveyEventArgs(instanceId, employee, surveyResponse));

}
}
}
}

  现在,将ExternalDataExchange服务添加到运行时,并且将接口实现的一个实例添加为本地服务。使用OnWorkflowIdled事件发送响应给宿主。

using
System;
using System.Collections.Generic;
using
System.Text;
using
System.Threading;
using
System.Workflow.Runtime;
using
System.Workflow.Runtime.Hosting;
using
System.Workflow.Activities;

namespace ExternalEventSample
{
class
Program
{
static
SurveyResponseService surveyService;
staticvoid
Main(string[]
args)
{
using(WorkflowRuntime
workflowRuntime =new
WorkflowRuntime())
{
// add the local service to the external data
exchange service

surveyService =new
SurveyResponseService();
ExternalDataExchangeService
dataService =new ExternalDataExchangeService();

workflowRuntime.AddService(dataService);

dataService.AddService(surveyService);
AutoResetEvent
waitHandle =new AutoResetEvent(false);

workflowRuntime.WorkflowCompleted +=delegate(object
sender, WorkflowCompletedEventArgs e)
{
waitHandle.Set(); };
workflowRuntime.WorkflowTerminated
+=delegate(object
sender, WorkflowTerminatedEventArgs e)
{

Console.WriteLine(e.Exception.Message);

waitHandle.Set();
};

workflowRuntime.WorkflowIdled += OnWorkflowIdled;

WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof

WorkflowConsoleApplication13.Workflow1));

instance.Start();
waitHandle.WaitOne();

}
}
staticvoid OnWorkflowIdled(object sender, WorkflowEventArgs e)

{

surveyService.CompleteSurvey(e.WorkflowInstance.InstanceId, "Matt", "Very
Statisfied");

}
}
}

  回到工作流,将CallExternalMethod活动拖到设计窗口。注意智能标签验证表明接口类型和方法名称都没有定义。点击
InterfaceType属性会弹出一个标准的类型浏览窗口以选择合适的接口。方法可以从MethodName属性的下拉列表中选择,方法选择好后,对
应于输入参数和接口里定义的输出的其他属性就会添加到属性窗口里。

  当CallExternalMethod活动执行时,它通过ActivityExecutionContext.GetService获得对契约实现的访问,并调用其方法。

  为使用HandleExternalEvent活动,要做的第一件事就是为宿主程序提供一种为工作流运行时触发事件的方法,以接收和路由消息。
在上例中通过调用服务实现的方法CompleteSurvey方法来完成。该方法将触发一个事件,运行时会根据传入的workflowId参数将其路由给
相应的工作流实例。在内部实现时,HandleExternalEvent会创建一个队列并订阅放在队列里的消息。当接收到一个事件时,运行时会将消息放
在正在等待该类型消息的队列上。可以让多个队列同时监听同一种类型消息--如等待多个员工完成调查。在这种情况下,事件将被路由到哪个队列的粒度可通过使
用correlation来指定。

  为使用HandleExternalEvent,首先将它拖到设计窗口里,其过程与CallExternalMethod类似。当工作流到达
HandleExternalEvent活动时,它会创建一个队列来监听指定类型的事件类型,然后,或者停下来,或者继续执行其他被调度执行的活动。

时间: 2024-10-21 16:35:09

WCF揭秘学习笔记(5):WF定制活动的相关文章

WCF揭秘学习笔记(3):使用DataContractSerializer

使用DataContractSerializer 终结点(包括地址.绑定.契约)可通过代码以编程方式添加到服务中.如: using(ServiceHost host =new ServiceHost(typeof(DerivativesCalculator),new Uri[] { new Uri("http://localhost:8000/Derivatives") })){ host.AddServiceEndpoint(typeof(IServiceViewOfService)

WCF揭秘学习笔记(4):可信赖会话、会话管理、队列、事务

可信赖会话 WCF的可信赖会话在绑定层保证消息只会被传输一次,并且保证消息间的顺序.当使用TCP通信时,协议本身保证了可靠性,但它只在两点间的网络 包这个层面提供了这样的保证.WCF的可信赖会话特性保证了在传输过程中消息不会丢失.重复或错位.这种保证是消息层面的,且适用于任何数目节点间的通 信.另外,使用可信赖会话时,WCF会重连掉线的连接,在重连失败时还会释放会话占用的相关资源.可信赖会话还会通过调整消息的发送频率来缓解网络拥挤. 为使用WCF的可信赖会话,必须选择支持可信赖会话的绑定.支持这

WCF揭秘学习笔记(2):数据表示

背景知识 WCF提供了一种语言为软件通信建模,称作服务模型.使用更底层的编程架构提供的类可以从这种语言建立的模型中生成可用的通信软件. 在服务模型使用的语言中,负责通信的软件部分称为服务(service).一个服务具有一个或多个通信的终结点,终结点包括地址.绑定和契约. 地址的作用比较简单,它通过使用URL指定服务的唯一地址. 绑定指定了客户端与服务器间的通信协议.绑定至少需要分别提供一种编码消息和传输消息的协议. 契约指定了在一个终结点可以执行的操作. 以上节中的契约为例: //契约[Serv

Python学习笔记_Chapter 6定制数据对象

1. 有用的BIF a. 判断字符串中是否包含子字符串 1 if s_a in s_b: b. pop() 描述:从指定的列表位置删除并返回一个数据项. 1 (sarah_name,sarah_dob)=l_rah.pop(0),l_rah.pop(0) 2 #pop(0)中0位置为list中第一个数据项 3 #第一次执行pop赋值给sarah_name c. strip() 输入的是字符串,返回的是列表 d.open 读文件时可以多种方式打开文件,取出的数据是不同的,可以是文本也可以是二进制.

根据兄弟元素的数量来设置样式(CSS揭秘学习笔记)

CSS揭秘学习笔记 li.length == 4 li:first-child:nth-last-child(4), li:first-child:nth-last-child(4) ~ li { /* 当列表正好包含四项时,命中所有列表项 */ } li.length >= 4 li:first-child:nth-last-child(n+4), li:first-child:nth-last-child(n+4) ~ li { /* 当列表至少包含四项时,命中所有列表项 */ } li.l

javascript学习笔记之时间定制器

时间间隔定制器 单次定制 setTimeout() 函数 setTimeout("操作",时间); eg:setTimeout("alert('五分钟后显示',5*60*1000); 多次定制 setIntervar()函数 setIntervar("操作",间隔时间); eg:setIntervar("alert('每间隔五分钟显示',5*60*1000); javascript学习笔记之时间定制器

Android学习笔记(五)——活动的生命周期

//此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 为了能写出流畅连贯的程序,我们需要了解一下活动的生命周期. 一.返回栈 Android 中的活动是可以层叠的.我们每启动一个新的活动,就会覆盖在原活动之上,然后点击 Back 键会销毁最上面的活动.事实上,Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack) .默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位

Python学习笔记之selenium 定制启动 chrome 的选项

学习地址:http://blog.csdn.net/vinson0526/article/details/51850929 使用 selenium 时,我们可能需要对 chrome 做一些特殊的设置,以完成我们期望的浏览器行为,比如阻止图片加载,阻止JavaScript执行 等动作.这些需要 selenium的 ChromeOptions 来帮助我们完成 什么是 chromeoptions chromeoptions 是一个方便控制 chrome 启动时属性的类.通过 selenium 的源码,

Android第一行代码学习笔记六---Intent向活动传递数据

@1.向下一个活动传递数据: Intent提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需把这些数据再从Intent中取出就可以了,比如firstActivity中有一个字符串要传递到secondActivity中,修改firstActivity中按钮点击事件,代码可以这样编写: button.setOnClickListener(new View.OnClickListener() { public void onClick(V