Поделиться через


Создание рабочих процессов, действий и выражений с помощью императивного кода

Определение рабочего процесса — это дерево настроенных объектов действий. Это дерево действий можно определить различными способами, включая ручное редактирование XAML или с помощью конструктора рабочих процессов для создания XAML. Однако использование XAML не является обязательным. Определения рабочих процессов также можно создавать программным способом. В этом разделе представлен обзор создания определений рабочих процессов, действий и выражений с помощью кода. Примеры работы с рабочими процессами XAML с помощью кода см. в статье сериализация рабочих процессов и действий из XAML.

Создание определений рабочих процессов

Определение рабочего процесса можно создать, создав экземпляр типа действия и настроив свойства объекта действия. Для задач, которые не содержат дочерних задач, это можно выполнить с помощью нескольких строк кода.

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

WorkflowInvoker.Invoke(wf);

Замечание

Примеры в этом разделе используют WorkflowInvoker для выполнения рабочих процессов. Дополнительные сведения о вызове рабочих процессов, передаче аргументов и различных доступных вариантах размещения см. в разделе Using WorkflowInvoker и WorkflowApplication.

В этом примере создается рабочий процесс, состоящий из одного WriteLine действия. Аргумент WriteLine действия Text задан и вызывается рабочий процесс. Если действие содержит дочерние действия, метод построения аналогичен. В следующем примере используется Sequence действие, содержащее два WriteLine действия.

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

WorkflowInvoker.Invoke(wf);

Использование инициализаторов объектов

В примерах этого раздела используется синтаксис инициализации объектов. Синтаксис инициализации объектов может быть полезным способом создания определений рабочих процессов в коде, так как он предоставляет иерархическое представление действий в рабочем процессе и показывает связь между действиями. При программном создании рабочих процессов не требуется использовать синтаксис инициализации объектов. Следующий пример функционально эквивалентен предыдущему примеру.

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

Дополнительные сведения о инициализаторах объектов см. в статье "Практическое руководство. Инициализация объектов без вызова конструктора (руководство по программированию на C#) и практическое руководство. Объявление объекта с помощью инициализатора объектов.

Работа с переменными, литеральными значениями и выражениями

При создании определения рабочего процесса с помощью кода следует учитывать, какой код выполняется как часть создания определения рабочего процесса и какой код выполняется в рамках выполнения экземпляра этого рабочего процесса. Например, следующий рабочий процесс предназначен для создания случайного числа и записи его в консоль.

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

При выполнении этого кода определения рабочего процесса вызов Random.Next выполняется и результат хранится в определении рабочего процесса в виде литерального значения. Многие экземпляры этого рабочего процесса могут вызываться, и все они будут отображать одно и то же число. Во время выполнения рабочего процесса для генерации случайных чисел необходимо использовать выражение, которое вычисляется при каждом запуске рабочего процесса. В следующем примере выражение Visual Basic используется с параметром VisualBasicValue<TResult>.

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

Выражение в предыдущем примере также можно реализовать, используя CSharpValue<TResult> и выражение на C#.

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

Выражения C# необходимо скомпилировать перед вызовом рабочего процесса, содержащего их. Если выражения C# не компилируются, NotSupportedException возникает при вызове рабочего процесса с сообщением, аналогичным следующему: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. В большинстве сценариев, связанных с рабочими процессами, созданными в Visual Studio, выражения C# компилируются автоматически, но в некоторых сценариях, таких как рабочие процессы кода, выражения C# должны быть скомпилированы вручную. Пример компиляции выражений C# см. в разделе "Использование выражений C# в рабочих процессах кода " раздела "Выражения C# ".

Выражение VisualBasicValue<TResult> означает, что в синтаксисе Visual Basic его можно использовать как r-значение в выражении, а выражение CSharpValue<TResult> означает то же самое в синтаксисе C#. Эти выражения вычисляются каждый раз при выполнении содержащего действия. Результат выражения назначается переменной nрабочего процесса, и эти результаты используются следующим действием в рабочем процессе. Для доступа к значению переменной рабочего процесса n во время выполнения требуется ActivityContext. К этому можно получить доступ с помощью следующего лямбда-выражения.

Замечание

Обратите внимание, что оба этих кода являются примерами использования C# в качестве языка программирования, но один использует a VisualBasicValue<TResult> и один использует .CSharpValue<TResult> VisualBasicValue<TResult> и CSharpValue<TResult> может использоваться как в проектах Visual Basic, так и в C#. По умолчанию выражения, созданные в конструкторе рабочих процессов, соответствуют языку проекта размещения. При создании рабочих процессов в коде нужный язык находится на усмотрении автора рабочего процесса.

В этих примерах результат выражения назначается переменной nрабочего процесса, и эти результаты используются следующим действием в рабочем процессе. Для доступа к значению переменной рабочего процесса n во время выполнения требуется ActivityContext. К этому можно получить доступ с помощью следующего лямбда-выражения.

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

Дополнительные сведения о лямбда-выражениях см. в лямбда-выражениях (справочнике по C#) или лямбда-выражениям (Visual Basic).

Лямбда-выражения не сериализуются в формате XAML. Если предпринята попытка сериализации рабочего процесса с лямбда-выражениями, LambdaSerializationException создается следующее сообщение: "Этот рабочий процесс содержит лямбда-выражения, указанные в коде. Эти выражения не сериализуются в XAML. Чтобы сделать рабочий процесс xaml-сериализуемым, используйте VisualBasicValue/VisualBasicReference или ExpressionServices.Convert(lambda). Это преобразует лямбда-выражения в выраженные действия. Чтобы сделать это выражение совместимым с XAML, используйте ExpressionServices и Convert, как показано в следующем примере.

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>. Обратите внимание, что при использовании выражения Visual Basic не требуется лямбда-выражение.

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

Во время выполнения выражения Visual Basic компилируются в выражения LINQ. Оба предыдущих примера сериализуются в XAML, но если сериализованный XAML предназначен для просмотра и редактирования в конструкторе рабочих процессов, используется VisualBasicValue<TResult> для выражений. Сериализованные рабочие процессы, которые используются ExpressionServices.Convert , можно открыть в конструкторе, но значение выражения будет пустым. Дополнительные сведения о сериализации рабочих процессов в XAML см. в статье сериализация рабочих процессов и действий из XAML.

Литеральные выражения и ссылочные типы

Литеральные выражения представлены в рабочих процессах действием Literal<T> . Следующие WriteLine действия функционально эквивалентны.

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

Недопустимо инициализировать литеральное выражение с любым ссылочным типом, кроме String. В следующем примере свойство Assign действия Value инициализируется с помощью литерального выражения, используя List<string>.

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

При проверке рабочего процесса, содержащего это действие, возвращается следующая ошибка проверки: "Литерал поддерживает только типы значений и неизменяемый тип System.String. Тип System.Collections.Generic.List'1[System.String] не может использоваться как литерал". Если вызывается рабочий процесс, создаётся исключение, InvalidWorkflowException, которое содержит текст ошибки проверки. Это ошибка проверки, так как создание литерального выражения с типом ссылки не создает новый экземпляр ссылочного типа для каждого экземпляра рабочего процесса. Чтобы устранить эту проблему, замените литеральное выражение на такое, которое создаёт и возвращает новый экземпляр ссылочного типа.

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

Дополнительные сведения о выражениях см. в разделе "Выражения".

Вызов методов для объектов с помощью выражений и действия InvokeMethod

Это InvokeMethod<TResult> действие можно использовать для вызова статических и экземплярных методов классов в .NET Framework. В предыдущем примере в этом разделе было создано случайное число с помощью Random класса.

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

Действие InvokeMethod<TResult> также можно было бы использовать для вызова Next метода 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  
}  

Поскольку Next не является статическим методом, для свойства Random предоставляется экземпляр класса TargetObject. В этом примере новый экземпляр создается с помощью выражения Visual Basic, но он также может быть создан ранее и сохранен в переменной рабочего процесса. В этом примере было бы проще использовать действие Assign<T> вместо действия InvokeMethod<TResult>. Если вызов метода, который в конечном итоге вызывается действиями Assign<T> или InvokeMethod<TResult>, выполняется долго, InvokeMethod<TResult> имеет преимущество, так как у него есть свойство RunAsynchronously. Если для этого свойства задано trueзначение, вызываемый метод будет выполняться асинхронно в отношении рабочего процесса. Если другие действия находятся параллельно, они не будут заблокированы во время асинхронного выполнения метода. Кроме того, если вызываемый метод не имеет возвращаемого значения, то InvokeMethod<TResult> это подходящий способ вызова метода.

Аргументы и динамические действия

Определение рабочего процесса создается в коде путем сборки действий в дерево действий и настройки любых свойств и аргументов. Существующие аргументы могут быть привязаны, но новые аргументы нельзя добавить в действия. К ним относятся аргументы рабочего процесса, передаваемые корневому действию. В императивном коде аргументы рабочего процесса указываются в качестве свойств нового типа СРЕДЫ CLR, а в XAML они объявляются с помощью x:Class и x:Member. Так как при создании определения рабочего процесса в виде дерева объектов в памяти нет нового типа CLR, аргументы нельзя добавить. Однако аргументы можно добавить в DynamicActivity. В этом примере создается объект, DynamicActivity<TResult> который принимает два целых аргумента, добавляет их вместе и возвращает результат. Для каждого аргумента создается DynamicActivityProperty, а результат операции назначается аргументу Result компонента 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);

Дополнительные сведения о динамических действиях см. в разделе "Создание действия в среде выполнения".

Сводка мероприятий

Динамические действия — это один из способов определения действия, содержащего аргументы с помощью кода, но действия также можно создавать в коде и компилировать в типы. Простые действия могут быть созданы из CodeActivity, а асинхронные действия из AsyncCodeActivity. Эти действия могут иметь аргументы, возвращаемые значения и определять логику с помощью императивного кода. Примеры создания этих типов действий см. в разделе "Базовый класс CodeActivity" и "Создание асинхронных действий".

Действия, производные от NativeActivity, могут определять свою логику с помощью императивного кода и также могут содержать дочерние действия, которые определяют логику. Они также имеют полный доступ к функциям среды выполнения, таким как создание закладок. Примеры создания деятельности на основе NativeActivity см. в «Базовый класс NativeActivity», «Практическое руководство: Создание деятельности» и примере «Настраиваемый составной элемент с использованием Native Activity».

Действия, происходящие от Activity, определяют свою логику исключительно с помощью дочерних действий. Обычно эти действия создаются с помощью конструктора рабочих процессов, но также могут быть определены с помощью кода. В следующем примере активность Square определяется как наследуемая от Activity<int>. Действие Square имеет один InArgument<T> с именем Value, и определяет свою логику, задавая Sequence действие через свойство Implementation. Действие Sequence содержит WriteLine действие и Assign<T> действие. Вместе эти три действия реализуют логику Square действия.

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

В следующем примере определение рабочего процесса, состоящее из одного Square действия, вызывается с помощью WorkflowInvoker.

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

При вызове рабочего процесса в консоли отображаются следующие выходные данные:

Возведение в квадрат значения: 5
Результат: 25