Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Определение рабочего процесса — это дерево настроенных объектов действий. Это дерево действий можно определить различными способами, включая ручное редактирование 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