Dela via


Redigera arbetsflöden, aktiviteter och uttryck med imperativ kod

En arbetsflödesdefinition är ett träd med konfigurerade aktivitetsobjekt. Det här aktivitetsträdet kan definieras på många olika sätt, till exempel genom handredigering av XAML eller genom att använda Arbetsflödesdesignern för att skapa XAML. Användning av XAML är dock inte ett krav. Arbetsflödesdefinitioner kan också skapas programmatiskt. Det här avsnittet innehåller en översikt över hur du skapar arbetsflödesdefinitioner, aktiviteter och uttryck med hjälp av kod. Exempel på hur du arbetar med XAML-arbetsflöden med hjälp av kod finns i Serialisera arbetsflöden och aktiviteter till och från XAML.

Skapa arbetsflödesdefinitioner

Du kan skapa en arbetsflödesdefinition genom att instansiera en instans av en aktivitetstyp och konfigurera aktivitetsobjektets egenskaper. För aktiviteter som inte innehåller underordnade aktiviteter kan detta utföras med några rader kod.

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

WorkflowInvoker.Invoke(wf);

Kommentar

Exemplen i det här avsnittet används WorkflowInvoker för att köra exempelarbetsflödena. Mer information om hur du anropar arbetsflöden, skickar argument och de olika värdalternativ som är tillgängliga finns i Använda WorkflowInvoker och WorkflowApplication.

I det här exemplet skapas ett arbetsflöde som består av en enda WriteLine aktivitet. Aktivitetens WriteLine argument har angetts Text och arbetsflödet anropas. Om en aktivitet innehåller underordnade aktiviteter är konstruktionsmetoden liknande. I följande exempel används en Sequence aktivitet som innehåller två WriteLine aktiviteter.

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

WorkflowInvoker.Invoke(wf);

Använda objektinitierare

Exemplen i det här avsnittet använder initieringssyntax för objekt. Initieringssyntax för objekt kan vara ett användbart sätt att skapa arbetsflödesdefinitioner i kod eftersom det ger en hierarkisk vy över aktiviteterna i arbetsflödet och visar relationen mellan aktiviteterna. Det finns inget krav på att använda initieringssyntax för objekt när du programmatiskt skapar arbetsflöden. Följande exempel är funktionellt likvärdigt med föregående exempel.

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

Mer information om objektinitierare finns i Så här initierar du objekt utan att anropa en konstruktor (C#-programmeringsguide) och Så här deklarerar du ett objekt med hjälp av en objektinitierare.

Arbeta med variabler, literalvärden och uttryck

När du skapar en arbetsflödesdefinition med hjälp av kod bör du vara medveten om vilken kod som körs som en del av skapandet av arbetsflödesdefinitionen och vilken kod som körs som en del av körningen av en instans av arbetsflödet. Följande arbetsflöde är till exempel avsett att generera ett slumptal och skriva det till konsolen.

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

När den här arbetsflödesdefinitionskoden körs görs anropet till Random.Next och resultatet lagras i arbetsflödesdefinitionen som ett literalvärde. Många instanser av det här arbetsflödet kan anropas och alla skulle visa samma nummer. För att slumptalsgenereringen ska ske under arbetsflödeskörningen måste ett uttryck användas som utvärderas varje gång arbetsflödet körs. I följande exempel används ett Visual Basic-uttryck med en VisualBasicValue<TResult>.

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

Uttrycket i föregående exempel kan också implementeras med ett CSharpValue<TResult> och ett C#-uttryck.

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

C#-uttryck måste kompileras innan arbetsflödet som innehåller dem anropas. Om C#-uttrycken inte kompileras utlöses ett NotSupportedException när arbetsflödet anropas med ett meddelande som liknar följande: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. I de flesta scenarier med arbetsflöden som skapats i Visual Studio kompileras C#-uttryck automatiskt, men i vissa scenarier, till exempel kodarbetsflöden, måste C#-uttrycken kompileras manuellt. Ett exempel på hur du kompilerar C#-uttryck finns i avsnittet Använda C#-uttryck i kodarbetsflöden i C#-uttryck .

A VisualBasicValue<TResult> representerar ett uttryck i Visual Basic-syntax som kan användas som ett r-värde i ett uttryck och ett CSharpValue<TResult> uttryck i C#-syntax som kan användas som ett r-värde i ett uttryck. Dessa uttryck utvärderas varje gång den innehållande aktiviteten körs. Resultatet av uttrycket tilldelas till arbetsflödesvariabeln noch dessa resultat används av nästa aktivitet i arbetsflödet. För att få åtkomst till värdet för arbetsflödesvariabeln n vid körning ActivityContext krävs. Du kan komma åt detta med hjälp av följande lambda-uttryck.

Kommentar

Observera att båda dessa kod är exempel som använder C# som programmeringsspråk, men en använder en VisualBasicValue<TResult> och en använder en CSharpValue<TResult>. VisualBasicValue<TResult> och CSharpValue<TResult> kan användas i både Visual Basic- och C#-projekt. Som standard matchar uttryck som skapats i arbetsflödesdesignern språket för värdprojektet. När du skapar arbetsflöden i kod är det önskade språket efter eget gottfinnande för arbetsflödesförfattaren.

I dessa exempel tilldelas resultatet av uttrycket till arbetsflödesvariabeln noch dessa resultat används av nästa aktivitet i arbetsflödet. För att få åtkomst till värdet för arbetsflödesvariabeln n vid körning ActivityContext krävs. Du kan komma åt detta med hjälp av följande lambda-uttryck.

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

Mer information om lambda-uttryck finns i Lambda-uttryck (C#-referens) eller Lambda-uttryck (Visual Basic).

Lambda-uttryck kan inte serialiseras i XAML-format. Om ett försök att serialisera ett arbetsflöde med lambda-uttryck görs genereras ett LambdaSerializationException med följande meddelande: "Det här arbetsflödet innehåller lambda-uttryck som anges i kod. Dessa uttryck är inte XAML-serialiserbara. För att göra arbetsflödet XAML-serialiserbart använder du antingen VisualBasicValue/VisualBasicReference eller ExpressionServices.Convert(lambda). Detta konverterar dina lambda-uttryck till uttrycksaktiviteter." Om du vill göra det här uttrycket kompatibelt med XAML använder du ExpressionServices och Convert, enligt följande exempel.

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

En VisualBasicValue<TResult> kan också användas. Observera att inget lambda-uttryck krävs när du använder ett Visual Basic-uttryck.

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

Vid körning kompileras Visual Basic-uttryck till LINQ-uttryck. Båda de föregående exemplen är serialiserbara för XAML, men om den serialiserade XAML är avsedd att visas och redigeras i arbetsflödesdesignern använder du VisualBasicValue<TResult> för dina uttryck. Serialiserade arbetsflöden som använder ExpressionServices.Convert kan öppnas i designern, men värdet för uttrycket blir tomt. Mer information om hur du serialiserar arbetsflöden till XAML finns i Serialisera arbetsflöden och aktiviteter till och från XAML.

Literaluttryck och referenstyper

Literaluttryck representeras i arbetsflöden Literal<T> av aktiviteten. Följande WriteLine aktiviteter är funktionellt likvärdiga.

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

Det är ogiltigt att initiera ett literaluttryck med någon referenstyp förutom String. I följande exempel initieras en Assign aktivitets Value egenskap med ett literaluttryck med hjälp av en List<string>.

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

När arbetsflödet som innehåller den här aktiviteten verifieras returneras följande verifieringsfel: "Literal stöder endast värdetyper och den oföränderliga typen System.String. Typen System.Collections.Generic.List'1[System.String] kan inte användas som en literal." Om arbetsflödet anropas utlöses en InvalidWorkflowException som innehåller texten i verifieringsfelet. Det här är ett valideringsfel eftersom skapandet av ett literaluttryck med en referenstyp inte skapar en ny instans av referenstypen för varje instans av arbetsflödet. Lös detta genom att ersätta literaluttrycket med ett som skapar och returnerar en ny instans av referenstypen.

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

Mer information om uttryck finns i Uttryck.

Anropa metoder på objekt med hjälp av uttryck och InvokeMethod-aktiviteten

Aktiviteten InvokeMethod<TResult> kan användas för att anropa statiska metoder och instansmetoder för klasser i .NET Framework. I ett tidigare exempel i det här avsnittet genererades ett slumpmässigt tal med hjälp av Random klassen.

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

Aktiviteten InvokeMethod<TResult> kunde också ha använts för att anropa Next -metoden för Random klassen.

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  
}  

Eftersom Next inte är en statisk metod tillhandahålls en instans av Random klassen för TargetObject egenskapen. I det här exemplet skapas en ny instans med ett Visual Basic-uttryck, men det kan också ha skapats tidigare och lagrats i en arbetsflödesvariabel. I det här exemplet skulle det vara enklare att använda Assign<T> aktiviteten i stället InvokeMethod<TResult> för aktiviteten. Om metodanropet som slutligen anropas av antingen aktiviteterna Assign<T> eller InvokeMethod<TResult> är tidskrävande, InvokeMethod<TResult> har en fördel eftersom det har en RunAsynchronously egenskap. När den här egenskapen är inställd truepå körs den anropade metoden asynkront med avseende på arbetsflödet. Om andra aktiviteter är parallella blockeras de inte medan metoden körs asynkront. Om metoden som ska anropas inte har något returvärde är det lämpligt InvokeMethod<TResult> sätt att anropa metoden.

Argument och dynamiska aktiviteter

En arbetsflödesdefinition skapas i kod genom att aktiviteter samlas i ett aktivitetsträd och konfigurera eventuella egenskaper och argument. Befintliga argument kan bindas, men nya argument kan inte läggas till i aktiviteter. Detta inkluderar arbetsflödesargument som skickas till rotaktiviteten. I imperativ kod anges arbetsflödesargument som egenskaper för en ny CLR-typ, och i XAML deklareras de med hjälp x:Class av och x:Member. Eftersom ingen ny CLR-typ skapas när en arbetsflödesdefinition skapas som ett träd med minnesinterna objekt kan argument inte läggas till. Argument kan dock läggas till i en DynamicActivity. I det här exemplet skapas en DynamicActivity<TResult> som tar två heltalsargument, lägger till dem tillsammans och returnerar resultatet. A DynamicActivityProperty skapas för varje argument och resultatet av åtgärden tilldelas till Result argumentet för 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);

Mer information om dynamiska aktiviteter finns i Skapa en aktivitet vid körning.

Kompilerade aktiviteter

Dynamiska aktiviteter är ett sätt att definiera en aktivitet som innehåller argument med hjälp av kod, men aktiviteter kan också skapas i kod och kompileras till typer. Enkla aktiviteter kan skapas som härleds från CodeActivity, och asynkrona aktiviteter som härleds från AsyncCodeActivity. Dessa aktiviteter kan ha argument, returnera värden och definiera deras logik med imperativ kod. Exempel på hur du skapar dessa typer av aktiviteter finns i CodeActivity Base Class och Creating Asynchronous Activities (CodeActivity Base Class ) och Creating Asynchronous Activities (Skapa asynkrona aktiviteter).

Aktiviteter som härleds från NativeActivity kan definiera sin logik med imperativ kod och de kan också innehålla underordnade aktiviteter som definierar logiken. De har också fullständig åtkomst till funktionerna i körningen, till exempel att skapa bokmärken. Exempel på hur du skapar en NativeActivity-baserad aktivitet finns i NativeActivity Base Class, How to: Create an Activity ,, and the Custom Composite using Native Activity sample (NativeActivity Base Class, How to: Create an Activity, and the Custom Composite using Native Activity sample).

Aktiviteter som härleds från Activity definierar sin logik enbart genom användning av underordnade aktiviteter. Dessa aktiviteter skapas vanligtvis med hjälp av arbetsflödesdesignern, men kan också definieras med hjälp av kod. I följande exempel definieras en Square aktivitet som härleds från Activity<int>. Aktiviteten Square har ett enda InArgument<T> namn Valueoch definierar dess logik genom att ange en Sequence aktivitet med hjälp av Implementation egenskapen . Aktiviteten Sequence innehåller en WriteLine aktivitet och en Assign<T> aktivitet. Tillsammans implementerar dessa tre aktiviteter aktivitetens Square logik.

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

I följande exempel anropas en arbetsflödesdefinition som består av en enda Square aktivitet med hjälp av WorkflowInvoker.

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

När arbetsflödet anropas visas följande utdata för konsolen:

Kvarsar värdet: 5
Resultat: 25