Metah.W: 一种工作流元编程语言
Metah.W (MW) 是一种基于 C# 的 Windows Workflow Foundation 4.5 的元编程语言。它提供了一种不同于 WF 设计器的组合活动的方式。
Metah.W (MW) 是一种基于 C# 的 Windows Workflow Foundation 4.5 的元编程语言。它提供了一种不同于 WF 设计器的组合活动的方式。其网站是 https://github.com/knat/Metah。
本文假定您熟悉 WF 4.5 编程。
命名空间名称缩写
System.Activities -> SA System.Activities.Statements -> SAS System.ServiceModel.Activities -> SSA
本文中的所有代码清单都包含在 HelloMW 项目中。
我是一名在中国工作的 .NET 程序员,我的英语不太好(可能很差),如果您能改进本文,我将非常感激。
引言
下图摘自 MSDN 库的 “入门教程”,代表了一个在 WF 设计器中组合的猜数字活动。
WF 设计器生成 XAML 代码。XAML 并非组合活动的必需品。完全合法且合理的方式是在 C#/VB 中组合活动。事实上,MSDN WF 示例中的大多数复合活动都是用 C#/VB 编写的。Handmade.cs 中的 SequentialNumberGuess
在功能上等同于上一图中的 XAML 代码。
//Handmade.cs
using System;
using System.Threading;
using System.Collections.Generic;
using System.Activities;
using System.Activities.Expressions;
using System.Activities.Statements;
namespace Handmade {
public sealed class SequentialNumberGuess : Activity {
public InArgument<int> MaxNumber { get; set; }
public OutArgument<int> Turns { get; set; }
private Activity GetImplementation() {
var target = new Variable<int>();
var guess = new Variable<int>();
return new Sequence {
Variables = { target, guess },
Activities = {
new Assign<int> {
To = new OutArgument<int>(target),
Value = new InArgument<int>(ctx => new Random().Next(1, MaxNumber.Get(ctx) + 1))
},
new DoWhile {
Body = new Sequence {
Activities = {
new Prompt {
BookmarkName = "EnterGuess",
Text = new InArgument<string>(ctx =>
"Please enter a number between 1 and " + MaxNumber.Get(ctx)),
Result = new OutArgument<int>(guess)
},
new Assign<int> {
To = new OutArgument<int>(ctx => Turns.Get(ctx)),
Value = new InArgument<int>(ctx => Turns.Get(ctx) + 1)
},
new If {
Condition = new InArgument<bool>(ctx => guess.Get(ctx) != target.Get(ctx)),
Then = new If {
Condition = new InArgument<bool>(ctx => guess.Get(ctx) < target.Get(ctx)),
Then = new WriteLine { Text = "Your guess is too low."},
Else = new WriteLine { Text = "Your guess is too high."}
}
}
}
},
Condition = new LambdaValue<bool>(ctx => guess.Get(ctx) != target.Get(ctx))
}
}
};
}
private Func<Activity> _implementation;
protected override Func<Activity> Implementation {
get {
return _implementation ?? (_implementation = GetImplementation);
}
set { throw new NotSupportedException(); }
}
}
public sealed class Prompt : Activity<int> {
public InArgument<string> BookmarkName { get; set; }
public InArgument<string> Text { get; set; }
private Activity GetImplementation() {
return new Sequence {
Activities = {
new WriteLine {
Text = new InArgument<string>(ctx => Text.Get(ctx))
},
new ReadInt {
BookmarkName = new InArgument<string>(ctx => BookmarkName.Get(ctx)),
Result = new OutArgument<int>(ctx => Result.Get(ctx))
}
}
};
}
private Func<Activity> _implementation;
protected override Func<Activity> Implementation {
get {
return _implementation ?? (_implementation = GetImplementation);
}
set { throw new NotSupportedException(); }
}
}
public sealed class ReadInt : NativeActivity<int> {
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context) {
context.CreateBookmark(BookmarkName.Get(context), OnReadComplete);
}
protected override bool CanInduceIdle { get { return true; } }
void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state) {
Result.Set(context, (int)state);
}
}
class Program {
static void Main() {
var syncEvent = new AutoResetEvent(false);
var idleEvent = new AutoResetEvent(false);
var wfApp = new WorkflowApplication(new SequentialNumberGuess(),
new Dictionary<string, object>() { { "MaxNumber", 100 } });
wfApp.Completed = (WorkflowApplicationCompletedEventArgs e) => {
Console.WriteLine("Congratulations, you guessed the number in {0} turns.", e.Outputs["Turns"]);
syncEvent.Set();
};
wfApp.Aborted = (WorkflowApplicationAbortedEventArgs e) => {
Console.WriteLine(e.Reason);
syncEvent.Set();
};
wfApp.OnUnhandledException = (WorkflowApplicationUnhandledExceptionEventArgs e) => {
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
wfApp.Idle = (WorkflowApplicationIdleEventArgs e) => {
idleEvent.Set();
};
wfApp.Run();
var handles = new WaitHandle[] { syncEvent, idleEvent };
while (WaitHandle.WaitAny(handles) == 1) {
var isValidEntry = false;
while (!isValidEntry) {
int guess;
if (!int.TryParse(Console.ReadLine(), out guess)) {
Console.WriteLine("Please enter an integer.");
}
else {
isValidEntry = true;
wfApp.ResumeBookmark("EnterGuess", guess);
}
}
}
}
}
}
我们可以将活动分为两类:原始活动和复合活动。原始活动继承自 SA.CodeActivity
、SA.CodeActivity<T>
、SA.NativeActivity
、SA.NativeActivity<T>
等。复合活动直接继承自 SA.Activity
或 SA.Activity<T>
,由原始/复合活动组成。在 Handmade.cs 中,SequentialNumberGuess
和 Prompt
是复合活动,ReadInt
是原始活动。
如前述代码所示,在 C#(/VB) 中组合活动非常冗长。有没有更简化的方法?有!欢迎来到元编程世界,欢迎使用 Metah.W (MW)。
- 您需要 Visual Studio 2013;
- 下载并安装 最新的 Metah vsix 包;
- 打开 VS 2013 -> 新建项目 -> Visual C# -> Metah.W -> 创建一个 Metah.W 控制台应用程序(或者您可以打开 HelloMW 项目);
- 删除 Program.cs;
- 添加新项 -> Visual C# 项 -> Metah.W -> 创建一个名为 FirstLook.mw 的新 MW 文件;
- 将以下代码复制到 FirstLook.mw 中。
//FirstLook.mw
using System;
using System.Threading;
using System.Collections.Generic;
using System.Activities;
import HelloMW.FirstLook;
namespace HelloMW.FirstLook {
public sealed activity SequentialNumberGuess(int MaxNumber, out int Turns) {
int target, guess;
target = new Random().Next(1, MaxNumber) + 1;
do {
guess = new Prompt().Invoke("EnterGuess", "Please enter a number between 1 and " + MaxNumber);
Turns++;
if (guess != target) {
if (guess < target)
Console.WriteLine("Your guess is too low.");
else
Console.WriteLine("Your guess is too high.");
}
}
while (guess != target);
}
public sealed activity Prompt(string BookmarkName, string Text) as int {
Console.WriteLine(Text);
Result = new ReadInt().Invoke(BookmarkName);
}
public sealed class ReadInt : NativeActivity<int> {
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context) {
context.CreateBookmark(BookmarkName.Get(context), OnReadComplete);
}
protected override bool CanInduceIdle { get { return true; } }
void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state) {
Result.Set(context, (int)state);
}
}
class Program {
static void Main() {
var syncEvent = new AutoResetEvent(false);
var idleEvent = new AutoResetEvent(false);
var wfApp = new WorkflowApplication(new SequentialNumberGuess(),
new Dictionary<string, object>() { { "MaxNumber", 100 } });
wfApp.Completed = (WorkflowApplicationCompletedEventArgs e) => {
Console.WriteLine("Congratulations, you guessed the number in {0} turns.", e.Outputs["Turns"]);
syncEvent.Set();
};
wfApp.Aborted = (WorkflowApplicationAbortedEventArgs e) => {
Console.WriteLine(e.Reason);
syncEvent.Set();
};
wfApp.OnUnhandledException = (WorkflowApplicationUnhandledExceptionEventArgs e) => {
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
wfApp.Idle = (WorkflowApplicationIdleEventArgs e) => {
idleEvent.Set();
};
wfApp.Run();
var handles = new WaitHandle[] { syncEvent, idleEvent };
while (WaitHandle.WaitAny(handles) == 1) {
var isValidEntry = false;
while (!isValidEntry) {
int guess;
if (!int.TryParse(Console.ReadLine(), out guess)) {
Console.WriteLine("Please enter an integer.");
}
else {
isValidEntry = true;
wfApp.ResumeBookmark("EnterGuess", guess);
}
}
}
}
}
}
MW 继承自 C#,因此与任何 C# 代码兼容。就像 WF 设计器一样,MW 只能编写复合活动。在 FirstLook.mw 中,SequentialNumberGuess
和 Prompt
是用 MW 语法编写的(称之为 MW 活动)。MW 活动类似于函数。您可以声明零个或多个参数(MaxNumber
、Turns
、BookmarkName
等)和一个可选的返回类型(Prompt
中的 as int
)。在活动主体中,您可以声明变量(target
、guess
)并使用语句。MW 语句可以是 C# 表达式语句(target = new Random().Next(1, MaxNumber) + 1;
等)、已知语句(if-else、while、do-while、foreach、try-catch-finally 等)或特殊语句(parallel、pick、state-machine、delay、receive、send-reply 等)。因为 MW 活动类似于函数,所以您可以调用它并将返回值赋给变量/参数(guess = new Prompt().Invoke(...)
)。您甚至可以调用非 MW 活动(Result = new ReadInt().Invoke(BookmarkName)
)!最后,您可以像普通活动一样使用 MW 活动(new WorkflowApplication(new SequentialNumberGuess(), ...)
)。
构建项目,然后在解决方案资源管理器窗口中单击“显示所有文件”按钮。FirstLook.mw.cs 是由 MW 编译器从 FirstLook.mw 生成的。
//FirstLook.mw.cs, generated by the MW compiler
//The using directives are generated as is
namespace HelloMW.FirstLook
{
//Class ReadInt and Program are generated as is
public sealed class SequentialNumberGuess : global::System.Activities.Activity
{
public global::System.Activities.InArgument<int> MaxNumber { get; set; }
public global::System.Activities.OutArgument<int> Turns { get; set; }
private global::System.Activities.Activity __GetImplementation__()
{
global::System.Activities.Activity __vroot__;
{
var __v__0 = new global::System.Activities.Statements.Sequence();
var target = new global::System.Activities.Variable<int>();
__v__0.Variables.Add(target);
var guess = new global::System.Activities.Variable<int>();
__v__0.Variables.Add(guess);
__v__0.Activities.Add(new global::MetahWActionActivity(__ctx__ =>
{
target.SetEx(__ctx__, new Random().Next(1, MaxNumber.Get(__ctx__)) + 1);
}
));
var __v__1 = new global::System.Activities.Statements.DoWhile();
__v__1.Condition = new global::MetahWFuncActivity<bool>(__ctx__ => guess.Get(__ctx__) != target.Get(__ctx__));
{
var __v__2 = new global::System.Activities.Statements.Sequence();
__v__2.Activities.Add(new Prompt().Initialize(__activity2__ =>
{
__activity2__.BookmarkName = new global::System.Activities.InArgument<string>(new global::MetahWFuncActivity<string>(__ctx__ => "EnterGuess"));
__activity2__.Text = new global::System.Activities.InArgument<string>(new global::MetahWFuncActivity<string>(__ctx__ => "Please enter a number between 1 and " + MaxNumber.Get(__ctx__)));
__activity2__.Result = new global::System.Activities.OutArgument<int>(new global::MetahWLocationActivity<int>(guess));
}
));
__v__2.Activities.Add(new global::MetahWActionActivity(__ctx__ =>
{
Turns.SetEx(__ctx__, __val__ => ++__val__, true);
}
));
var __v__3 = new global::System.Activities.Statements.If();
__v__3.Condition = new global::System.Activities.InArgument<bool>(new global::MetahWFuncActivity<bool>(__ctx__ => guess.Get(__ctx__) != target.Get(__ctx__)));
var __v__4 = new global::System.Activities.Statements.If();
__v__4.Condition = new global::System.Activities.InArgument<bool>(new global::MetahWFuncActivity<bool>(__ctx__ => guess.Get(__ctx__) < target.Get(__ctx__)));
__v__4.Then = new global::MetahWActionActivity(__ctx__ =>
{
Console.WriteLine("Your guess is too low.");
}
);
__v__4.Else = new global::MetahWActionActivity(__ctx__ =>
{
Console.WriteLine("Your guess is too high.");
}
);
__v__3.Then = __v__4;
__v__2.Activities.Add(__v__3);
__v__1.Body = __v__2;
}
__v__0.Activities.Add(__v__1);
__vroot__ = __v__0;
}
return __vroot__;
}
private global::System.Func<global::System.Activities.Activity> __implementation__;
protected override global::System.Func<global::System.Activities.Activity> Implementation
{
get
{
return __implementation__ ?? (__implementation__ = __GetImplementation__);
}
set
{
throw new global::System.NotSupportedException();
}
}
}
public sealed class Prompt : global::System.Activities.Activity<int>
{
public global::System.Activities.InArgument<string> BookmarkName { get; set; }
public global::System.Activities.InArgument<string> Text { get; set; }
private global::System.Activities.Activity __GetImplementation__()
{
global::System.Activities.Activity __vroot__;
var __v__0 = new global::System.Activities.Statements.Sequence();
__v__0.Activities.Add(new global::MetahWActionActivity(__ctx__ =>
{
Console.WriteLine(Text.Get(__ctx__));
}
));
__v__0.Activities.Add(new ReadInt().Initialize(__activity2__ =>
{
__activity2__.BookmarkName = new global::System.Activities.InArgument<string>(new global::MetahWFuncActivity<string>(__ctx__ => BookmarkName.Get(__ctx__)));
__activity2__.Result = new global::System.Activities.OutArgument<int>(new global::MetahWLocationActivity<int>(Result));
}
));
__vroot__ = __v__0;
return __vroot__;
}
private global::System.Func<global::System.Activities.Activity> __implementation__;
protected override global::System.Func<global::System.Activities.Activity> Implementation
{
get
{
return __implementation__ ?? (__implementation__ = __GetImplementation__);
}
set
{
throw new global::System.NotSupportedException();
}
}
}
}
//Helper code(extension methods, MetahWActionActivity, MetahWFuncActivity<T>, etc) is omitted
这是另一个例子
//SecondLook.mw
using System;
using System.Activities;
namespace HelloMW.SecondLook
{
class Program
{
static void Main()
{
WorkflowInvoker.Invoke(new Drive());
}
}
public enum DriveAction { Neutral = 1, Forward, Reverse, TurnOff }
activity Drive()
{
bool isMoved;
isMoved = false;
while (!isMoved)
{
statemachine goto InPark
{
DriveAction action;
InPark:
~> Console.WriteLine("Enter InPark");
<~ Console.WriteLine("Exit InPark");
on action = new GetDriveAction().Invoke();
if (action == DriveAction.Neutral) goto InNeutral;
InNeutral:
~> Console.WriteLine("Enter InNeutral");
<~ Console.WriteLine("Exit InNeutral");
on action = new GetDriveAction().Invoke();
{
if (action == DriveAction.Forward) goto InForward;
if (action == DriveAction.Reverse) goto InReverse;
if (action == DriveAction.TurnOff) goto TurnedOff;
}
InForward:
~> { Console.WriteLine("Enter InForward"); isMoved = true; }
<~ Console.WriteLine("Exit InForward");
on action = new GetDriveAction().Invoke();
if (action == DriveAction.Neutral) goto InNeutral;
InReverse:
~> { Console.WriteLine("Enter InReverse"); isMoved = true; }
<~ Console.WriteLine("Exit InReverse");
on action = new GetDriveAction().Invoke();
if (action == DriveAction.Neutral) goto InNeutral;
TurnedOff: break
~> Console.WriteLine("TurnedOff");
}
Console.WriteLine("isMoved: " + isMoved);
}
}
activity GetDriveAction() as DriveAction
{
delay TimeSpan.FromSeconds(1);
Result = (DriveAction)_random.Next((int)DriveAction.Neutral, (int)DriveAction.TurnOff + 1);
Console.WriteLine("!action: " + Result.ToString());
}
##
{
private static readonly Random _random = new Random((int)DateTime.Now.Ticks);
}
}
强烈建议您花些时间在 Visual Studio 中阅读生成的代码。
到目前为止,有三种编写复合活动的方法:使用 WF 设计器(XAML)、使用 C#/VB 和使用 Metah.W。我不是 WF 设计器的粉丝,因为它非常笨拙:在 WF 设计器中工作就像戴着厚手套在迷你键盘上打字。如果您喜欢编写 IL 代码,那您肯定会喜欢在 C#/VB 中编写复合活动。元编程优雅高效,我认为是最佳选择。
在项目编译过程中,首先,将 MW 文件(.mw)和项目的 C# 文件发送给 MW 编译器,MW 编译器解析并分析 MW 文件,并从它们生成 C# 文件(.mw.cs),然后所有 C# 文件(生成的和正常的)都发送给 C# 编译器生成最终的 CLR 程序集。
MW 项目继承自 C# 项目。任何现有的 C# 项目都可以通过在 .csproj 文件末尾插入以下代码来支持 MW。
<Import Project="$([System.IO.Directory]::GetFiles($([System.IO.Path]::Combine($([System.Environment]::GetFolderPath(SpecialFolder.LocalApplicationData)), `Microsoft\VisualStudio\12.0\Extensions`)), `Metah.W.targets`, System.IO.SearchOption.AllDirectories))" />
活动
MW 继承自 C#。除了 C# 的保留关键字外,MW 还增加了以下保留关键字:activity
、cancellable
、confirm
、compensable
、compensate
、contentcorr
、delay
、flow
、fif
、fswitch
、import
、nopersist
、parallel
、persist
、pforecah
、pick
、receive
、receivereply
、send
、sendreply
、statemachine
、terminate
、transacted
和 transactedreceive
。这意味着如果标识符与其中之一相等,您必须使用 @
来转义它(@activity
、@pick
等)。
语法
compilation-unit:
C#-extern-alias-directive* (import | C#-using-directive)* C#-global-attribute-list* namespace-member*
;
import:
'import' C#-namespace-name? ';'
;
namespace-member:
namespace | activity | C#-type-declaration
;
namespace:
'namespace' C#-namespace-name '{' C#-extern-alias-directive* C#-using-directive* namespace-member* '}'
;
activity:
C#-attribute-list* C#-class-modifier* 'activity' C#-identifier C#-type-parameter-list?
'(' (parameter (',' parameter)*)? ')' return-type?
(':' C#-interface-name (',' C#-interface-name)*)? C#-type-parameter-constraint-clause*
sequence-statement csharp-class-members-block?
;
parameter:
C#-attribute-list* ('out' | 'ref')? C#-type-name C#-identifier
;
return-type:
'as' C#-type-name
;
sequence-statement:
'{' variable* statement* '}'
;
csharp-class-members-block:
'##' '{' C#-class-member* '}'
;
MW 活动是 C# 类和函数的混合体。示例:
public class SomeAttribute1Attribute : Attribute { }
public class SomeAttribute2Attribute : Attribute { }
public interface ISomeInterface
{
int Method();
}
public class SomeClass<T> where T : class, new() { }
[SomeAttribute1]
internal sealed activity A1<T>([RequiredArgument]int Arg1, [SomeAttribute2]out T Arg2,
ref SomeClass<T> Arg3) as IEnumerable<T> : ISomeInterface where T : class, new()
{
}
##
{
private int _i;
int ISomeInterface.Method() { return ++_i; }
}
public sealed partial activity A2<T>(int Arg1, T Arg2) : ISomeInterface
{
}
partial class A2<T>
{
private int _i;
int ISomeInterface.Method() { return ++_i; }
}
MW 编译器将 MW 活动转换为同名的活动类,C# 代码按原样生成。
public class SomeAttribute1Attribute : Attribute { }
public class SomeAttribute2Attribute : Attribute { }
public interface ISomeInterface
{
int Method();
}
public class SomeClass<T> where T : class, new() { }
[SomeAttribute1]
internal sealed class A1<T> : global::System.Activities.Activity<IEnumerable<T>>, ISomeInterface where T : class, new()
{
private int _i;
int ISomeInterface.Method() { return ++_i; }
[RequiredArgument]
public global::System.Activities.InArgument<int> Arg1 { get; set; }
[SomeAttribute2]
public global::System.Activities.OutArgument<T> Arg2 { get; set; }
public global::System.Activities.InOutArgument<SomeClass<T>> Arg3 { get; set; }
}
public sealed partial class A2<T> : global::System.Activities.Activity, ISomeInterface
{
public global::System.Activities.InArgument<int> Arg1 { get; set; }
public global::System.Activities.InArgument<T> Arg2 { get; set; }
}
partial class A2<T>
{
private int _i;
int ISomeInterface.Method() { return ++_i; }
}
如果 MW 活动声明了返回类型,则活动类继承自 SA.Activity<ReturnTypeName>
;否则继承自 SA.Activity
。
MW 参数的方向可以是 in(默认)、out(通过 out
修饰符)或 in-out(通过 ref
修饰符)。从 MW 参数生成一个类型为 SA.InArgument<ParaTypeName>
、SA.OutArgument<ParaTypeName>
或 SA.InOutArgument<ParaTypeName>
的同名公共属性。因此,强烈建议将参数名首字母大写以符合 .NET 约定。
我们可以认为 MW 活动是活动类的语法糖。然而,MW 编译器向活动类添加了一个概念性(或虚构)的 Invoke
方法。如果活动声明了返回类型,则该方法的返回类型与之相同;否则为 void
。方法的参数与活动的参数相同,如果所有方向都是 in,则每个参数都有默认值。从 MW 元编程的角度来看,A1<T>
和 A2
的活动类是:
[SomeAttribute1]
internal sealed class A1<T> : global::System.Activities.Activity<IEnumerable<T>>, ISomeInterface where T : class, new()
{
private int _i;
int ISomeInterface.Method() { return ++_i; }
[RequiredArgument]
public global::System.Activities.InArgument<int> Arg1 { get; set; }
[SomeAttribute2]
public global::System.Activities.OutArgument<T> Arg2 { get; set; }
public global::System.Activities.InOutArgument<SomeClass<T>> Arg3 { get; set; }
internal IEnumerable<T> Invoke(int Arg1, out T Arg2, ref SomeClass<T> Arg3)
{
throw new global::System.NotImplementedException();
}
}
public sealed partial class A2<T> : global::System.Activities.Activity, ISomeInterface
{
public global::System.Activities.InArgument<int> Arg1 { get; set; }
public global::System.Activities.InArgument<T> Arg2 { get; set; }
internal void Invoke(int Arg1 = default(int), T Arg2 = default(T))
{
throw new global::System.NotImplementedException();
}
}
Invoke
方法仅在元编程阶段存在,与生成的代码无关。
MW 活动可以调用非 MW 活动。非 MW 活动可以是同一项目中的 C# 代码,也可以是引用程序集中的 IL 代码。要调用非 MW 活动,您必须先导入它。考虑以下非 MW 活动:
namespace NS1
{
public class A3 : System.Activities.CodeActivity
{
public System.Activities.InArgument<int> Arg1 { get; set; }
public System.Activities.InOutArgument<string> Arg2 { get; set; }
...
}
namespace NS2
{
public class A4<T> : System.Activities.NativeActivity<IEnumerable<T>>
{
public System.Activities.InArgument<int> Arg1 { get; set; }
public System.Activities.InArgument<T> Arg2 { get; set; }
...
}
}
}
使用 import
导入命名空间中的所有非 MW 活动。
import NS1;
import NS1.NS2;
导入非 MW 活动意味着 MW 编译器将一个概念性(或虚构)的 Invoke
方法添加到类中。如果类继承自 SA.Activity<T>
,则该方法的返回类型为 T
;否则为 void
。所有类型为 SA.InArgument<T>
、SA.OutArgument<T>
或 SA.InOutArgument<T>
的公共属性将转换为该方法的参数。从 MW 元编程的角度来看,A3
和 A4<T>
是:
namespace NS1
{
public class A3 : System.Activities.CodeActivity
{
public System.Activities.InArgument<int> Arg1 { get; set; }
public System.Activities.InOutArgument<string> Arg2 { get; set; }
public void Invoke(int Arg1, ref string Arg2)
{
throw new global::System.NotImplementedException();
}
...
}
namespace NS2
{
public class A4<T> : System.Activities.NativeActivity<IEnumerable<T>>
{
public System.Activities.InArgument<int> Arg1 { get; set; }
public System.Activities.InArgument<T> Arg2 { get; set; }
public IEnumerable<T> Invoke(int Arg1 = default(int), T Arg2 = default(T))
{
throw new global::System.NotImplementedException();
}
...
}
}
}
import
具有项目范围的影响,同一个命名空间可以重复导入。要导入全局命名空间,请使用:
import;
从调用者的角度来看,Invoke
方法在 MW 和非 MW 活动之间没有区别。
变量、已知语句和 C# 表达式语句
语法
variable:
C#-type-name C#-identifier (',' C#-identifier)* ';'
;
statement:
csharp-expression-statement | csharp-block-statement | empty-statement | sequence-statement | if-statement
| while-statement | do-while-statement | foreach-statement | switch-statement | throw-statement | try-statement
| delay-statement | parallel-statement | parallel-foreach-statement | pick-statement | state-machine-statement
| flow-statement | transacted-statement | cancellable-statement | compensable-statement | confirm-statement
| compensate-statement | persist-statement | no-persist-statement | terminate-statement | receive-statement
| send-reply-statement | send-statement | receive-reply-statement | content-correlation-statement
| transacted-receive-statement
;
csharp-expression-statement:
C#-expression-statement
;
csharp-block-statement :
'##' C#-block-statement
;
empty-statement:
';'
;
if-statement:
'if' '(' C#-expression ')' statement ('else' statement)?
;
while-statement:
'while' '(' C#-expression ')' statement
;
do-while-statement:
'do' statement 'while' '(' C#-expression ')' ';'
;
foreach-statement:
'foreach' '(' C#-type-name C#-identifier 'in' C#-expression ')' statement
;
switch-statement:
'switch' '(' C#-expression ')' '{' switch-statement-case* switch-statement-default? '}'
;
switch-statement-case:
'case' C#-expression ':' statement? 'break' ';'
;
switch-statement-default:
'default' ':' statement? 'break' ';'
;
throw-statement:
'throw' C#-expression? ';'
;
try-statement:
'try' sequence-statement (try-statement-catch+ try-statement-finally? | try-statement-finally)
;
try-statement-catch:
'catch' '(' C#-type-name C#-identifier ')' sequence-statement
;
try-statement-finally:
'finally' sequence-statement
;
MW 没有 return 语句。如果活动声明了返回类型,MW 编译器会自动声明一个名为 Result
的保留 out 参数用于返回该值。
activity GetDateTime() as DateTime
{
Result = DateTime.Now;
}
变量在 {
之后、任何语句之前声明。您不能在声明时为其赋值。变量/参数的作用域规则与 C# 相同。
activity Demo(int Arg1, bool Arg1/*ERROR: duplicate parameter name*/,
int Result/*ERROR: duplicate parameter name*/) as long
{
string Arg1;//ERROR: duplicate variable name
int i;
if (...)
{
int i;//ERROR: duplicate variable name
int i2;
}
else
{
int i2;//OK
}
}
与 C# 不同,您可以访问(读取)一个变量或 out 参数而无需先为其赋值,它会返回默认值。
activity NeedNotAssignValues(out SomeClass<Exception> Arg1) as bool
{
string s;
Console.WriteLine(Arg1 == default(SomeClass<Exception>));//True
Console.WriteLine(Result == default(bool));//True
Console.WriteLine(s == default(string));//True
}
这也意味着您无需为 out 参数赋值。
编写 MW 活动意味着将其组合自其他活动。每个 MW 语句代表一个活动。
MW 语句 | 活动类 |
csharp-expression-statement |
MetahWActionActivity /MetahWSequenceActivity /被调用的活动类 |
csharp-block-statement |
MetahWActionActivity |
empty-statement |
MetahWActionActivity |
sequence-statement |
SAS.Sequence |
if-statement |
SAS.If |
while-statement |
SAS.While |
do-while-statement |
SAS.DoWhile |
foreach-statement |
SAS.ForEach<T> |
switch-statement |
SAS.Switch<T> |
throw-statement |
SAS.Throw/Rethrow |
try-statement |
SAS.TryCatch |
delay-statement |
SAS.Delay |
parallel-statement |
SAS.Parallel |
parallel-foreach-statement |
SAS.ParallelForEach<T> |
pick-statement |
SAS.Pick |
state-machine-statement |
SAS.StateMachine |
flow-statement |
SAS.Flowchart |
transacted-statement |
SAS.TransactionScope |
cancellable-statement |
SAS.CancellationScope |
compensable-statement |
SAS.CompensableActivity |
confirm-statement |
SAS.Confirm |
compensate-statement |
SAS.Compensate |
persist-statement |
SAS.Persist |
no-persist-statement |
SAS.NoPersistScope |
terminate-statement |
SAS.TerminateWorkflow |
receive-statement |
SSA.Receive |
send-reply-statement |
SSA.SendReply |
send-statement |
SSA.Send |
receive-reply-statement |
SSA.ReceiveReply |
content-correlation-statement |
SSA.InitializeCorrelation |
transacted-receive-statement |
SSA.TransactedReceiveScope |
MW 编译器会尝试进行一些优化:两个或多个连续的 csharp-expression-statement
/csharp-block-statement
可能会生成为单个 MetahWActionActivity
;如果一个 sequence-statement
只有一个成员语句,则该 sequence-statement
会被丢弃,而使用其成员语句。
empty-statement
、sequence-statement
、if-statement
、while-statement
、do-while-statement
、foreach-statement
、switch-statement
、throw-statement
和 try-statement
是已知的。
activity WellknownStatements()
{
try
{
foreach(int i in new[]{2,3,5,7,11,13,17})
{
Console.Write(i + ": ");
switch(i)
{
case 2:
Console.WriteLine("even number");
break;
case 7:
Console.WriteLine("lucky number");
break;
case 13:
{
Console.WriteLine("bad number");
throw new ArgumentException("bad number 13");
}
break;
default:
Console.WriteLine("normal number");
break;
}
if(i == 2)
{
int j;
j = i;
while(j++ < 7);
}
}
}
catch(ArgumentException ex)
{
Console.WriteLine(ex);
}
catch(Exception ex)
{
throw;
}
finally
{
Console.WriteLine("finally");
}
}
您可以随意使用 csharp-expression-statement
。
以下代码演示了 csharp-block-statement
。
activity CSharpBlock(int Arg1, string Arg2) as bool
{
int i;
##
{
if(Arg1 < 0) throw new ArgumentOutOfRangeException("Arg1");//They are C# statements
Result = int.TryParse(Arg2, out i);
var i2 = Arg1 + i;//It's a C# variable
for(; i < i2; i++);//They are C# statements
//...
}
//...
}
考虑以下代码:
activity UseMWStatements(int Arg1, string Arg2)
{
if(Arg1 < 0) throw new ArgumentOutOfRangeException("Arg1");//They are MW statements
if(string.IsNullOrEmpty(Arg2)) throw new ArgumentNullException("Arg2");//They are MW statements
}
activity UseCSStatements(int Arg1, string Arg2)
{
##
{
if(Arg1 < 0) throw new ArgumentOutOfRangeException("Arg1");//They are C# statements
if(string.IsNullOrEmpty(Arg2)) throw new ArgumentNullException("Arg2");//They are C# statements
}
}
MW 语句比相应的 C# 语句慢得多,因此建议尽可能使用 C# 语句来提高性能。
您可以通过调用 Invoke
方法来调用另一个活动。活动调用只是一个 C# 调用表达式,因此您可以随意使用它。
activity TryParse(string String, out int Value) as bool
{
Result = int.TryParse(String, out Value);
}
activity RepeatString(String String, int Count) as string
{
##
{
for (var i = 0; i < Count; i++)
Result += String;
}
}
activity GetUpperChars(string String, out char[] Chars) as int
{
Chars = String.Where(ch => {
var isUpper = char.IsUpper(ch);
if (isUpper) Result++;
return isUpper;
}).ToArray();
}
activity InvokeActivities(string String, string Count)
{
int count;
char[] chars;
if (new TryParse().Invoke(Count, out count) && count > 0
&& new GetUpperChars().Invoke(new RepeatString().Invoke(String, count), out chars) > 0)
##
{
foreach(var ch in chars)
Console.WriteLine(ch);
}
else
Console.WriteLine("Invalid arguments. String: {0}, Count: {1}", String, Count);
}
活动调用不能用于 csharp-block-statement
、lambda 表达式、匿名方法或查询表达式体。
activity GetChars() as IEnumerable<char>
{
Result = "AbcD";
}
activity IsUpper(char Char) as bool
{
Result = char.IsUpper(Char);
}
activity Demo()
{
IEnumerable<char> chars;
##
{
chars = new GetChars().Invoke();//ERROR: activity invocations cannot be used in C# block statements
}
chars = "AbcD".Where(ch => new IsUpper().Invoke(ch));//ERROR: activity invocations cannot be used in lambda expressions
chars = from ch in "AbcD" where new IsUpper().Invoke(ch) select ch;//ERROR: activity invocations cannot be used in query expression bodies
chars = from ch in new GetChars().Invoke() where char.IsUpper(ch) select ch;//OK. Activity invocations CAN be used in query expression from clauses
}
活动不能直接或间接调用自身。
//Fibonacci() can pass the compilation but falls into a dead loop when running
activity Fibonacci(int Value) as int
{
Result = Value <= 1 ? 1 : new Fibonacci().Invoke(Value - 1) + new Fibonacci().Invoke(Value - 2);
}
调用活动意味着包含它,活动不能直接或间接包含自身。
活动委托和调用
MW 编译器会为每个活动委托添加一个概念性(或虚构)的 Invoke
方法。从 MW 元编程的角度来看,活动委托是:
namespace System.Activities
{
public sealed class ActivityAction : ...
{
public void Invoke() { throw new NotImplementedException();}
...
}
public sealed class ActivityAction<T1> : ...
{
public void Invoke(T1 arg1) { throw new NotImplementedException();}
...
}
public sealed class ActivityAction<T1, T2> : ...
{
public void Invoke(T1 arg1, T2 arg2) { throw new NotImplementedException();}
...
}
...
public sealed class ActivityAction<T1, T2, ..., T16> : ...
{
public void Invoke(T1 arg1, T2 arg2, ..., T16 arg16) { throw new NotImplementedException();}
...
}
public sealed class ActivityFunc<TResult> : ...
{
public TResult Invoke() { throw new NotImplementedException();}
...
}
public sealed class ActivityFunc<T1, TResult> : ...
{
public TResult Invoke(T1 arg1) { throw new NotImplementedException();}
...
}
public sealed class ActivityFunc<T1, T2, TResult> : ...
{
public TResult Invoke(T1 arg1, T2 arg2) { throw new NotImplementedException();}
...
}
...
public sealed class ActivityFunc<T1, T2, ..., T16, TResult> : ...
{
public TResult Invoke(T1 arg1, T2 arg2, ..., T16 arg16) { throw new NotImplementedException();}
...
}
}
MW 编译器会生成一些扩展方法,用于从活动创建活动委托。
//global namespace
internal static class MetahWExtensions
{
internal static ActivityAction ToAction(this Activity activity) {...}
internal static ActivityAction<T1> ToAction<TActivity, T1>(this TActivity activity, Action<TActivity, InArgument<T1>> initializer) where TActivity : Activity {...}
internal static ActivityAction<T1, T2> ToAction<TActivity, T1, T2>(this TActivity activity, Action<TActivity, InArgument<T1>, InArgument<T2>> initializer) where TActivity : Activity {...}
...
internal static ActivityAction<T1, T2, ..., T16> ToAction<TActivity, T1, T2, ..., T16>(this TActivity activity, Action<TActivity, InArgument<T1>, InArgument<T2>, ..., InArgument<T16>> initializer) where TActivity : Activity {...}
internal static ActivityFunc<TResult> ToFunc<TActivity, TResult>(this TActivity activity, Action<TActivity, OutArgument<TResult>> initializer) where TActivity : Activity {...}
internal static ActivityFunc<T1, TResult> ToFunc<TActivity, T1, TResult>(this TActivity activity, Action<TActivity, InArgument<T1>, OutArgument<TResult>> initializer) where TActivity : Activity {...}
internal static ActivityFunc<T1, T2, TResult> ToFunc<TActivity, T1, T2, TResult>(this TActivity activity, Action<TActivity, InArgument<T1>, InArgument<T2>, OutArgument<TResult>> initializer) where TActivity : Activity {...}
...
internal static ActivityFunc<T1, T2, ..., T16, TResult> ToFunc<TActivity, T1, T2, ..., T16, TResult>(this TActivity activity, Action<TActivity, InArgument<T1>, InArgument<T2>, ..., InArgument<T16>, OutArgument<TResult>> initializer) where TActivity : Activity {...}
}
以下代码演示了完整的用法。
activity InvokeDelegates()
{
if(Action != null)
Action.Invoke();
if(Action1 != null)
Action1.Invoke(1);
if(Action2 != null)
Action2.Invoke(2, "Action2");
if(Func != null)
Console.WriteLine("Invoker(): " + Func.Invoke());
if(Func1 != null)
Console.WriteLine("Invoker(): " + Func1.Invoke(11));
if(Func2 != null)
Console.WriteLine("Invoker(): " + Func2.Invoke(12, "Func2"));
}
##
{
public ActivityAction Action { get; set; }//Activity delegates must be passed via properties or fields
public ActivityAction<int> Action1 { get; set; }
public ActivityAction<int, string> Action2 { get; set; }
public ActivityFunc<string> Func { get; set; }
public ActivityFunc<int, string> Func1 { get; set; }
public ActivityFunc<int, string, string> Func2 { get; set; }
}
activity Display(string String, int Int32) as string
{
Result = string.Format("String: {0}, Int32: {1}", String, Int32);
Console.WriteLine("Display(): " + Result);
}
activity ActivityDelegatesTest()
{
new InvokeDelegates {
Action = new Display().ToAction(),
Action1 = new Display().ToAction<Display, int>((act, arg1) => {
act.Int32 = arg1;
}),
Action2 = new Display().ToAction<Display, int, string>((act, arg1, arg2) => {
act.Int32 = arg1;
act.String = arg2;
}),
Func = new Display().ToFunc<Display, string>((act, result) => {
act.Result = result;
}),
Func1 = new Display().ToFunc<Display, int, string>((act, arg1, result) => {
act.Int32 = arg1;
act.Result = result;
}),
Func2 = new Display().ToFunc<Display, int, string, string>((act, arg1, arg2, result) => {
act.Int32 = arg1;
act.String = arg2;
act.Result = result;
}),
}.Invoke();
/*output:
Display(): String: , Int32: 0
Display(): String: , Int32: 1
Display(): String: Action2, Int32: 2
Display(): String: , Int32: 0
Invoker(): String: , Int32: 0
Display(): String: , Int32: 11
Invoker(): String: , Int32: 11
Display(): String: Func2, Int32: 12
Invoker(): String: Func2, Int32: 12
*/
}
Delay、parallel 和 parallel-foreach 语句
语法
delay-statement:
'delay' C#-expression ';'
;
parallel-statement:
'parallel' sequence-statement until-clause?
;
parallel-foreach-statement:
'pforeach' '(' C#-type-name C#-identifier 'in' C#-expression ')' statement until-clause?
;
until-clause:
'until' C#-expression ';'
;
delay-statement
的表达式类型必须是 System.TimeSpan
。until-clause
的表达式类型必须是 bool
。对于 parallel-statement
,每个成员语句都是一个分支;对于 parallel-foreach-statement
,每个项都是一个分支。parallel(-foreach)-statement
并行执行所有分支。一个分支结束后,如果存在 until-clause
并且其计算结果为 true,则 parallel(-foreach)-statement
结束,所有其他分支将被取消;否则,parallel(-foreach)-statement
将等待另一个分支结束,并重新计算 until-clause
(如果存在),直到所有分支结束。示例:
activity Delay(int Seconds)
{
Console.WriteLine("Begin to delay {0} seconds", Seconds);
delay TimeSpan.FromSeconds(Seconds);
Console.WriteLine("End delaying {0} seconds", Seconds);
}
activity Parallel()
{
parallel
{
//branch 1
new Delay().Invoke(4);
//branch 2
new Delay().Invoke(2);
}
/*output:
Begin to delay 4 seconds
Begin to delay 2 seconds
End delaying 2 seconds
End delaying 4 seconds
*/
}
activity Parallel2()
{
parallel
{
//branch 1
new Delay().Invoke(4);
//branch 2
new Delay().Invoke(2);
}
until true;
/*output:
Begin to delay 4 seconds
Begin to delay 2 seconds
End delaying 2 seconds
*/
}
activity ParallelForeach()
{
pforeach(int seconds in new []{4, 2, 6})
new Delay().Invoke(seconds);
/*output:
Begin to delay 6 seconds
Begin to delay 2 seconds
Begin to delay 4 seconds
End delaying 2 seconds
End delaying 4 seconds
End delaying 6 seconds
*/
}
activity ParallelForeach2()
{
pforeach(int seconds in new []{4, 2, 6})
new Delay().Invoke(seconds);
until true;
/*output:
Begin to delay 6 seconds
Begin to delay 2 seconds
Begin to delay 4 seconds
End delaying 2 seconds
*/
}
Pick 语句
语法
pick-statement:
'pick' '{' pick-statement-branch* '}'
;
pick-statement-branch:
trigger-clause action-clause?
| '{' variable* trigger-clause action-clause? '}'
;
trigger-clause:
'on' statement
;
action-clause:
'do' statement
;
您可以在 {
之后声明变量,pick-statement
除外。这是因为 SAS.Pick
不能包含变量,这可能是 Microsoft 的疏忽。pick-statement
并行执行所有 trigger-clause
。一个触发器结束后,所有其他触发器都会被取消,并执行相应的 action-clause
(如果存在)。action-clause
结束后,pick-statement
结束。示例:
activity Pick()
{
pick
{
//brach 1
on new Delay().Invoke(4);
do Console.WriteLine("After 4 seconds");
//brach 2
on new Delay().Invoke(2);
do Console.WriteLine("After 2 seconds");
}
/*output:
Begin to delay 4 seconds
Begin to delay 2 seconds
End delaying 2 seconds
After 2 seconds
*/
}
状态机语句
语法
state-machine-statement:
'statemachine' goto-clause? '{' variable* state-machine-statement-node+ '}'
;
state-machine-statement-node:
state-machine-statement-common-node | state-machine-statement-final-node
;
state-machine-statement-final-node:
label-clause 'break' (';' | entry-clause)
;
label-clause:
C#-identifier ':'
;
goto-clause:
'goto' C#-identifier
;
entry-clause:
'~>' statement
;
exit-clause:
'<~' statement
;
state-machine-statement-common-node:
label-clause
( entry-clause? exit-clause? state-machine-statement-transition+
| '{' variable* entry-clause? exit-clause? state-machine-statement-transition+ '}' )
;
state-machine-statement-transition:
state-machine-statement-transition-with-trigger | state-machine-statement-transition-body
;
state-machine-statement-transition-with-trigger:
trigger-clause (state-machine-statement-transition-body | '{' state-machine-statement-transition-body+ '}')
;
state-machine-statement-transition-body:
condition-clause? action-clause? goto-clause ';'
;
condition-clause:
'if' '(' C#-expression ')'
;
state-machine-statement
必须至少有一个公共节点和一个终止节点。每个节点由唯一的标签标识。如果状态机声明了 goto-clause
(它必须引用一个公共节点),则执行将转移到该公共节点;否则,转移到第一个声明的公共节点。在进入节点后,将执行 entry-clause
(如果存在)。state-machine-statement-transition
将执行转移到另一个节点(可能是自身节点)。没有 trigger-clause
的转换就像有一个虚构的立即结束的触发器。公共节点的所有触发器都并行执行。在一个触发器结束后(其他触发器不会被取消),将按声明的顺序计算触发器的 condition-clause
。没有 condition-clause
的转换体就像有一个始终返回 true 的虚构条件。如果条件计算结果为 true,则执行 exit-clause
(如果存在),然后执行相应的 action-clause
(如果存在),并将执行转移到相应 goto-clause
指定的节点(其他运行中的触发器将被取消)。如果条件计算结果为 false,则计算下一个条件。如果所有条件计算结果都为 false,则该触发器将再次执行。在终止节点结束后,状态机结束。除了 SecondLook.mw 之外,这里还有另一个示例:
activity DelayEx(int Seconds) as int
{
Console.WriteLine("Begin to delay {0} seconds", Seconds);
delay TimeSpan.FromSeconds(Seconds);
Result = _random.Next(5);
Console.WriteLine("End delaying {0} seconds. Result: {1}", Seconds, Result);
}
##
{
private static readonly Random _random = new Random((int)DateTime.Now.Ticks);
}
activity StateMachine(bool Arg1)
{
statemachine
{
Node1:
~> Console.WriteLine("~> Node1");
<~ Console.WriteLine("<~ Node1");
if(!Arg1) do Console.WriteLine("Node1: goto Node2"); goto Node2;
if(Arg1) do Console.WriteLine("Node1: goto Node3"); goto Node3;
Node2:
{
int value;
~> Console.WriteLine("~> Node2");
<~ Console.WriteLine("<~ Node2");
on value = new DelayEx().Invoke(5);
{
if(value == 3) do Console.WriteLine("Node2.delay(5).value==3: goto Node3"); goto Node3;
if(value == 4) do Console.WriteLine("Node2.delay(5).value==4: goto Node4"); goto Node4;
}
on value = new DelayEx().Invoke(3);
if(value == 2) do Console.WriteLine("Node2.delay(3).value==2: goto Node2"); goto Node2;
on value = new DelayEx().Invoke(20);
do Console.WriteLine("Node2.delay(20): goto Final"); goto Final;
}
Node3:
{
int value;
~> Console.WriteLine("~> Node3");
<~ Console.WriteLine("<~ Node3");
on value = new DelayEx().Invoke(1);
{
if(value < 3) do Console.WriteLine("Node3.delay(1).value<3: goto Node2"); goto Node2;
if(value >= 3) do Console.WriteLine("Node3.delay(1).value>=3: goto Final"); goto Final;
}
}
Node4:
~> Console.WriteLine("~> Node4");
<~ Console.WriteLine("<~ Node4");
goto Final;
Final: break
~> Console.WriteLine("~> Final");
}
/*possible output:
~> Node1
<~ Node1
Node1: goto Node2
~> Node2
Begin to delay 20 seconds
Begin to delay 3 seconds
Begin to delay 5 seconds
End delaying 3 seconds. Result: 2
<~ Node2
Node2.delay(3).value==2: goto Node2
~> Node2
Begin to delay 20 seconds
Begin to delay 3 seconds
Begin to delay 5 seconds
End delaying 3 seconds. Result: 4
Begin to delay 3 seconds
End delaying 5 seconds. Result: 4
<~ Node2
Node2.delay(5).value==4: goto Node4
~> Node4
<~ Node4
~> Final
*/
}
Flow 语句
语法
flow-statement:
'flow' goto-clause? '{' variable* flow-statement-node+ '}'
;
flow-statement-node:
flow-statement-step-node | flow-statement-if-node | flow-statement-switch-node
;
flow-statement-step-node:
label-clause statement flow-statement-jump
;
flow-statement-if-node:
label-clause 'fif' '(' C#-expression ')' flow-statement-jump ('else' flow-statement-jump)?
;
flow-statement-switch-node:
label-clause 'fswitch' '(' C#-expression ')'
'{' flow-statement-switch-node-case* flow-statement-switch-node-default? '}'
;
flow-statement-switch-node-case:
'case' C#-expression ':' flow-statement-jump
;
flow-statement-switch-node-default:
'default' ':' flow-statement-jump
;
flow-statement-jump:
(goto-clause | 'break') ';'
;
每个流节点都由唯一的标签标识。如果 flow-statement
声明了 goto-clause
,则执行将转移到指定的节点;否则,转移到第一个节点。break
表示退出流。如果 fif
的条件计算结果为 false 且没有 else
子句,或者如果不存在 switch-default 且没有与 switch 值匹配的 switch-case,则 flow-statement
结束。示例:
activity Flow(int Number)
{
flow goto Begin
{
Begin:
Console.Write(Number + ": ");
goto IsValid;
IsValid:
fif (Number > 0 && Number < 20) goto Evaluate;
else goto InvalidNumber;
Evaluate:
fswitch(Number)
{
case 7: goto LuckyNumber;
case 13: goto BadNumber;
default: goto NormalNumber;
}
LuckyNumber:
Console.WriteLine("lucky number");
break;
BadNumber:
Console.WriteLine("bad number");
break;
NormalNumber:
{
Console.WriteLine("normal number");
Number++;
}
goto Begin;
InvalidNumber:
Console.WriteLine("invalid number");
break;
}
}
activity FlowTest()
{
foreach(int number in new int[]{7, 0, 13, 5})
new Flow().Invoke(number);
/*output:
7: lucky number
0: invalid number
13: bad number
5: normal number
6: normal number
7: lucky number
*/
}
事务语句
语法
transacted-statement:
'transacted' sequence-statement ((timeout-clause init-clause? | init-clause) ';')?
;
timeout-clause:
'timeout' C#-expression
;
init-clause:
'init' C#Expression
;
cancellable-statement:
'cancellable' sequence-statement cancellation-handler
;
cancellation-handler:
'cancel' sequence-statement
;
compensable-statement:
'compensable' ('explicit'? C#-identifier)?
sequence-statement confirmation-handler? compensation-handler? cancellation-handler?
;
confirmation-handler:
'confirm' sequence-statement
;
compensation-handler:
'compensate' sequence-statement
;
confirm-statement:
'confirm' C#-expression? ';'
;
compensate-statement:
'compensate' C#-expression? ';'
;
timeout-clause
的表达式类型必须是 System.TimeSpan
。transacted-statement
的 init-clause
的表达式类型必须是 System.Action<SAS.TransactionScope>
。transacted-statement
的示例:
activity Transacted(int Arg1)
{
transacted
{
//...
}
timeout TimeSpan.FromSeconds(Arg1)
init (System.Activities.Statements.TransactionScope ts) => {
ts.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead;
ts.AbortInstanceOnTransactionFailure = false;
}
;
}
有关更多信息,请参阅 MSDN 库 “工作流事务”。
cancellable-statement
的示例:
activity Cancellable()
{
parallel
{
//branch 1
cancellable
{
new Delay().Invoke(4);
}
cancel
{
Console.WriteLine("Cancellation handler for delaying 4 seconds");
}
//branch 2
cancellable
{
new Delay().Invoke(2);
}
cancel
{
Console.WriteLine("Cancellation handler for delaying 2 seconds");
}
}
until true;
/*output:
Begin to delay 4 seconds
Begin to delay 2 seconds
End delaying 2 seconds
Cancellation handler for delaying 4 seconds
*/
}
有关更多信息,请参阅 MSDN 库 “建模工作流中的取消行为”。
compensable-statement
、confirm-statement
和 compensate-statement
的示例:
activity Compensable(bool Arg1)
{
compensable cToken
{
new Delay().Invoke(4);
}
confirm
{
Console.WriteLine("Confirmation handler");
}
compensate
{
Console.WriteLine("Compensation handler");
}
cancel
{
Console.WriteLine("Cancellation handler");
}
if(Arg1) confirm cToken;
else compensate cToken;
}
在前面的代码中,MW 编译器会自动创建一个类型为 SAS.CompensationToken
、名为 cToken
的 token 变量,并将其绑定到 compensable-statement
,因此您可以在 confirm-statement
和 compensate-statement
中使用该变量。您可以将现有的 token 变量/参数显式绑定到 compensable-statement
。
activity Compensable2() as System.Activities.Statements.CompensationToken
{
compensable explicit Result
{
new Delay().Invoke(4);
}
confirm
{
Console.WriteLine("Confirmation handler");
}
compensate
{
Console.WriteLine("Compensation handler");
}
cancel
{
Console.WriteLine("Cancellation handler");
}
}
activity Compensable2Test()
{
confirm new Compensable2().Invoke();
}
有关更多信息,请参阅 MSDN 库 “补偿”。
运行时语句
语法
persist-statement:
'persist' ';'
;
no-persist-statement:
'nopersist' sequence-statement
;
terminate-statement:
'terminate' (reason-clause exception-clause? | exception-clause) ';'
;
reason-clause:
'for' C#-expression
;
exception-clause:
'throw' C#-expression
;
示例
activity RuntimeStatements()
{
persist;
nopersist
{
//...
}
terminate for "reason message" throw new Exception("exception message");
}
有关更多信息,请参阅 MSDN 库 “WF 中的运行时活动”。
服务语句
您应该熟悉 WF 服务和 API。如果熟悉,请在 Visual Studio 中打开 HelloMW 项目,并查看 WFServices.mw 和生成的 WFServices.mw.cs,您将轻松理解 MW 服务语句。
语法
receive-statement:
'receive' contract-operation-name-pair receive-content
request-reply-correlation? callback-correlation?
('ref' (ref-context-correlation | ref-content-correlation | ref-callback-correlation))?
init-clause? ';'
;
send-reply-statement:
'sendreply' send-content
context-correlation? content-correlation?
('ref' request-reply-correlation)?
init-clause? ';'
;
send-statement:
'send' contract-operation-name-pair send-content
request-reply-correlation? callback-correlation?
('ref' (ref-context-correlation | ref-callback-correlation))?
endpoint-address-clause? init-clause? ';'
;
receive-reply-statement:
'receivereply' receive-content
context-correlation?
('ref' request-reply-correlation)?
init-clause? ';'
;
contract-operation-name-pair:
'{' C#-expression ',' C#-expression '}'
;
endpoint-address-clause:
'endpointaddress' C#-expression
;
receive-content:
'(' (receive-message-content | receive-parameters-content)? ')'
;
receive-message-content:
'~>' C#-identifier
;
receive-parameters-content:
receive-content-parameter (',' receive-content-parameter)*
;
receive-content-parameter:
C#-expression '~>' C#-identifier
;
send-content:
'(' (send-message-content | send-parameters-content)? ')'
;
send-message-content:
'<~' C#-expression
;
send-parameters-content:
send-content-parameter (',' send-content-parameter)*
;
send-content-parameter:
C#-expression '<~' C#-expression
;
request-reply-correlation:
'requestcorr' C#-identifier
;
context-correlation:
'contextcorr' (C#-identifier | 'explicit' C#-expression)
;
ref-context-correlation:
'contextcorr' C#-expression
;
callback-correlation:
'callbackcorr' (C#-identifier | 'explicit' C#-expression)
;
ref-callback-correlation:
'callbackcorr' C#-expression
;
content-correlation:
'contentcorr' (C#-identifier | 'explicit' C#-expression) 'on' C#-expression
;
ref-content-correlation:
'contentcorr' C#-expression 'on' C#-expression
;
content-correlation-statement:
'contentcorr' (C#-identifier | 'explicit' C#-expression)
'on' content-correlation-statement-data (',' content-correlation-statement-data)*
;
content-correlation-statement-data:
'{' C#-expression ',' C#-expression '}'
;
transacted-receive-statement:
'transactedreceive' sequence-statement
;
本质上有两种消息交换模式(MEP):请求-响应和数据报。receive-statement
和 send-reply-statement
构成一个请求-响应服务操作;send-statement
和 receive-reply-statement
使用一个请求-响应服务操作。单个 receive-statement
构成一个数据报服务操作;单个 send-statement
使用一个数据报服务操作。我们可以将 receive-statement
和 send-reply-statement
称为服务方语句,将 send-statement
和 receive-reply-statement
称为客户端方语句。我们也可以将 receive-statement
和 send-statement
称为请求语句,将 send-reply-statement
和 receive-reply-statement
称为回复语句。
一个操作属于一个契约(接口)。在 contract-operation-name-pair
中,第一个类型为 System.Xml.Linq.XName
的 C# 表达式是契约名称,第二个类型为 string
的 C# 表达式是操作名称。
receive-content
表示将 XML 消息反序列化为变量/参数(“XML 消息 ~>
变量/参数”)。send-content
表示将表达式值序列化为 XML 消息(“XML 消息 <~
表达式”)。消息内容按原样序列化/反序列化。所有内容参数都在一个包装器中进行序列化/反序列化。对于内容参数,第一个类型为 string
的 C# 表达式指定参数名称。
receive-statement
、send-reply-statement
、send-statement
和 receive-reply-statement
的 init-clause
的表达式类型必须是 System.Action<SSA.Receive>
、System.Action<SSA.SendReply>
、System.Action<SSA.Send>
和 System.Action<SSA.ReceiveReply>
。endpoint-address-clause
的表达式类型必须是 System.Uri
。
示例
using System;
using System.Activities;
using System.ServiceModel;
using System.ServiceModel.Activities;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.Xml.Linq;
using System.Runtime.Serialization;
namespace WFServices
{
class Program
{
//You need run Visual Studio as Administrator to run/debug the program
static void Main()
{
Run(new Service(), new Client(), new WSHttpContextBinding());
}
static void Run(Activity sericeActivity, Activity clientActivity, Binding binding, bool addContract2 = true)
{
try
{
var serviceHost = new WorkflowServiceHost(sericeActivity, Constants.ServiceAddress);
serviceHost.AddServiceEndpoint(Constants.Contract1Name, binding, Constants.ServiceAddress);
if (addContract2)
serviceHost.AddServiceEndpoint(Constants.Contract2Name, binding, Constants.ServiceAddress);
serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
serviceHost.Description.Behaviors.Add(new ServiceBehaviorAttribute { IncludeExceptionDetailInFaults = true });
serviceHost.Open();
Console.WriteLine("Host: service host opened for " + sericeActivity.GetType().FullName);
if(clientActivity!=null)
WorkflowInvoker.Invoke(clientActivity);
Console.WriteLine("Host: press enter to end");
Console.ReadLine();
serviceHost.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
public static class Constants
{
public const string NamespaceString = "http://schemas.example.com/project1";
public static readonly XNamespace Namespace = XNamespace.Get(NamespaceString);
public static readonly XName Contract1Name = Namespace.GetName("IContract1");
public const string Contract1Op1Name = "Operation1";
public const string Contract1Op1RetPara1Name = "RetPara1";
public const string Contract1Op1RetPara2Name = "RetPara2";
public const string Contract1Op2Name = "Operation2";
public const string Contract1Op2Para1Name = "Para1";
public const string Contract1Op2Para2Name = "Para2";
public static readonly XName Contract2Name = Namespace.GetName("IContract2");
public const string Contract2Op1Name = "Operation1";
public static readonly Uri ServiceAddress = new Uri("https://:8080/WFService");
internal static readonly Endpoint CtxBindingEndpoint = new Endpoint { AddressUri = ServiceAddress, Binding = new WSHttpContextBinding() };
}
[DataContract(Namespace = Constants.NamespaceString)]
public class Data1
{
[DataMember]
public int Member1 { get; set; }
[DataMember]
public string Member2 { get; set; }
public override string ToString()
{
return string.Format("Member1: {0}, Member2: {1}", Member1, Member2);
}
}
activity Service()
{
Data1 data;
int para1;
string para2;
//contract1.op1
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> data)
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", data);
sendreply(Constants.Contract1Op1RetPara1Name <~ data.Member1 + 1, Constants.Contract1Op1RetPara2Name <~ data.Member2 + "++")
contextcorr ctxcorr1
;
Console.WriteLine("Service: after Contract1.Op1 sendreply");
//contract1.op2
receive {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name ~> para1, Constants.Contract1Op2Para2Name ~> para2)
ref contextcorr ctxcorr1
;
Console.WriteLine("Service: after Contract1.Op2 receive. para1: {0}, para2: {1}", para1, para2);
sendreply(<~ new Data1 {Member1 = para1 + data.Member1, Member2 = para2 + data.Member2});
Console.WriteLine("Service: after Contract1.Op2 sendreply");
//contract2.op1
receive {Constants.Contract2Name, Constants.Contract2Op1Name} (~> data)
ref contextcorr ctxcorr1
;
Console.WriteLine("Service: after Contract2.Op1 receive. data: {0}", data);
}
activity Client()
{
Data1 data;
int para1;
string para2;
//contract1.op1
send {Constants.Contract1Name, Constants.Contract1Op1Name} (<~ new Data1 {Member1 = 42, Member2 = "Hello"})
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op1 send");
receivereply(Constants.Contract1Op1RetPara1Name ~> para1, Constants.Contract1Op1RetPara2Name ~> para2)
contextcorr ctxcorr1
;
Console.WriteLine("Client: after Contract1.Op1 receivereply. para1: {0}, para2: {1}", para1, para2);
//contract1.op2
send {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name <~ para1, Constants.Contract1Op2Para2Name <~ para2)
ref contextcorr ctxcorr1
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op2 send");
receivereply(~> data);
Console.WriteLine("Client: after Contract1.Op2 receivereply. data: {0}", data);
//contract2.op1
send {Constants.Contract2Name, Constants.Contract2Op1Name} (<~ data)
ref contextcorr ctxcorr1
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract2.Op1 send");
}
}
程序运行时,打开 https://:8080/WFService?singleWsdl 查看 WSDL。
send-reply-statement
必须与 receive-statement
相关联,receive-reply-statement
必须与 send-statement
相关联。默认情况下,MW 编译器会自动将回复语句关联到最近的前一个请求语句。您可以使用 request-reply-correlation
显式地将回复语句关联到请求语句。
activity RequestCorrService()
{
Data1 data;
int para1;
string para2;
//contract1.op1
{
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> data)
requestcorr reqcorr1
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", data);
}
{
sendreply(Constants.Contract1Op1RetPara1Name <~ data.Member1 + 1, Constants.Contract1Op1RetPara2Name <~ data.Member2 + "++")
contextcorr ctxcorr1
ref requestcorr reqcorr1
;
Console.WriteLine("Service: after Contract1.Op1 sendreply");
}
//contract1.op2
{
receive {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name ~> para1, Constants.Contract1Op2Para2Name ~> para2)
requestcorr reqcorr2
ref contextcorr ctxcorr1
;
Console.WriteLine("Service: after Contract1.Op2 receive. para1: {0}, para2: {1}", para1, para2);
}
{
sendreply(<~ new Data1 {Member1 = para1 + data.Member1, Member2 = para2 + data.Member2})
ref requestcorr reqcorr2
;
Console.WriteLine("Service: after Contract1.Op2 sendreply");
}
//contract2.op1
receive {Constants.Contract2Name, Constants.Contract2Op1Name} (~> data)
ref contextcorr ctxcorr1
;
Console.WriteLine("Service: after Contract2.Op1 receive. data: {0}", data);
}
activity RequestCorrClient()
{
Data1 data;
int para1;
string para2;
{
send {Constants.Contract1Name, Constants.Contract1Op1Name} (<~ new Data1 {Member1 = 42, Member2 = "Hello"})
requestcorr reqcorr1
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op1 send");
}
{
receivereply(Constants.Contract1Op1RetPara1Name ~> para1, Constants.Contract1Op1RetPara2Name ~> para2)
contextcorr ctxcorr1
ref requestcorr reqcorr1
;
Console.WriteLine("Client: after Contract1.Op1 receivereply. para1: {0}, para2: {1}", para1, para2);
}
{
send {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name <~ para1, Constants.Contract1Op2Para2Name <~ para2)
requestcorr reqcorr2
ref contextcorr ctxcorr1
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op2 send");
}
{
receivereply(~> data)
ref requestcorr reqcorr2
;
Console.WriteLine("Client: after Contract1.Op2 receivereply. data: {0}", data);
}
send {Constants.Contract2Name, Constants.Contract2Op1Name} (<~ data)
ref contextcorr ctxcorr1
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract2.Op1 send");
}
requestcorr <id>
设置请求-回复关联,ref requestcorr <id>
引用关联,因此回复语句与请求语句相关联。请求-回复语句必须在同一个 MW 活动中。
如前述代码所示,contextcorr <id>
设置了上下文关联,ref contextcorr <id>
引用了关联。但是,您可以显式设置上下文关联。
activity Contract1Op1Service(System.ServiceModel.Activities.CorrelationHandle CtxCorrHandle) as Data1
{
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> Result)
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", Result);
sendreply(Constants.Contract1Op1RetPara1Name <~ Result.Member1 + 1, Constants.Contract1Op1RetPara2Name <~ Result.Member2 + "++")
contextcorr explicit CtxCorrHandle
;
Console.WriteLine("Service: after Contract1.Op1 sendreply");
}
activity Contract1Op2Service(System.ServiceModel.Activities.CorrelationHandle CtxCorrHandle, Data1 Data)
{
int para1;
string para2;
receive {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name ~> para1, Constants.Contract1Op2Para2Name ~> para2)
ref contextcorr CtxCorrHandle
;
Console.WriteLine("Service: after Contract1.Op2 receive. para1: {0}, para2: {1}", para1, para2);
sendreply(<~ new Data1 {Member1 = para1 + Data.Member1, Member2 = para2 + Data.Member2});
Console.WriteLine("Service: after Contract1.Op2 sendreply");
}
activity ExplicitContextCorrService()
{
System.ServiceModel.Activities.CorrelationHandle ctxCorrHandle;
Data1 data;
data = new Contract1Op1Service().Invoke(ctxCorrHandle);
new Contract1Op2Service().Invoke(ctxCorrHandle, data);
//contract2.op1
receive {Constants.Contract2Name, Constants.Contract2Op1Name} (~> data)
ref contextcorr ctxCorrHandle
;
Console.WriteLine("Service: after Contract2.Op1 receive. data: {0}", data);
}
activity Contract1Op1Client(System.ServiceModel.Activities.CorrelationHandle CtxCorrHandle, out int Para1, out string Para2)
{
send {Constants.Contract1Name, Constants.Contract1Op1Name} (<~ new Data1 {Member1 = 42, Member2 = "Hello"})
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op1 send");
receivereply(Constants.Contract1Op1RetPara1Name ~> Para1, Constants.Contract1Op1RetPara2Name ~> Para2)
contextcorr explicit CtxCorrHandle
;
Console.WriteLine("Client: after Contract1.Op1 receivereply. para1: {0}, para2: {1}", Para1, Para2);
}
activity Contract1Op2Client(System.ServiceModel.Activities.CorrelationHandle CtxCorrHandle, int Para1, string Para2) as Data1
{
send {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name <~ Para1, Constants.Contract1Op2Para2Name <~ Para2)
ref contextcorr CtxCorrHandle
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op2 send");
receivereply(~> Result);
Console.WriteLine("Client: after Contract1.Op2 receivereply. data: {0}", Result);
}
activity ExplicitContextCorrClient()
{
System.ServiceModel.Activities.CorrelationHandle ctxCorrHandle;
Data1 data;
int para1;
string para2;
new Contract1Op1Client().Invoke(ctxCorrHandle, out para1, out para2);
data = new Contract1Op2Client().Invoke(ctxCorrHandle, para1, para2);
//contract2.op1
send {Constants.Contract2Name, Constants.Contract2Op1Name} (<~ data)
ref contextcorr ctxCorrHandle
init snd => {
snd.Endpoint = Constants.CtxBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract2.Op1 send");
}
有关更多信息,请参阅 MSDN 库 “上下文交换关联”。
content-correlation
的 on
表达式类型必须是 System.ServiceModel.MessageQuerySet
。content-correlation-statement-data
的所有表达式类型都必须是 string
。这是内容关联的示例:
...
Run(new ContentCorrService(), new ContentCorrClient(), new BasicHttpBinding());
...
public static class Constants
{
...
internal static readonly Endpoint BasicBindingEndpoint = new Endpoint { AddressUri = ServiceAddress, Binding = new BasicHttpBinding() };
private static readonly XPathMessageContext XPathMessageContext = new XPathMessageContext();
internal static readonly MessageQuerySet Contract1Op1RetPara1MsgQrySet = new MessageQuerySet {
{"Id", new XPathMessageQuery("sm:body()/project1:" + Contract1Op1Name + "Response/project1:" + Contract1Op1RetPara1Name, XPathMessageContext)}
};
internal static readonly MessageQuerySet Contract1Op2Para1MsgQrySet = new MessageQuerySet {
{"Id", new XPathMessageQuery("sm:body()/project1:" + Contract1Op2Name + "/project1:" + Contract1Op2Para1Name, XPathMessageContext)}
};
internal static readonly MessageQuerySet Data1Member1MsgQrySet = new MessageQuerySet {
{"Id", new XPathMessageQuery("sm:body()/project1:Data1/project1:Member1", XPathMessageContext)}
};
static Constants()
{
XPathMessageContext.AddNamespace("project1", NamespaceString);
}
}
activity ContentCorrService()
{
Data1 data;
int para1;
string para2;
//contract1.op1
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> data)
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", data);
sendreply(Constants.Contract1Op1RetPara1Name <~ data.Member1, Constants.Contract1Op1RetPara2Name <~ data.Member2 + "++")
contentcorr contentCorr1 on Constants.Contract1Op1RetPara1MsgQrySet
;
/*You can comment the last sendreply and uncomment me
sendreply(Constants.Contract1Op1RetPara1Name <~ data.Member1, Constants.Contract1Op1RetPara2Name <~ data.Member2 + "++");
contentcorr contentCorr1 on {"Id", data.Member1.ToString()}
*/
Console.WriteLine("Service: after Contract1.Op1 sendreply");
//contract1.op2
receive {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name ~> para1, Constants.Contract1Op2Para2Name ~> para2)
ref contentcorr contentCorr1 on Constants.Contract1Op2Para1MsgQrySet
;
Console.WriteLine("Service: after Contract1.Op2 receive. para1: {0}, para2: {1}", para1, para2);
sendreply(<~ new Data1 {Member1 = para1, Member2 = para2 + data.Member2});
Console.WriteLine("Service: after Contract1.Op2 sendreply");
//contract2.op1
receive {Constants.Contract2Name, Constants.Contract2Op1Name} (~> data)
ref contentcorr contentCorr1 on Constants.Data1Member1MsgQrySet
;
Console.WriteLine("Service: after Contract2.Op1 receive. data: {0}", data);
}
activity ContentCorrClient()
{
Data1 data;
int para1;
string para2;
//contract1.op1
send {Constants.Contract1Name, Constants.Contract1Op1Name} (<~ new Data1 {Member1 = 42, Member2 = "Hello"})
init snd => {
snd.Endpoint = Constants.BasicBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op1 send");
receivereply(Constants.Contract1Op1RetPara1Name ~> para1, Constants.Contract1Op1RetPara2Name ~> para2)
;
Console.WriteLine("Client: after Contract1.Op1 receivereply. para1: {0}, para2: {1}", para1, para2);
//contract1.op2
send {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name <~ para1, Constants.Contract1Op2Para2Name <~ para2)
init snd => {
snd.Endpoint = Constants.BasicBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract1.Op2 send");
receivereply(~> data);
Console.WriteLine("Client: after Contract1.Op2 receivereply. data: {0}", data);
//contract2.op1
send {Constants.Contract2Name, Constants.Contract2Op1Name} (<~ data)
init snd => {
snd.Endpoint = Constants.BasicBindingEndpoint;
}
;
Console.WriteLine("Client: after Contract2.Op1 send");
}
这是显式内容关联的示例。
activity Contract1Op1Service2(System.ServiceModel.Activities.CorrelationHandle ContentCorrHandle) as Data1
{
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> Result)
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", Result);
sendreply(Constants.Contract1Op1RetPara1Name <~ Result.Member1, Constants.Contract1Op1RetPara2Name <~ Result.Member2 + "++")
contentcorr explicit ContentCorrHandle on Constants.Contract1Op1RetPara1MsgQrySet
;
/*You can comment the last sendreply and uncomment me
sendreply(Constants.Contract1Op1RetPara1Name <~ Result.Member1, Constants.Contract1Op1RetPara2Name <~ Result.Member2 + "++");
contentcorr explicit ContentCorrHandle on {"Id", Result.Member1.ToString()}
*/
Console.WriteLine("Service: after Contract1.Op1 sendreply");
}
activity Contract1Op2Service2(System.ServiceModel.Activities.CorrelationHandle ContentCorrHandle, Data1 Data)
{
int para1;
string para2;
receive {Constants.Contract1Name, Constants.Contract1Op2Name}
(Constants.Contract1Op2Para1Name ~> para1, Constants.Contract1Op2Para2Name ~> para2)
ref contentcorr ContentCorrHandle on Constants.Contract1Op2Para1MsgQrySet
;
Console.WriteLine("Service: after Contract1.Op2 receive. para1: {0}, para2: {1}", para1, para2);
sendreply(<~ new Data1 {Member1 = para1, Member2 = para2 + Data.Member2});
Console.WriteLine("Service: after Contract1.Op2 sendreply");
}
activity ExplicitContentCorrService()
{
System.ServiceModel.Activities.CorrelationHandle contentCorrHandle;
Data1 data;
data = new Contract1Op1Service2().Invoke(contentCorrHandle);
new Contract1Op2Service2().Invoke(contentCorrHandle, data);
//contract2.op1
receive {Constants.Contract2Name, Constants.Contract2Op1Name} (~> data)
ref contentcorr contentCorrHandle on Constants.Data1Member1MsgQrySet
;
Console.WriteLine("Service: after Contract2.Op1 receive. data: {0}", data);
}
有关更多信息,请参阅 MSDN 库 “基于内容的关联”。
这是回调关联的示例。
static void RunCallcack()
{
try
{
var clientHost = new WorkflowServiceHost(new CallbackClient());
var binding = new WSHttpContextBinding();
clientHost.AddServiceEndpoint(Constants.ClientContractName, binding, Constants.ClientAddress);
clientHost.AddServiceEndpoint(Constants.CallbackContractName, binding, Constants.CallbackAddress);
clientHost.Open();
Console.WriteLine("Host: client host opened");
var serviceHost = new WorkflowServiceHost(new CallbackService());
serviceHost.AddServiceEndpoint(Constants.Contract1Name, binding, Constants.ServiceAddress);
serviceHost.Open();
Console.WriteLine("Host: service host opened");
WorkflowInvoker.Invoke(new CallbackTest());
Console.WriteLine("Host: press enter to end");
Console.ReadLine();
serviceHost.Close();
clientHost.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static class Constants
{
...
public static readonly XName ClientContractName = Namespace.GetName("IClientContract");
public const string ClientOp1Name = "ClientOperation1";
public static readonly XName CallbackContractName = Namespace.GetName("ICallbackContract");
public const string CallbackOp1Name = "CallbackOperation1";
public static readonly Uri ServiceAddress = new Uri("https://:8080/WFService");
public static readonly Uri ClientAddress = new Uri("https://:8081/client");
public static readonly Uri CallbackAddress = new Uri("https://:8081/client/callback");
}
activity CallbackService()
{
Data1 data;
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> data)
callbackcorr callbackCorr1
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", data);
send {Constants.CallbackContractName, Constants.CallbackOp1Name}(<~ new Data1{Member1 = data.Member1 + 1, Member2 = data.Member2 + "++"})
ref callbackcorr callbackCorr1
init snd => {
snd.Endpoint = new Endpoint {
Binding = new WSHttpContextBinding()
};
}
;
}
activity CallbackClient()
{
Data1 data;
receive {Constants.ClientContractName, Constants.ClientOp1Name}()
init rcv => {
rcv.CanCreateInstance = true;
}
;
send {Constants.Contract1Name, Constants.Contract1Op1Name}(<~ new Data1{Member1 = 42, Member2 = "Hello"})
callbackcorr callbackCorr1
init snd => {
snd.Endpoint = new Endpoint {
AddressUri = Constants.ServiceAddress,
Binding = new WSHttpContextBinding {
ClientCallbackAddress = Constants.CallbackAddress
}
};
}
;
receive {Constants.CallbackContractName, Constants.CallbackOp1Name}(~> data)
ref callbackcorr callbackCorr1
;
Console.WriteLine("Callback: after ICallbackContract.Op1 receive. data: {0}", data);
}
activity CallbackTest()
{
send {Constants.ClientContractName, Constants.ClientOp1Name}()
init snd => {
snd.Endpoint = new Endpoint {
AddressUri = Constants.ClientAddress,
Binding = new WSHttpContextBinding()
};
}
;
}
有关更多信息,请参阅 MSDN 库 “持久双工关联”。
transacted-receive-statement
的第一个成员语句必须是 receive-statement
。
activity TransactedReceive()
{
transactedreceive
{
Data1 data;
receive {Constants.Contract1Name, Constants.Contract1Op1Name} (~> data)
init rcv => {
rcv.CanCreateInstance = true;
}
;
Console.WriteLine("Service: after Contract1.Op1 receive. data: {0}", data);
}
}