65.9K
CodeProject 正在变化。 阅读更多。
Home

Metah.W: 一种工作流元编程语言

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2014年12月17日

MIT

13分钟阅读

viewsIcon

25838

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.CodeActivitySA.CodeActivity<T>SA.NativeActivitySA.NativeActivity<T> 等。复合活动直接继承自 SA.ActivitySA.Activity<T>,由原始/复合活动组成。在 Handmade.cs 中,SequentialNumberGuessPrompt 是复合活动,ReadInt 是原始活动。

如前述代码所示,在 C#(/VB) 中组合活动非常冗长。有没有更简化的方法?有!欢迎来到元编程世界,欢迎使用 Metah.W (MW)。

  1. 您需要 Visual Studio 2013;
  2. 下载并安装 最新的 Metah vsix 包
  3. 打开 VS 2013 -> 新建项目 -> Visual C# -> Metah.W -> 创建一个 Metah.W 控制台应用程序(或者您可以打开 HelloMW 项目);
  4. 删除 Program.cs;
  5. 添加新项 -> Visual C# 项 -> Metah.W -> 创建一个名为 FirstLook.mw 的新 MW 文件;
  6. 将以下代码复制到 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 中,SequentialNumberGuessPrompt 是用 MW 语法编写的(称之为 MW 活动)。MW 活动类似于函数。您可以声明零个或多个参数(MaxNumberTurnsBookmarkName 等)和一个可选的返回类型(Prompt 中的 as int)。在活动主体中,您可以声明变量(targetguess)并使用语句。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 还增加了以下保留关键字:activitycancellableconfirmcompensablecompensatecontentcorrdelayflowfiffswitchimportnopersistparallelpersistpforecahpickreceivereceivereplysendsendreplystatemachineterminatetransactedtransactedreceive。这意味着如果标识符与其中之一相等,您必须使用 @ 来转义它(@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 元编程的角度来看,A3A4<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-statementsequence-statementif-statementwhile-statementdo-while-statementforeach-statementswitch-statementthrow-statementtry-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.TimeSpanuntil-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.TimeSpantransacted-statementinit-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-statementconfirm-statementcompensate-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-statementcompensate-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-statementsend-reply-statement 构成一个请求-响应服务操作;send-statementreceive-reply-statement 使用一个请求-响应服务操作。单个 receive-statement 构成一个数据报服务操作;单个 send-statement 使用一个数据报服务操作。我们可以将 receive-statementsend-reply-statement 称为服务方语句,将 send-statementreceive-reply-statement 称为客户端方语句。我们也可以将 receive-statementsend-statement 称为请求语句,将 send-reply-statementreceive-reply-statement 称为回复语句。

一个操作属于一个契约(接口)。在 contract-operation-name-pair 中,第一个类型为 System.Xml.Linq.XName 的 C# 表达式是契约名称,第二个类型为 string 的 C# 表达式是操作名称。

receive-content 表示将 XML 消息反序列化为变量/参数(“XML 消息 ~> 变量/参数”)。send-content 表示将表达式值序列化为 XML 消息(“XML 消息 <~ 表达式”)。消息内容按原样序列化/反序列化。所有内容参数都在一个包装器中进行序列化/反序列化。对于内容参数,第一个类型为 string 的 C# 表达式指定参数名称。

receive-statementsend-reply-statementsend-statementreceive-reply-statementinit-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-correlationon 表达式类型必须是 System.ServiceModel.MessageQuerySetcontent-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);
    }
}

欢迎提出任何问题或建议。

© . All rights reserved.