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 Projektanta przepływu pracy 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 typu działalności i konfigurując właściwości obiektu działalności. W przypadku aktywności, które nie zawierają aktywności podrzędnych, można to zrobić przy użyciu kilku wierszy kodu.

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

WorkflowInvoker.Invoke(wf);

Uwaga / Notatka

Przykłady przedstawione w tym temacie wykorzystują WorkflowInvoker do wykonywania 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 WorkflowInvoker 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 aktywności Sequence, która zawiera dwie WriteLine aktywności.

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 stałymi 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))
        }
    }
};

Kiedy ten kod definicji przepływu pracy jest wykonany, następuje wywołanie Random.Next, a wynik jest przechowywany w definicji przepływu pracy jako wartość literalna. 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 CSharpValue<TResult> i wyrażenia 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 temacieC# 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 wykonywana jest zawierająca je aktywność. 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 w czasie wykonywania uzyskać dostęp do wartości zmiennej n przepływu, jest wymagany ActivityContext parametr. Dostęp do tego można uzyskać przy użyciu następującego wyrażenia lambda.

Uwaga / Notatka

Należy pamiętać, że oba te przykłady kodu używają języka programowania C#, ale jeden korzysta z elementu VisualBasicValue<TResult>, a drugi z elementu CSharpValue<TResult>. VisualBasicValue<TResult> i CSharpValue<TResult> można 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 w czasie wykonywania uzyskać dostęp do wartości zmiennej n przepływu, jest wymagany ActivityContext 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 umożliwić serializację przepływu pracy XAML, użyj 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))
}

Można również użyć VisualBasicValue<TResult>. 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 na wyrażenia 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łów 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.")  
}  

Inicjalizowanie wyrażenia literału przy użyciu jakiegokolwiek typu referencyjnego jest nieprawidłowe z wyjątkiem String. W poniższym przykładzie właściwość aktywności Assign jest inicjowana za pomocą wyrażenia literału przy użyciu Value.

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 dla typu referencyjnego nie powoduje utworzenia nowej instancji typu referencyjnego dla każdej instancji przepływu pracy. Aby rozwiązać ten problem, zastąp wyrażenie literalne jednym, które 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)")
}

Aktywność InvokeMethod<TResult> mogła być również użyta do wywołania metody Next klasy Random.

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ą, instancja klasy Random jest przekazywana do właściwości TargetObject. 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, zamiast Assign<T> lepiej użyć InvokeMethod<TResult>. Jeśli wywołanie metody, ostatecznie wywołane przez działania Assign<T> lub InvokeMethod<TResult>, jest długotrwałe, InvokeMethod<TResult> ma przewagę, ponieważ ma właściwość RunAsynchronously. 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. Dla każdego argumentu tworzony jest DynamicActivityProperty, 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 dynamicznych aktywności, zobacz Tworzenie aktywności w czasie uruchomienia.

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 czynności można utworzyć, które pochodzą z CodeActivity, oraz czynności asynchroniczne, które pochodzą z AsyncCodeActivity. 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 wynikające z NativeActivity mogą definiować swoją logikę przy użyciu kodu imperatywnego, a także mogą zawierać działania podrzędne, które definiują logikę. Mają również pełny dostęp do funkcji środowiska uruchomieniowego, takich jak tworzenie zakładek. Przykłady tworzenia działania opartego na NativeActivity, można znaleźć w tematach: NativeActivity Base Class (Klasa podstawowa NativeActivity), How to: Create an Activity (Jak utworzyć działanie) oraz w przykładzie Custom Composite using Native Activity (Tworzenie działania niestandardowego przy użyciu działania natywnego).

Działanie pochodzące z Activity definiują swoją logikę 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 aktywność pochodzącą z Activity<int>. Działanie Square ma pojedyncze InArgument<T> o nazwie Value, a jego logikę określa, specyfikując działanie Sequence przy użyciu właściwości Implementation. Aktywność Sequence zawiera aktywność WriteLine oraz Assign<T>. 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