Delen via


Werkstromen, activiteiten en expressies ontwerpen met imperatieve code

Een werkstroomdefinitie is een structuur van geconfigureerde activiteitsobjecten. Deze structuur van activiteiten kan op verschillende manieren worden gedefinieerd, waaronder door XAML met de hand te bewerken of door de workflowontwerper te gebruiken om XAML te produceren. Het gebruik van XAML is echter geen vereiste. Werkstroomdefinities kunnen ook programmatisch worden gemaakt. Dit onderwerp bevat een overzicht van het maken van werkstroomdefinities, activiteiten en expressies met behulp van code. Zie Werkstromen en activiteiten van en naar XAML serialiseren voor voorbeelden van het werken met XAML-werkstromen met behulp van code.

Werkstroomdefinities maken

U kunt een werkstroomdefinitie maken door een exemplaar van een activiteitstype te instantiëren en de eigenschappen van het activiteitsobject te configureren. Voor activiteiten die geen onderliggende activiteiten bevatten, kan dit worden bereikt met behulp van een paar regels code.

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

WorkflowInvoker.Invoke(wf);

Notitie

De voorbeelden in dit onderwerp gebruiken WorkflowInvoker om de voorbeeldwerkstromen uit te voeren. Zie WorkflowInvoker en WorkflowApplication gebruiken voor meer informatie over het aanroepen van werkstromen, het doorgeven van argumenten en de verschillende beschikbare hostingopties.

In dit voorbeeld wordt een werkstroom gemaakt die bestaat uit één WriteLine activiteit. Het argument van Text de WriteLine activiteit wordt ingesteld en de werkstroom wordt aangeroepen. Als een activiteit onderliggende activiteiten bevat, is de constructiemethode vergelijkbaar. In het volgende voorbeeld wordt een Sequence activiteit gebruikt die twee WriteLine activiteiten bevat.

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

WorkflowInvoker.Invoke(wf);

Object-initialisatiefuncties gebruiken

In de voorbeelden in dit onderwerp wordt de syntaxis van objectitialisatie gebruikt. De syntaxis van object initialisatie kan een handige manier zijn om werkstroomdefinities in code te maken, omdat deze een hiërarchische weergave biedt van de activiteiten in de werkstroom en de relatie tussen de activiteiten weergeeft. Er is geen vereiste om de syntaxis van object initialisatie te gebruiken wanneer u programmatisch werkstromen maakt. Het volgende voorbeeld is functioneel gelijk aan het vorige voorbeeld.

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);

Zie How to: Initialize Objects without Calling a Constructor (C# Programming Guide) and How to: Declare an Object by Using an Object Initializer(C#Programming Guide) (Een object declareren met behulp van een Object Initializer) voor meer informatie over object-initialisatiefuncties.

Werken met variabelen, letterlijke waarden en expressies

Wanneer u een werkstroomdefinitie maakt met behulp van code, moet u weten welke code wordt uitgevoerd als onderdeel van het maken van de werkstroomdefinitie en welke code wordt uitgevoerd als onderdeel van de uitvoering van een exemplaar van die werkstroom. De volgende werkstroom is bijvoorbeeld bedoeld om een willekeurig getal te genereren en naar de console te schrijven.

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))
        }
    }
};

Wanneer deze werkstroomdefinitiecode wordt uitgevoerd, wordt de aanroep uitgevoerd Random.Next en wordt het resultaat opgeslagen in de werkstroomdefinitie als een letterlijke waarde. Veel exemplaren van deze werkstroom kunnen worden aangeroepen en allemaal hetzelfde nummer weergeven. Als u wilt dat het genereren van willekeurige getallen plaatsvindt tijdens de uitvoering van de werkstroom, moet een expressie worden gebruikt die telkens wordt geëvalueerd wanneer de werkstroom wordt uitgevoerd. In het volgende voorbeeld wordt een Visual Basic-expressie gebruikt met een VisualBasicValue<TResult>.

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

De expressie in het vorige voorbeeld kan ook worden geïmplementeerd met behulp van een CSharpValue<TResult> en een C#-expressie.

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

C#-expressies moeten worden gecompileerd voordat de werkstroom met deze expressies wordt aangeroepen. Als de C#-expressies niet zijn gecompileerd, wordt er een NotSupportedException gegenereerd wanneer de werkstroom wordt aangeroepen met een bericht dat vergelijkbaar is met het volgende: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. In de meeste scenario's waarbij werkstromen in Visual Studio zijn gemaakt, worden de C#-expressies automatisch gecompileerd, maar in sommige scenario's, zoals codewerkstromen, moeten de C#-expressies handmatig worden gecompileerd. Zie het gedeelte C#-expressies gebruiken in de sectie Codewerkstromen van het onderwerp C#-expressies gebruiken voor een voorbeeld van het compileren van C#-expressies .

A VisualBasicValue<TResult> vertegenwoordigt een expressie in visual Basic-syntaxis die kan worden gebruikt als een r-waarde in een expressie en een CSharpValue<TResult> expressie in C#-syntaxis die kan worden gebruikt als een r-waarde in een expressie. Deze expressies worden geëvalueerd telkens wanneer de activiteit wordt uitgevoerd. Het resultaat van de expressie wordt toegewezen aan de werkstroomvariabele nen deze resultaten worden gebruikt door de volgende activiteit in de werkstroom. Voor toegang tot de waarde van de werkstroomvariabele n tijdens runtime is dit ActivityContext vereist. Dit kan worden geopend met behulp van de volgende lambda-expressie.

Notitie

Houd er rekening mee dat beide code voorbeelden zijn van C# als programmeertaal, maar één gebruikt een VisualBasicValue<TResult> en één een CSharpValue<TResult>. VisualBasicValue<TResult> en CSharpValue<TResult> kan worden gebruikt in zowel Visual Basic- als C#-projecten. Expressies die in de werkstroomontwerper zijn gemaakt, komen standaard overeen met de taal van het hostingproject. Bij het maken van werkstromen in code is de gewenste taal naar eigen goeddunken van de auteur van de werkstroom.

In deze voorbeelden wordt het resultaat van de expressie toegewezen aan de werkstroomvariabele nen worden deze resultaten gebruikt door de volgende activiteit in de werkstroom. Voor toegang tot de waarde van de werkstroomvariabele n tijdens runtime is dit ActivityContext vereist. Dit kan worden geopend met behulp van de volgende lambda-expressie.

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

Zie Lambda-expressies (C#-verwijzing) of Lambda-expressies (Visual Basic) voor meer informatie over lambda-expressies.

Lambda-expressies zijn niet serialiseerbaar in XAML-indeling. Als er een poging wordt gedaan om een werkstroom met lambda-expressies te serialiseren, wordt er een LambdaSerializationException bericht gegenereerd met het volgende bericht: 'Deze werkstroom bevat lambda-expressies die zijn opgegeven in code. Deze expressies zijn niet serialiseerbaar met XAML. Gebruik VisualBasicValue/VisualBasicReference of ExpressionServices.Convert(lambda) om uw werkstroom XAML-serializeerbaar te maken. Hiermee worden uw lambda-expressies geconverteerd naar expressieactiviteiten. Als u deze expressie compatibel wilt maken met XAML, gebruikt ExpressionServices u en Convert, zoals wordt weergegeven in het volgende voorbeeld.

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

Een VisualBasicValue<TResult> kan ook worden gebruikt. Houd er rekening mee dat er geen lambda-expressie is vereist wanneer u een Visual Basic-expressie gebruikt.

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()")
}

Tijdens runtime worden Visual Basic-expressies gecompileerd in LINQ-expressies. Beide eerdere voorbeelden zijn serialiseerbaar voor XAML, maar als de geserialiseerde XAML bedoeld is om te worden weergegeven en bewerkt in de werkstroomontwerper, gebruikt VisualBasicValue<TResult> u deze voor uw expressies. Geserialiseerde werkstromen die worden gebruikt ExpressionServices.Convert , kunnen worden geopend in de ontwerpfunctie, maar de waarde van de expressie is leeg. Zie Werkstromen en activiteiten van en naar XAML serialiseren voor meer informatie over het serialiseren van werkstromen naar en van XAML.

Letterlijke expressies en verwijzingstypen

Letterlijke expressies worden weergegeven in werkstromen door de Literal<T> activiteit. De volgende WriteLine activiteiten zijn functioneel gelijkwaardig.

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

Het is ongeldig om een letterlijke expressie te initialiseren met een verwijzingstype behalve String. In het volgende voorbeeld wordt de eigenschap van Value een Assign activiteit geïnitialiseerd met een letterlijke expressie met behulp van een List<string>.

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

Wanneer de werkstroom met deze activiteit wordt gevalideerd, wordt de volgende validatiefout geretourneerd: 'Letterlijk ondersteunt alleen waardetypen en het onveranderbare type System.String. Het type System.Collections.Generic.List'1[System.String] kan niet worden gebruikt als letterlijke waarde. Als de werkstroom wordt aangeroepen, wordt er een InvalidWorkflowException gegenereerd die de tekst van de validatiefout bevat. Dit is een validatiefout omdat het maken van een letterlijke expressie met een verwijzingstype geen nieuw exemplaar van het referentietype maakt voor elk exemplaar van de werkstroom. U kunt dit oplossen door de letterlijke expressie te vervangen door een expressie die een nieuw exemplaar van het referentietype maakt en retourneert.

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

Zie Expressies voor meer informatie over expressies.

Methoden voor objecten aanroepen met behulp van expressies en de InvokeMethod-activiteit

De InvokeMethod<TResult> activiteit kan worden gebruikt om statische en instantiemethoden van klassen in .NET Framework aan te roepen. In een vorig voorbeeld in dit onderwerp is een willekeurig getal gegenereerd met behulp van de Random klasse.

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

De InvokeMethod<TResult> activiteit kan ook zijn gebruikt om de Next methode van de Random klasse aan te roepen.

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  
}  

Omdat Next dit geen statische methode is, wordt er een exemplaar van de Random klasse opgegeven voor de TargetObject eigenschap. In dit voorbeeld wordt een nieuw exemplaar gemaakt met behulp van een Visual Basic-expressie, maar deze kan ook eerder zijn gemaakt en opgeslagen in een werkstroomvariabele. In dit voorbeeld is het eenvoudiger om de Assign<T> activiteit te gebruiken in plaats van de InvokeMethod<TResult> activiteit. Als de methodeaanroep uiteindelijk wordt aangeroepen door de Assign<T> of InvokeMethod<TResult> activiteiten lang actief is, InvokeMethod<TResult> heeft dit een voordeel omdat deze een RunAsynchronously eigenschap heeft. Wanneer deze eigenschap is ingesteld trueop, wordt de aangeroepen methode asynchroon uitgevoerd met betrekking tot de werkstroom. Als andere activiteiten parallel zijn, worden ze niet geblokkeerd terwijl de methode asynchroon wordt uitgevoerd. Als de methode die moet worden aangeroepen geen retourwaarde heeft, is het ook InvokeMethod<TResult> de juiste manier om de methode aan te roepen.

Argumenten en dynamische activiteiten

Een werkstroomdefinitie wordt in code gemaakt door activiteiten samen te stellen in een activiteitenstructuur en eventuele eigenschappen en argumenten te configureren. Bestaande argumenten kunnen worden gebonden, maar nieuwe argumenten kunnen niet worden toegevoegd aan activiteiten. Dit omvat werkstroomargumenten die zijn doorgegeven aan de hoofdactiviteit. In imperatieve code worden werkstroomargumenten opgegeven als eigenschappen voor een nieuw CLR-type, en in XAML worden ze gedeclareerd met behulp van x:Class en x:Member. Omdat er geen nieuw CLR-type is gemaakt wanneer een werkstroomdefinitie wordt gemaakt als een structuur van in-memory objecten, kunnen argumenten niet worden toegevoegd. Argumenten kunnen echter worden toegevoegd aan een DynamicActivity. In dit voorbeeld wordt een DynamicActivity<TResult> gemaakt waarin twee gehele getallen worden gebruikt, deze worden samengevoegd en het resultaat wordt geretourneerd. Er DynamicActivityProperty wordt een gemaakt voor elk argument en het resultaat van de bewerking wordt toegewezen aan het Result argument van de DynamicActivity<TResult>.

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);

Zie Een activiteit tijdens runtime maken voor meer informatie over dynamische activiteiten.

Gecompileerde activiteiten

Dynamische activiteiten zijn een manier om een activiteit te definiëren die argumenten bevat met behulp van code, maar activiteiten kunnen ook worden gemaakt in code en gecompileerd in typen. Eenvoudige activiteiten kunnen worden gemaakt die zijn afgeleid van CodeActivityen asynchrone activiteiten die zijn afgeleid van AsyncCodeActivity. Deze activiteiten kunnen argumenten hebben, waarden retourneren en hun logica definiëren met behulp van imperatieve code. Zie CodeActivity Base-klasse en Asynchrone activiteiten maken voor voorbeelden van het maken van deze typen activiteiten.

Activiteiten die zijn afgeleid van NativeActivity kunnen hun logica definiëren met imperatieve code en ze kunnen ook onderliggende activiteiten bevatten die de logica definiëren. Ze hebben ook volledige toegang tot de functies van de runtime, zoals het maken van bladwijzers. Zie NativeActivity Base Class, How to: Create an Activity, and the Custom Composite using Native Activity sample(s) voor voorbeelden van het maken van een NativeActivityactiviteit op basis van een op -gebaseerde activiteit.

Activiteiten die zijn afgeleid van Activity de definitie van hun logica, uitsluitend via het gebruik van onderliggende activiteiten. Deze activiteiten worden meestal gemaakt met behulp van de werkstroomontwerper, maar kunnen ook worden gedefinieerd met behulp van code. In het volgende voorbeeld wordt een Square activiteit gedefinieerd die is afgeleid van Activity<int>. De Square activiteit heeft één InArgument<T> naam Valueen definieert de logica ervan door een Sequence activiteit op te geven met behulp van de Implementation eigenschap. De Sequence activiteit bevat een WriteLine activiteit en een Assign<T> activiteit. Samen implementeren deze drie activiteiten de logica van de Square activiteit.

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))  
                }  
            }  
        };  
    }  
}  

In het volgende voorbeeld wordt een werkstroomdefinitie die bestaat uit één Square activiteit aangeroepen met behulp van WorkflowInvoker.

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

Wanneer de werkstroom wordt aangeroepen, wordt de volgende uitvoer weergegeven in de console:

Kwadratuur van de waarde: 5
Resultaat: 25