Freigeben über


Erstellen von Workflows, Aktivitäten und Ausdrücken mit imperativem Code

Eine Workflowdefinition ist eine Struktur von konfigurierten Aktivitätsobjekten. Diese Struktur von Aktivitäten kann auf viele Arten definiert werden, z. B. durch die Handbearbeitung von XAML oder mithilfe des Workflow-Designers zum Erstellen von XAML. Die Verwendung von XAML ist jedoch keine Voraussetzung. Workflowdefinitionen können auch programmgesteuert erstellt werden. Dieses Thema bietet eine Übersicht über das Erstellen von Workflowdefinitionen, Aktivitäten und Ausdrücken mithilfe von Code. Beispiele für das Arbeiten mit XAML-Workflows mit Code finden Sie unter Serialisieren von Workflows und Aktivitäten in und aus XAML.

Erstellen von Workflowdefinitionen

Eine Workflowdefinition kann erstellt werden, indem eine Instanz eines Aktivitätstyps instanziiert und die Eigenschaften des Aktivitätsobjekts konfiguriert werden. Für Aktivitäten, die keine untergeordneten Aktivitäten enthalten, erreichen Sie dies mit einigen Codezeilen.

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

Hinweis

In den Beispielen in diesem Thema wird WorkflowInvoker verwendet, um die Beispielworkflows auszuführen. Weitere Informationen zum Aufrufen von Workflows, das Übergeben von Argumenten und die verschiedenen verfügbaren Hostingoptionen finden Sie unter Verwenden von WorkflowInvoker und WorkflowApplication.

In diesem Beispiel wird ein Workflow erstellt, der aus einer einzelnen WriteLine Aktivität besteht. Das WriteLine Argument der Text Aktivität wird festgelegt, und der Workflow wird aufgerufen. Wenn eine Aktivität untergeordnete Aktivitäten enthält, ist die Konstruktionsmethode ähnlich. Im folgenden Beispiel wird eine Sequence Aktivität verwendet, die zwei WriteLine Aktivitäten enthält.

Activity wf = new Sequence
{
    Activities =
    {
        new WriteLine
        {
            Text = "Hello"
        },
        new WriteLine
        {
            Text = "World."
        }
    }
};

WorkflowInvoker.Invoke(wf);

Verwenden von Objektinitialisierern

In den Beispielen in diesem Thema wird die Objektinitialisierungssyntax verwendet. Die Objektinitialisierungssyntax kann hilfreich sein, um Workflowdefinitionen im Code zu erstellen, da sie eine hierarchische Ansicht der Aktivitäten im Workflow bereitstellt und die Beziehung zwischen den Aktivitäten anzeigt. Es ist nicht erforderlich, die Objektinitialisierungssyntax zu verwenden, wenn Sie Workflows programmgesteuert erstellen. Das folgende Beispiel entspricht funktional dem vorherigen Beispiel.

WriteLine hello = new WriteLine();
hello.Text = "Hello";

WriteLine world = new WriteLine();
world.Text = "World";

Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);

WorkflowInvoker.Invoke(wf);

Weitere Informationen zu Objektinitialisierern finden Sie unter Objekte initialisieren, ohne einen Konstruktor aufzurufen (C#-Programmierhandbuch) und Ein Objekt mit einem Objektinitialisierer deklarieren.

Arbeiten mit Variablen, Literalwerten und Ausdrücken

Beachten Sie beim Erstellen einer Workflowdefinition mithilfe von Code, welcher Code als Teil der Erstellung der Workflowdefinition ausgeführt wird und welcher Code als Teil der Ausführung einer Instanz dieses Workflows ausgeführt wird. Der folgende Workflow soll beispielsweise eine Zufallszahl generieren und in die Konsole schreiben.

Variable<int> n = new Variable<int>
{
    Name = "n"
};

Activity wf = new Sequence
{
    Variables = { n },
    Activities =
    {
        new Assign<int>
        {
            To = n,
            Value = new Random().Next(1, 101)
        },
        new WriteLine
        {
            Text = new InArgument<string>((env) => "The number is " + n.Get(env))
        }
    }
};

Wenn dieser Workflowdefinitionscode ausgeführt wird, wird der Aufruf Random.Next ausgeführt, und das Ergebnis wird in der Workflowdefinition als Literalwert gespeichert. Viele Instanzen dieses Workflows können aufgerufen werden, und alle würden dieselbe Zahl anzeigen. Damit die Zufallszahl während der Workflowausführung generiert wird, muss ein Ausdruck verwendet werden, der bei jeder Ausführung des Workflows ausgewertet wird. Im folgenden Beispiel wird ein Ausdruck in Visual Basic mit einem VisualBasicValue<TResult> verwendet.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

Der Ausdruck im vorherigen Beispiel kann auch mit einem CSharpValue<TResult> und einem C#-Ausdruck implementiert werden.

new Assign<int>  
{  
    To = n,  
    Value = new CSharpValue<int>("new Random().Next(1, 101)")  
}  

C#-Ausdrücke müssen kompiliert werden, bevor der Workflow, der sie enthält, aufgerufen wird. Wenn die C#-Ausdrücke nicht kompiliert werden, wird ein NotSupportedException Ausgelöst, wenn der Workflow mit einer Meldung wie folgt aufgerufen wird: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. In den meisten Szenarien, in denen Workflows in Visual Studio erstellt wurden, werden die C#-Ausdrücke automatisch kompiliert, aber in einigen Szenarien, z. B. Codeworkflows, müssen die C#-Ausdrücke manuell kompiliert werden. Ein Beispiel zum Kompilieren von C#-Ausdrücken finden Sie im Abschnitt "Verwenden von C#-Ausdrücken in Codeworkflows " des Themas "C#-Ausdrücke" .

A VisualBasicValue<TResult> stellt einen Ausdruck in der Visual Basic-Syntax dar, der als R-Wert in einem Ausdruck verwendet werden kann, und ein CSharpValue<TResult> Ausdruck in der C#-Syntax, der als R-Wert in einem Ausdruck verwendet werden kann. Diese Ausdrücke werden jedes Mal ausgewertet, wenn die enthaltende Aktivität ausgeführt wird. Das Ergebnis des Ausdrucks wird der Workflowvariable nzugewiesen, und diese Ergebnisse werden von der nächsten Aktivität im Workflow verwendet. Um zur Laufzeit auf den Wert der Workflowvariable n zuzugreifen, ist ActivityContext erforderlich. Auf diesen kann mithilfe des folgenden Lambda-Ausdrucks zugegriffen werden.

Hinweis

Beachten Sie, dass beide Codebeispiele C# als Programmiersprache verwenden, aber eine verwendet eine VisualBasicValue<TResult> und eine verwendet eine CSharpValue<TResult>. VisualBasicValue<TResult> und CSharpValue<TResult> kann sowohl in Visual Basic- als auch in C#-Projekten verwendet werden. Standardmäßig stimmen ausdrücke, die im Workflow-Designer erstellt wurden, mit der Sprache des Hostingprojekts überein. Beim Erstellen von Workflows im Code liegt die gewünschte Sprache im Ermessen des Workflowautors.

In diesen Beispielen wird das Ergebnis des Ausdrucks der Workflowvariable nzugewiesen, und diese Ergebnisse werden von der nächsten Aktivität im Workflow verwendet. Um zur Laufzeit auf den Wert der Workflowvariable n zuzugreifen, ist ActivityContext erforderlich. Auf diesen kann mithilfe des folgenden Lambda-Ausdrucks zugegriffen werden.

new WriteLine
{
    Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}

Weitere Informationen zu Lambda-Ausdrücken finden Sie unter Lambda-Ausdrücke (C#-Referenz) oder Lambda-Ausdrücke (Visual Basic).

Lambda-Ausdrücke können nicht im XAML-Format serialisiert werden. Wenn versucht wird, einen Workflow mit Lambda-Ausdrücken zu serialisieren, wird ein LambdaSerializationException Fehler mit der folgenden Meldung ausgelöst: "Dieser Workflow enthält Lambda-Ausdrücke, die im Code angegeben sind. Diese Ausdrücke sind nicht XAML-serialisierbar. Um Ihren Workflow XAML-serialisierbar zu machen, verwenden Sie entweder VisualBasicValue/VisualBasicReference oder ExpressionServices.Convert(lambda). Dadurch werden Ihre Lambda-Ausdrücke in Ausdrucksaktivitäten konvertiert. Um diesen Ausdruck mit XAML kompatibel zu machen, verwenden Sie ExpressionServices und Convert, wie im folgenden Beispiel gezeigt.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}

Es kann auch ein VisualBasicValue<TResult> verwendet werden. Beachten Sie, dass bei Verwendung eines Visual Basic-Ausdrucks kein Lambda-Ausdruck erforderlich ist.

new WriteLine
{
    //Text = new InArgument<string>((env) => "The number is " + n.Get(env))
    //Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
    Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}

Zur Laufzeit werden Visual Basic-Ausdrücke in LINQ-Ausdrücke kompiliert. Beide vorherigen Beispiele sind in XAML serialisierbar, aber wenn das serialisierte XAML im Workflow-Designer angezeigt und bearbeitet werden soll, verwenden Sie VisualBasicValue<TResult> für Ihre Ausdrücke. Serialisierte Workflows, die ExpressionServices.Convert verwenden, können im Designer geöffnet werden, der Ausdruckswert bleibt jedoch leer. Weitere Informationen zum Serialisieren von Workflows in XAML finden Sie unter Serialisieren von Workflows und Aktivitäten in und aus XAML.

Literale Ausdrücke und Referenztypen

Literale Ausdrücke werden in Workflows durch die Literal<T> Aktivität dargestellt. Die folgenden WriteLine Aktivitäten sind funktional gleichwertig.

new WriteLine  
{  
    Text = "Hello World."  
},  
new WriteLine  
{  
    Text = new Literal<string>("Hello World.")  
}  

Es ist nicht zulässig, einen literalen Ausdruck mit einem beliebigen Verweistyp außer String zu initialisieren. Im folgenden Beispiel wird die Assign-Eigenschaft einer Value-Aktivität mit einem literalen Ausdruck mit List<string> initialisiert.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new List<string>())  
},  

Wenn der Workflow, der diese Aktivität enthält, überprüft wird, wird der folgende Überprüfungsfehler zurückgegeben: "Literal unterstützt nur Werttypen und den unveränderlichen Typ "System.String". Der System.Collections.Generic.List`1 [System.String]-Typ kann nicht als Literal verwendet werden." Beim Aufrufen des Workflows wird InvalidWorkflowException ausgelöst, die den Text des Validierungsfehlers enthält. Dies ist ein Validierungsfehler, da durch das Erstellen eines literalen Ausdrucks mit einem Verweistyp keine neue Instanz des Verweistyps für die einzelnen Instanzen des Workflows erstellt wird. Um diesen Fehler zu beheben, ersetzen Sie den literalen Ausdruck durch einen Ausdruck, der eine neue Instanz des Verweistyps erstellt und zurückgibt.

new Assign  
{  
    To = new OutArgument<List<string>>(items),  
    Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))  
},  

Weitere Informationen zu Ausdrücken finden Sie unter "Ausdrücke".

Aufrufen von Methoden für Objekte mithilfe von Ausdrücken und der InvokeMethod-Aktivität

Die InvokeMethod<TResult> Aktivität kann zum Aufrufen statischer und Instanzmethoden von Klassen im .NET Framework verwendet werden. In einem vorherigen Beispiel in diesem Thema wurde mithilfe der Random Klasse eine Zufallszahl generiert.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

Die InvokeMethod<TResult> Aktivität konnte auch verwendet werden, um die Next Methode der Random Klasse aufzurufen.

new InvokeMethod<int>  
{  
    TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),  
    MethodName = "Next",  
    Parameters =
    {  
        new InArgument<int>(1),  
        new InArgument<int>(101)  
    },  
    Result = n  
}  

Da Next es sich nicht um eine statische Methode handelt, wird eine Instanz der Random Klasse für die TargetObject Eigenschaft bereitgestellt. In diesem Beispiel wird eine neue Instanz mit einem Visual Basic Ausdruck erstellt, sie könnte aber auch zuvor erstellt und in einer Workflow-Variable gespeichert worden sein. In diesem Beispiel wäre es einfacher, die Assign<T> Aktivität anstelle der InvokeMethod<TResult> Aktivität zu verwenden. Wenn der Methodenaufruf, der letztendlich entweder durch die Assign<T>-Aktivität oder durch InvokeMethod<TResult>-Aktivität aufgerufen wird, eine lange Ausführungszeit aufweist, bietet InvokeMethod<TResult> einen Vorteil, da sie über eine RunAsynchronously-Eigenschaft verfügt. Wenn diese Eigenschaft auf true festgelegt ist, wird die aufgerufene Methode asynchron im Kontext des Workflows ausgeführt. Wenn andere Aktivitäten parallel sind, werden sie nicht blockiert, während die Methode asynchron ausgeführt wird. Auch wenn die aufzurufende Methode über keinen Rückgabewert verfügt, stellt InvokeMethod<TResult> die geeignete Möglichkeit zum Aufrufen der Methode dar.

Argumente und dynamische Aktivitäten

Eine Workflowdefinition wird im Code erstellt, indem Aktivitäten in einer Aktivitätsstruktur zusammengestellt und eigenschaften und Argumente konfiguriert werden. Vorhandene Argumente können gebunden werden, aber neue Argumente können nicht zu Aktivitäten hinzugefügt werden. Dazu gehören Workflowargumente, die an die Stammaktivität übergeben werden. Im imperativen Code werden Workflowargumente als Eigenschaften für einen neuen CLR-Typ angegeben, und in XAML werden sie mithilfe x:Class und x:Memberdeklariert. Da kein neuer CLR-Typ erstellt wird, wenn eine Workflowdefinition als Struktur von In-Memory-Objekten erstellt wird, können keine Argumente hinzugefügt werden. Argumente können zu einem DynamicActivity jedoch hinzugefügt werden. In diesem Beispiel wird ein DynamicActivity<TResult> Objekt erstellt, das zwei ganzzahlige Argumente akzeptiert, diese zusammen addiert und das Ergebnis zurückgibt. Für jedes Argument wird ein DynamicActivityProperty erstellt, und das Ergebnis des Vorgangs wird dem Argument Result von der DynamicActivity<TResult> zugewiesen.

InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();

DynamicActivity<int> wf = new DynamicActivity<int>
{
    Properties =
    {
        new DynamicActivityProperty
        {
            Name = "Operand1",
            Type = typeof(InArgument<int>),
            Value = Operand1
        },
        new DynamicActivityProperty
        {
            Name = "Operand2",
            Type = typeof(InArgument<int>),
            Value = Operand2
        }
    },

    Implementation = () => new Sequence
    {
        Activities =
        {
            new Assign<int>
            {
                To = new ArgumentReference<int> { ArgumentName = "Result" },
                Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
            }
        }
    }
};

Dictionary<string, object> wfParams = new Dictionary<string, object>
{
    { "Operand1", 25 },
    { "Operand2", 15 }
};

int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);

Weitere Informationen zu dynamischen Aktivitäten finden Sie unter Erstellen einer Aktivität zur Laufzeit.

Kompilierte Aktivitäten

Dynamische Aktivitäten sind eine Möglichkeit, eine Aktivität zu definieren, die Argumente mithilfe von Code enthält, aber Aktivitäten können auch im Code erstellt und in Typen kompiliert werden. Einfache Aktivitäten, die von CodeActivity abgeleitet sind, und asynchrone Aktivitäten, die von AsyncCodeActivity abgeleitet sind, können erstellt werden. Diese Aktivitäten können Argumente, Rückgabewerte enthalten und ihre Logik mithilfe von imperativem Code definieren. Beispiele zum Erstellen dieser Arten von Aktivitäten finden Sie unter CodeActivity-Basisklasse und Erstellen asynchroner Aktivitäten.

Von NativeActivity abgeleitete Aktivitäten können ihre Logik mithilfe von imperativem Code definieren und auch untergeordnete Aktivitäten enthalten, die ebenfalls die Logik definieren können. Sie haben auch vollzugriff auf die Features der Laufzeit, z. B. das Erstellen von Lesezeichen. Beispiele zum Erstellen einer NativeActivity-basierten Aktivität finden Sie unter NativeActivity-Basisklasse, How to: Create an Activity, and the Custom Composite using Native Activity sample.

Aktivitäten, die von Activity abgeleitet sind, definieren ihre Logik nur durch die Verwendung untergeordneter Aktivitäten. Diese Aktivitäten werden in der Regel mithilfe des Workflow-Designers erstellt, können aber auch mithilfe von Code definiert werden. Im folgenden Beispiel wird eine Square-Aktivität definiert, die von Activity<int> abgeleitet ist. Die Square Aktivität hat eine einzelne InArgument<T> namens Value, und definiert ihre Logik, indem eine Sequence Aktivität mithilfe der Implementation Eigenschaft definiert wird. Die Sequence Aktivität enthält eine WriteLine Aktivität und eine Assign<T> Aktivität. Zusammen implementieren diese drei Aktivitäten die Logik der Square Aktivität.

class Square : Activity<int>  
{  
    [RequiredArgument]  
    public InArgument<int> Value { get; set; }  
  
    public Square()  
    {  
        this.Implementation = () => new Sequence  
        {  
            Activities =  
            {  
                new WriteLine  
                {  
                    Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))  
                },  
                new Assign<int>  
                {  
                    To = new OutArgument<int>((env) => this.Result.Get(env)),  
                    Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))  
                }  
            }  
        };  
    }  
}  

Im folgenden Beispiel wird eine Workflow-Definition, die aus einer einzigen Square-Aktivität besteht, mit WorkflowInvoker aufgerufen.

Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};  
int result = WorkflowInvoker.Invoke(new Square(), inputs);  
Console.WriteLine("Result: {0}", result);  

Wenn der Workflow aufgerufen wird, wird die folgende Ausgabe in der Konsole angezeigt:

Quadrieren des Werts: 5
Ergebnis: 25