Udostępnij za pośrednictwem


Tworzenie przepływów pracy, działań i wyrażeń przy użyciu kodu imperatywnego

Definicja przepływu pracy to drzewo skonfigurowanych obiektów działań. To drzewo działań można zdefiniować na wiele sposobów, w tym przez ręczne edytowanie kodu XAML lub przy użyciu Projektant Workflow do tworzenia kodu XAML. Jednak użycie języka XAML nie jest wymagane. Definicje przepływu pracy można również tworzyć programowo. Ten temat zawiera omówienie tworzenia definicji, działań i wyrażeń przepływu pracy przy użyciu kodu. Aby zapoznać się z przykładami pracy z przepływami pracy XAML przy użyciu kodu, zobacz Serializowanie przepływów pracy i działań do i z XAML.

Tworzenie definicji przepływu pracy

Definicję przepływu pracy można utworzyć, tworząc wystąpienie wystąpienia typu działania i konfigurując właściwości obiektu działania. W przypadku działań, które nie zawierają działań podrzędnych, można to zrobić przy użyciu kilku wierszy kodu.

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

WorkflowInvoker.Invoke(wf);

Uwaga

Przykłady w tym temacie służą WorkflowInvoker do uruchamiania przykładowych przepływów pracy. Aby uzyskać więcej informacji na temat wywoływania przepływów pracy, przekazywania argumentów i różnych dostępnych opcji hostingu, zobacz Używanie przepływów pracyInvoker i WorkflowApplication.

W tym przykładzie tworzony jest przepływ pracy składający się z jednego WriteLine działania. WriteLine Argument działania Text jest ustawiony, a przepływ pracy jest wywoływany. Jeśli działanie zawiera działania podrzędne, metoda budowy jest podobna. W poniższym przykładzie użyto działania zawierającego Sequence dwa WriteLine działania.

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

WorkflowInvoker.Invoke(wf);

Używanie inicjatorów obiektów

Przykłady w tym temacie używają składni inicjowania obiektów. Składnia inicjowania obiektów może być przydatnym sposobem tworzenia definicji przepływu pracy w kodzie, ponieważ zapewnia hierarchiczny widok działań w przepływie pracy i pokazuje relację między działaniami. Podczas programowego tworzenia przepływów pracy nie trzeba używać składni inicjowania obiektów. Poniższy przykład jest funkcjonalnie odpowiednikiem poprzedniego przykładu.

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

Aby uzyskać więcej informacji na temat inicjatorów obiektów, zobacz How to: Initialize Objects without Calling a Constructor (Przewodnik programowania w języku C#) i How to: Declare an Object by Using an Object Initializer (Instrukcje: deklarowanie obiektu za pomocą inicjatora obiektów).

Praca ze zmiennymi, wartościami literałów i wyrażeniami

Podczas tworzenia definicji przepływu pracy przy użyciu kodu należy pamiętać o tym, jaki kod jest wykonywany w ramach tworzenia definicji przepływu pracy i jaki kod jest wykonywany w ramach wykonywania wystąpienia tego przepływu pracy. Na przykład następujący przepływ pracy ma na celu wygenerowanie losowej liczby i zapisanie jej w konsoli.

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

Po wykonaniu tego kodu definicji przepływu pracy wywołanie Random.Next metody jest wykonywane, a wynik jest przechowywany w definicji przepływu pracy jako wartość literału. Można wywołać wiele wystąpień tego przepływu pracy i wszystkie będą wyświetlać tę samą liczbę. Aby generowanie liczb losowych miało miejsce podczas wykonywania przepływu pracy, należy użyć wyrażenia, które jest oceniane za każdym razem, gdy przepływ pracy jest uruchamiany. W poniższym przykładzie wyrażenie języka Visual Basic jest używane z elementem VisualBasicValue<TResult>.

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

Wyrażenie w poprzednim przykładzie można również zaimplementować przy użyciu wyrażenia i CSharpValue<TResult> języka C#.

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

Wyrażenia języka C# należy skompilować przed wywołaniem przepływu pracy zawierającego je. Jeśli wyrażenia języka C# nie są kompilowane, NotSupportedException jest zgłaszany, gdy przepływ pracy jest wywoływany z komunikatem podobnym do następującego: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. W większości scenariuszy obejmujących przepływy pracy utworzone w programie Visual Studio wyrażenia języka C# są kompilowane automatycznie, ale w niektórych scenariuszach, takich jak przepływy pracy kodu, wyrażenia języka C# muszą być kompilowane ręcznie. Aby zapoznać się z przykładem kompilowania wyrażeń języka C#, zobacz sekcję Using C# expressions in code workflows (Używanie wyrażeń języka C# w przepływach pracy kodu) w temacie C# Expressions (Wyrażenia języka C#).

Wyrażenie VisualBasicValue<TResult> reprezentuje wyrażenie w składni Języka Visual Basic, które może być używane jako wartość r w wyrażeniu, a CSharpValue<TResult> reprezentuje wyrażenie w składni języka C#, które może być używane jako wartość r w wyrażeniu. Te wyrażenia są obliczane za każdym razem, gdy jest wykonywane działanie zawierające. Wynik wyrażenia jest przypisywany do zmiennej nprzepływu pracy , a te wyniki są używane przez następne działanie w przepływie pracy. Aby uzyskać dostęp do wartości zmiennej n przepływu pracy w czasie wykonywania, ActivityContext wymagany jest parametr . Dostęp do tego można uzyskać przy użyciu następującego wyrażenia lambda.

Uwaga

Należy pamiętać, że oba te przykłady są przykładami, które używają języka C# jako języka programowania, ale jeden używa VisualBasicValue<TResult> elementu i używa elementu CSharpValue<TResult>. VisualBasicValue<TResult> można CSharpValue<TResult> ich używać zarówno w projektach Visual Basic, jak i C#. Domyślnie wyrażenia utworzone w projektancie przepływu pracy są zgodne z językiem projektu hostingu. Podczas tworzenia przepływów pracy w kodzie żądany język jest według uznania autora przepływu pracy.

W tych przykładach wynik wyrażenia jest przypisywany do zmiennej nprzepływu pracy , a wyniki te są używane przez następne działanie w przepływie pracy. Aby uzyskać dostęp do wartości zmiennej n przepływu pracy w czasie wykonywania, ActivityContext wymagany jest parametr . Dostęp do tego można uzyskać przy użyciu następującego wyrażenia lambda.

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

Aby uzyskać więcej informacji na temat wyrażeń lambda, zobacz Wyrażenia lambda (odwołanie w języku C#) lub Wyrażenia lambda (Visual Basic).

Wyrażenia lambda nie można serializować do formatu XAML. Jeśli zostanie podjęta próba serializacji przepływu pracy z wyrażeniami lambda, LambdaSerializationException zostanie zgłoszony następujący komunikat: "Ten przepływ pracy zawiera wyrażenia lambda określone w kodzie. Te wyrażenia nie są serializowalne w języku XAML. Aby zapewnić możliwość serializacji przepływu pracy XAML, użyj elementu VisualBasicValue/VisualBasicReference lub ExpressionServices.Convert(lambda). Spowoduje to przekonwertowanie wyrażeń lambda na działania wyrażeń. Aby to wyrażenie było zgodne z językiem XAML, użyj wartości ExpressionServices i Convert, jak pokazano w poniższym przykładzie.

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

VisualBasicValue<TResult> Można również użyć . Należy pamiętać, że w przypadku używania wyrażenia Języka Visual Basic nie jest wymagane wyrażenie lambda.

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

W czasie wykonywania wyrażenia języka Visual Basic są kompilowane w wyrażeniaCH LINQ. Oba poprzednie przykłady można serializować do kodu XAML, ale jeśli serializowany kod XAML ma być wyświetlany i edytowany w projektancie przepływu pracy, użyj VisualBasicValue<TResult> dla wyrażeń. Serializowane przepływy pracy, które używają ExpressionServices.Convert , można otworzyć w projektancie, ale wartość wyrażenia będzie pusta. Aby uzyskać więcej informacji na temat serializacji przepływów pracy do XAML, zobacz Serializowanie przepływów pracy i działań do i z XAML.

Wyrażenia literału i typy odwołań

Wyrażenia literału są reprezentowane w przepływach pracy przez Literal<T> działanie. Następujące WriteLine działania są funkcjonalnie równoważne.

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

Inicjowanie wyrażenia literału przy użyciu dowolnego typu odwołania jest nieprawidłowe z wyjątkiem String. W poniższym przykładzie Assign właściwość działania Value jest inicjowana za pomocą wyrażenia literału przy użyciu elementu List<string>.

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

Po zweryfikowaniu przepływu pracy zawierającego to działanie zwracany jest następujący błąd weryfikacji: "Literał obsługuje tylko typy wartości i niezmienny typ System.String. Typ System.Collections.Generic.List'1[System.String] nie może być używany jako literał. Jeśli przepływ pracy jest wywoływany, InvalidWorkflowException zgłaszany jest komunikat zawierający tekst błędu walidacji. Jest to błąd weryfikacji, ponieważ utworzenie wyrażenia literału z typem odwołania nie powoduje utworzenia nowego wystąpienia typu odwołania dla każdego wystąpienia przepływu pracy. Aby rozwiązać ten problem, zastąp wyrażenie literału jednym, który tworzy i zwraca nowe wystąpienie typu odwołania.

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

Aby uzyskać więcej informacji na temat wyrażeń, zobacz Wyrażenia.

Wywoływanie metod w obiektach przy użyciu wyrażeń i działania InvokeMethod

Działanie InvokeMethod<TResult> może służyć do wywoływania metod statycznych i wystąpień klas w programie .NET Framework. W poprzednim przykładzie w tym temacie wygenerowano losową liczbę przy użyciu Random klasy .

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

Działanie InvokeMethod<TResult> mogło być również używane do wywoływania Next metody Random klasy.

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  
}  

Ponieważ Next nie jest metodą statyczną, wystąpienie Random klasy jest dostarczane dla TargetObject właściwości . W tym przykładzie nowe wystąpienie jest tworzone przy użyciu wyrażenia języka Visual Basic, ale mogło zostać utworzone wcześniej i zapisane w zmiennej przepływu pracy. W tym przykładzie użycie działania zamiast InvokeMethod<TResult> działania byłoby prostszeAssign<T>. Jeśli wywołanie metody jest ostatecznie wywoływane przez Assign<T> działanie lub InvokeMethod<TResult> jest długotrwałe, ma przewagęRunAsynchronously, InvokeMethod<TResult> ponieważ ma właściwość . Gdy ta właściwość jest ustawiona na true, wywołana metoda zostanie uruchomiona asynchronicznie w odniesieniu do przepływu pracy. Jeśli inne działania są równoległe, nie będą blokowane podczas asynchronicznego wykonywania metody. Ponadto jeśli wywoływana metoda nie ma wartości zwracanej, InvokeMethod<TResult> jest to odpowiedni sposób wywoływania metody.

Argumenty i działania dynamiczne

Definicja przepływu pracy jest tworzona w kodzie, tworząc działania w drzewie działań i konfigurując wszelkie właściwości i argumenty. Istniejące argumenty można powiązać, ale nie można dodać nowych argumentów do działań. Obejmuje to argumenty przepływu pracy przekazane do działania głównego. W kodzie imperatywne argumenty przepływu pracy są określane jako właściwości nowego typu CLR, a w języku XAML są deklarowane przy użyciu poleceń x:Class i x:Member. Ponieważ nie ma nowego typu CLR utworzonego podczas tworzenia definicji przepływu pracy jako drzewa obiektów w pamięci, nie można dodać argumentów. Argumenty można jednak dodać do elementu DynamicActivity. W tym przykładzie zostanie utworzony obiekt DynamicActivity<TResult> , który przyjmuje dwa argumenty całkowite, dodaje je razem i zwraca wynik. Element jest DynamicActivityProperty tworzony dla każdego argumentu, a wynik operacji jest przypisywany do Result argumentu 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);

Aby uzyskać więcej informacji na temat działań dynamicznych, zobacz Tworzenie działania w czasie wykonywania.

Skompilowane działania

Działania dynamiczne to jeden ze sposobów definiowania działania zawierającego argumenty przy użyciu kodu, ale działania można również tworzyć w kodzie i kompilować w typach. Proste działania można utworzyć, które pochodzą z CodeActivitydziałań asynchronicznych, które pochodzą z AsyncCodeActivityklasy . Te działania mogą mieć argumenty, zwracane wartości i definiować logikę przy użyciu kodu imperatywnego. Przykłady tworzenia tych typów działań można znaleźć w temacie CodeActivity Base Class (Klasa podstawowa CodeActivity) i Creating Asynchronous Activities (Tworzenie działań asynchronicznych).

Działania pochodzące z NativeActivity programu mogą definiować logikę przy użyciu kodu imperatywnego i mogą również zawierać działania podrzędne definiujące logikę. Mają również pełny dostęp do funkcji środowiska uruchomieniowego, takich jak tworzenie zakładek. Przykłady tworzenia działania opartego NativeActivityna funkcjach można znaleźć w temacie NativeActivity Base Class (Klasa podstawowa NativeActivity), How to: Create an Activity (Jak utworzyć działanie) i Custom Composite using Native Activity sample (Tworzenie działania niestandardowego przy użyciu działania natywnego).

Działania pochodzące z Activity definiowania ich logiki wyłącznie za pomocą działań podrzędnych. Te działania są zwykle tworzone przy użyciu projektanta przepływu pracy, ale można je również zdefiniować przy użyciu kodu. W poniższym przykładzie zdefiniowano Square działanie pochodzące z klasy Activity<int>. Działanie Square ma jedną InArgument<T> nazwę Valuei definiuje jej logikę Sequence , określając działanie przy użyciu Implementation właściwości . Działanie Sequence zawiera WriteLine działanie i Assign<T> działanie. Razem te trzy działania implementują logikę Square działania.

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

W poniższym przykładzie definicja przepływu pracy składająca się z jednego Square działania jest wywoływana przy użyciu polecenia WorkflowInvoker.

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

Po wywołaniu przepływu pracy do konsoli zostaną wyświetlone następujące dane wyjściowe:

Kwadratuj wartość: 5
Wynik: 25