Введение разработчика в Windows Workflow Foundation (WF) в .NET 4
Мэтт Милнер, Pluralsight
Ноябрь 2009 г.
Обновлено до выпуска: апрель 2010 г.
Обзор
Поскольку разработчики программного обеспечения знают, написание приложений может быть сложной задачей, и мы постоянно ищем инструменты и платформы, чтобы упростить процесс и помочь нам сосредоточиться на бизнес-задачах, которые мы пытаемся решить. Мы перешли от написания кода на компьютерах, таких как сборщик, на более высокий уровень языков, таких как C# и Visual Basic, которые упрощают разработку, устраняют проблемы низкого уровня, такие как управление памятью, и повышают производительность как разработчики. Для разработчиков Майкрософт переход на .NET позволяет среде CLR выделить память, очистить ненужные объекты и обрабатывать конструкции низкого уровня, такие как указатели.
Большая часть сложности приложения живет в логике и обработке, которая продолжается за кулисами. Такие проблемы, как асинхронное или параллельное выполнение, а также координация задач для реагирования на запросы пользователей или служб может быстро привести разработчиков приложений к низкому уровню кодирования дескрипторов, обратных вызовов, синхронизации и т. д. Как разработчики, нам нужна та же мощность и гибкость декларативной модели программирования для внутренних элементов приложения, что и для пользовательского интерфейса в Windows Presentation Foundation (WPF). Windows Workflow Foundation (WF) предоставляет декларативную платформу для создания логики приложений и служб и предоставляет разработчикам более высокий уровень языка для обработки асинхронных, параллельных задач и другой сложной обработки.
Наличие среды выполнения для управления памятью и объектами позволило нам сосредоточиться на важных бизнес-аспектах написания кода. Аналогичным образом, наличие среды выполнения, которая может управлять сложностями координации асинхронной работы, предоставляет набор функций, которые повышают производительность разработчика. WF — это набор средств для объявления рабочего процесса (бизнес-логики), действий, помогающих определить поток логики и управления, а также среду выполнения для выполнения результирующего определения приложения. Короче говоря, WF использует более высокий уровень языка для написания приложений, с целью повышения производительности разработчиков, упрощения управления приложениями и более быстрой реализации. Среда выполнения WF не только выполняет рабочие процессы, но и предоставляет службы и функции, важные при написании логики приложения, таких как сохраняемость состояния, закладки и возобновление бизнес-логики, все из которых приводят к гибкости потоков и процессов, обеспечивая масштабирование и масштабирование бизнес-процессов.
Для получения дополнительных концептуальных сведений о том, как использовать WF для создания приложений, рекомендуется ознакомиться с разделом "Дополнительные ресурсы" в разделе "Рабочий процесс" Дэвида Чаппела.
Новые возможности WF4
В версии 4 Microsoft® .NET Framework Windows Workflow Foundation представляет значительное количество изменений от предыдущих версий технологии, которая поставляется в составе .NET 3.0 и 3.5. На самом деле, команда пересмотрела ядро модели программирования, среды выполнения и инструментов и перепроектировала каждую из них для повышения производительности и производительности, а также для решения важных отзывов, полученных от взаимодействия с клиентами с помощью предыдущих версий. Значительные изменения были необходимы, чтобы обеспечить лучший интерфейс для разработчиков, внедряющих WF, и для того, чтобы WF по-прежнему был сильным базовым компонентом, который можно создать в своих приложениях. Я познакомим с высоким уровнем изменений здесь, и на протяжении всего документа каждый раздел получит более глубокое лечение.
Прежде чем продолжить, важно понимать, что обратная совместимость также является ключевой целью в этом выпуске. Новые компоненты платформы находятся в основном в сборках System.Activities.* в то время как компоненты платформы с обратной совместимостью находятся в сборках System.Workflow.* . Сборки System.Workflow.* являются частью .NET Framework 4 и обеспечивают полную обратную совместимость, чтобы вы могли перенести приложение в .NET 4 без изменений в код рабочего процесса. На протяжении всего этого документа я буду использовать имя WF4 для ссылки на новые компоненты, найденные в сборках System.Activities.* и WF3, чтобы ссылаться на компоненты, найденные в сборках System.Workflow.* .
Дизайнеров
Одним из наиболее видимых областей улучшения является конструктор рабочих процессов. Удобство использования и производительность были ключевыми целями команды для выпуска VS 2010. Теперь конструктор поддерживает возможность работать с гораздо большими рабочими процессами без снижения производительности и конструкторов, основанных на Windows Presentation Foundation (WPF), используя все преимущества расширенного взаимодействия с пользователем, которые можно создать с помощью декларативной платформы пользовательского интерфейса. Разработчики действий будут использовать XAML для определения того, как их действия выглядят и взаимодействуют с пользователями в визуальной среде разработки. Кроме того, повторное размещение конструктора рабочих процессов в собственных приложениях, чтобы не разработчики могли просматривать и взаимодействовать с рабочими процессами, теперь гораздо проще.
Поток данных
В WF3 поток данных в рабочем процессе непрозрачн. WF4 предоставляет четкую, краткую модель для потока данных и области использования аргументов и переменных. Эти понятия, знакомые всем разработчикам, упрощают определение хранилища данных, а также поток данных в рабочие процессы и действия. Модель потока данных также делает более очевидными ожидаемые входные и выходные данные заданного действия и повышает производительность среды выполнения по мере упрощения управления данными.
Блок-схема
Добавлено новое действие потока управления с именем Flowchart, чтобы разработчики смогли использовать модель блок-схемы для определения рабочего процесса. Блок-схема более тесно напоминает концепции и мыслительные процессы, которые многие аналитики и разработчики проходят при создании решений или разработке бизнес-процессов. Поэтому имеет смысл обеспечить деятельность, чтобы упростить моделирование концептуального мышления и планирования, которые уже были выполнены. Блок-схема включает такие понятия, как возврат к предыдущим шагам и разделение логики на основе одного условия или логики switch /Case.
Модель программирования
Модель программирования WF была обновлена, чтобы сделать ее более простой и более надежной. Действие является основным базовым типом в модели программирования и представляет как рабочие процессы, так и действия. Кроме того, вам больше не нужно создавать WorkflowRuntime для вызова рабочего процесса, можно просто создать экземпляр и выполнить его, упрощая модульные тесты и сценарии приложений, в которых не требуется выполнять проблемы при настройке определенной среды. Наконец, модель программирования рабочего процесса становится полностью декларативной композицией действий без кода, кроме упрощения разработки рабочих процессов.
Интеграция Windows Communication Foundation (WCF)
Преимущества WF, безусловно, применяются как к созданию служб, так и к потреблению или координации взаимодействия служб. Много усилий пошел на повышение интеграции между WCF и WF. Новые действия обмена сообщениями, корреляция сообщений и улучшенная поддержка размещения, а также полностью декларативное определение службы являются основными областями улучшения.
Начало работы с рабочим процессом
Лучший способ понять WF заключается в том, чтобы начать использовать его и применять концепции. Я рассмотрим несколько основных понятий, связанных с основами рабочего процесса, а затем рассмотрим создание нескольких простых рабочих процессов, чтобы проиллюстрировать, как эти понятия связаны друг с другом.
Структура рабочего процесса
Действия — это стандартные блоки WF, а все действия в конечном итоге являются производными от действия. Примечание по терминологии. Действия являются единицей работы в WF. Действия можно объединить в более крупные действия. Если действие используется в качестве точки входа верхнего уровня, она называется "Рабочий процесс", как и Main, просто другая функция, представляющая точку входа верхнего уровня в программы CLR. Например, на рис. 1 показан простой рабочий процесс, встроенный в код.
Последовательности s = новая последовательность
{
Действия = {
new WriteLine {Text = "Hello"},
новая последовательность {
Действия =
{
new WriteLine {Text = "Workflow"},
new WriteLine {Text = "World"}
}
}
}
};
рис. 1. Простой рабочий процесс
Обратите внимание на рис. 1, что действие последовательности используется в качестве корневого действия для определения стиля потока корневого элемента управления для рабочего процесса. Любое действие можно использовать в качестве корневого или рабочего процесса и выполняться даже простой строкой записи. Задав свойство "Действия" в последовательности с коллекцией других действий, я определил структуру рабочего процесса. Кроме того, дочерние действия могут иметь дочерние действия, создавая дерево действий, составляющих общее определение рабочего процесса.
Шаблоны рабочих процессов и конструктор рабочих процессов
WF4 поставляется с множеством действий и Visual Studio 2010 содержит шаблон для определения действий. Двумя наиболее распространенными действиями, используемыми для корневого потока управления, являются последовательности и блок-схемы. Хотя любое действие может выполняться как рабочий процесс, эти два предоставляют наиболее распространенные шаблоны проектирования для определения бизнес-логики. На рисунке 2 показан пример последовательной модели рабочего процесса, определяющей обработку полученного заказа, сохранения и отправки уведомлений другим службам.
рис. 2. Последовательное проектирование рабочих процессов
Тип рабочего процесса блок-схемы представлен в WF4 для решения распространенных запросов от существующих пользователей, таких как возможность вернуться к предыдущим шагам в рабочем процессе и потому что он более тесно похож на концептуальную структуру, выполненную аналитиками и разработчиками, работающими над определением бизнес-логики. Например, рассмотрим сценарий, в котором участвуют входные данные пользователя в приложение. В ответ на данные, предоставленные пользователем, программа должна продолжить процесс или вернуться к предыдущему шагу, чтобы снова запрашивать входные данные. При последовательном рабочем процессе это приведет к тому, что похоже на то, что показано на рис. 3, где действие DoTime используется для продолжения обработки до тех пор, пока не будет выполнено некоторое условие.
рис. 3. Последовательное выполнение ветвления решений
Дизайн на рис. 3 работает, но как разработчик или аналитик, глядя на рабочий процесс, модель не представляет исходную логику или требования, которые были описаны. Рабочий процесс блок-схемы на рис. 4 дает аналогичный технический результат последовательной модели, используемой на рис. 3, но проектирование рабочего процесса более тесно соответствует мышлению и требованиям, как первоначально определено.
рис. 4. Рабочий процесс блок-схемы
Поток данных в рабочих процессах
Первое мнение, что большинство людей имеют, когда они думают о рабочем процессе является бизнес-процессом или потоком приложения. Тем не менее, так же важно, как поток, — это данные, которые управляет процессом и сведениями, собранными и хранящимися во время выполнения рабочего процесса. В WF4 хранилище и управление данными является основной областью разработки.
Существует три основных понятия, которые необходимо понять в отношении данных: переменные, аргументы и выражения. Простые определения для каждой из них — это переменные для хранения данных, аргументы предназначены для передачи данных, а выражения — для управления данными.
Переменные — хранение данных
Переменные в рабочих процессах очень похожи на переменные, используемые в императивных языках: они описывают именованное расположение для хранения данных, и они следуют определенным правилам области. Чтобы создать переменную, сначала определите, какая область области должна быть доступна. Так же, как и переменные в коде, доступные на уровне класса или метода, переменные рабочего процесса могут быть определены в разных областях. Рассмотрим рабочий процесс на рис. 5. В этом примере можно определить переменную на корневом уровне рабочего процесса или в области, определенной действием последовательности данных канала сбора данных.
рис. 5. Переменные, области действия
Аргументы — передача данных
Аргументы определяются для действий и определяют поток данных в действие и из него. Аргументы можно рассматривать так же, как и аргументы для методов в императивном коде. Аргументы могут быть in, Out или In/Out, а также иметь имя и тип. При создании рабочего процесса можно определить аргументы в корневом действии, которое позволяет передавать данные в рабочий процесс при вызове. Аргументы рабочего процесса определяются так же, как и переменные с помощью окна аргументов.
При добавлении действий в рабочий процесс необходимо настроить аргументы для действий, и это в первую очередь делается путем ссылки на переменные в области или с помощью выражений, которые я обсудим далее. Фактически базовый класс Argument содержит свойство Expression, которое является действием, возвращающим значение типа аргумента, поэтому все эти параметры связаны и полагаются на действие.
При использовании конструктора рабочих процессов для редактирования аргументов можно вводить выражения, представляющие литеральные значения в сетке свойств, или использовать имена переменных для ссылки на переменную в области, как показано на рисунке 6, где emailResult и emailAddress определяются в рабочем процессе.
рис. 6. Настройка аргументов для действий
Выражения — действия с данными
Выражения — это действия, которые можно использовать в рабочем процессе для работы с данными. Выражения можно использовать в тех местах, где используется действие и заинтересованы в возвращаемом значении, что означает, что можно использовать выражения в аргументах параметров или определять условия действий, таких как действия While или If. Помните, что большинство вещей в WF4 производны от действия и выражений не отличаются, они являются производными от действия<TResult> означает, что они возвращают значение определенного типа. Кроме того, WF4 включает несколько распространенных выражений для ссылки на переменные и аргументы, а также выражения Visual Basic. Из-за этого уровня специализированных выражений выражения, используемые для определения аргументов, могут включать код, литеральные значения и ссылки на переменные. В таблице 1 представлена небольшая выборка типов выражений, которые можно использовать при определении аргументов с помощью конструктора рабочих процессов. При создании выражений в коде существует несколько дополнительных вариантов, включая использование лямбда-выражений.
Выражение | Тип выражения |
---|---|
"hello world" |
Строковое значение литерала |
10 |
Значение Литерала Int32 |
System.String.Concat("hello", ", "world") |
Вызов императивного метода |
"hello" & "world" |
Выражение Visual Basic |
argInputString |
Ссылка на аргумент (имя аргумента) |
varResult |
Ссылка на переменную (имя переменной) |
"Hello: " & argInputString |
Литералы и аргументы и переменные смешанные |
таблица 1. Примеры выражений
Создание первого рабочего процесса
Теперь, когда я рассмотрел основные понятия о действии и потоке данных, я могу создать рабочий процесс с помощью этих понятий. Начнем с простого рабочего процесса hello world, чтобы сосредоточиться на концепциях, а не на истинном предложении ценности WF. Чтобы начать, создайте проект модульного теста в Visual Studio 2010. Чтобы использовать ядро WF, добавьте ссылку на сборку System.Activities и добавьте инструкции using для System.Activities, System.Activities.Statements и System.IO в файле тестового класса. Затем добавьте метод теста, как показано на рис. 7, чтобы создать базовый рабочий процесс и выполнить его.
[TestMethod]
public void TestHelloWorldStatic()
{
Модуль записи StringWriter = new StringWriter();
Console.SetOut(writer);
Последовательность wf = новая последовательность
{
Действия = {
new WriteLine {Text = "Hello"},
new WriteLine {Text = "World"}
}
};
WorkflowInvoker.Invoke(wf);
Assert.IsTrue(String.Compare(
"Hello\r\nWorld\r\n",
писатель. GetStringBuilder(). ToString()) == 0,
"Неправильно написанная строка");
}
рис. 7. Создание мира hello в коде
Свойство Text в действии WriteLine — это строка<InArgument,>, и в этом примере я передал литеральное значение этому свойству.
Следующим шагом является обновление этого рабочего процесса для использования переменных и передачи этих переменных аргументам действия. На рисунке 8 показан новый тест, обновленный для использования переменных.
[TestMethod]
public void TestHelloWorldVariables()
{
Модуль записи StringWriter = new StringWriter();
Console.SetOut(writer);
Последовательность wf = новая последовательность
{
Переменные = {
new Variable<string>{Default = "Hello", Name = "hello"},
new Variable<string> { Default = "Bill", Name = "name" } },
Действия = {
new WriteLine { Text = new VisualBasicValue<string>("приветствие"),
new WriteLine { Text = new VisualBasicValue<string>(
"name + \"Гейтс\"")}
}
};
WorkflowInvoker.Invoke(wf);
}
рис. 8. Рабочий процесс кода с переменными
В этом случае переменные определяются как переменные типа<строковые> и задают значение по умолчанию. Переменные объявляются в действии последовательности, а затем ссылаются из аргумента Text двух действий. Кроме того, можно использовать выражения для инициализации переменных, как показано на рисунке 9, где класс TRes> ult<TResult используется для передачи строки, представляющей выражение. В первом случае выражение ссылается на имя переменной, а во втором случае переменная объединяется с литеральным значением. Синтаксис, используемый в текстовых выражениях, — Это Visual Basic, даже при написании кода в C#.
Действия = {
new WriteLine { Text = new VisualBasicValue<string>("приветствие"),
TextWriter = writer },
new WriteLine { Text = new VisualBasicValue<string>("name + \"Гейтс\""),
TextWriter = запись }
}
рис. 9. Использование выражений для определения аргументов
Хотя примеры кода помогают проиллюстрировать важные моменты и обычно чувствуют себя комфортно разработчикам, большинство пользователей будут использовать конструктор для создания рабочих процессов. В конструкторе вы перетаскиваете действия в область конструктора и используете обновленную, но знакомую сетку свойств, чтобы задать аргументы. Кроме того, конструктор рабочих процессов содержит развернутые области внизу, чтобы изменить аргументы для рабочего процесса и переменных. Чтобы создать аналогичный рабочий процесс в конструкторе, добавьте новый проект в решение, выбрав шаблон библиотеки действий, а затем добавьте новое действие.
При первом отображении конструктора рабочих процессов он не содержит никаких действий. Первым шагом в определении действия является выбор корневого действия. В этом примере добавьте действие блок-схемы в конструктор, перетащив его из категории блок-схемы на панели элементов. В конструкторе блок-схем перетащите два действия WriteLine из панели элементов и добавьте их ниже другой в блок-схему. Теперь необходимо подключить действия вместе, чтобы блок-схема знала путь к следующему. Для этого сначала наведите указатель мыши на зеленый круг "start" в верхней части блок-схемы, чтобы увидеть маркеры захвата, а затем щелкните и перетащите его на первую линию записи и перетащите ее на маркер перетаскивания, который отображается в верхней части действия. Выполните то же самое, чтобы подключить первую writeLine к второй Строке записи. Поверхность конструктора должна выглядеть как рис. 10.
рис. 10. Макет блок-схемы Hello
После создания структуры необходимо настроить некоторые переменные. Щелкнув область конструктора и нажав кнопку "Переменные" в нижней части конструктора, можно изменить коллекцию переменных для блок-схемы. Добавьте две переменные с именем "приветствие" и "имя" в список и задайте значения по умолчанию "Hello" и "Bill" соответственно, обязательно включите кавычки при настройке значений, так как это выражение, чтобы строковые строки должны быть кавычек. На рисунке 11 показано окно переменных, настроенное с двумя переменными и значениями по умолчанию.
рис. 11. Переменные, определенные в конструкторе рабочих процессов
Чтобы использовать эти переменные в действиях WriteLine, введите "приветствие" (без кавычки) в сетке свойств для аргумента Text первого действия и "name" (снова без кавычки) для аргумента Text во второй Записи. Во время выполнения при вычислении аргументов Text значение переменной будет разрешено и используется действием WriteLine.
В тестовом проекте добавьте ссылку на проект, содержащий рабочий процесс. Затем можно добавить метод теста, как показано на рис. 12, чтобы вызвать этот рабочий процесс и проверить выходные данные. На рис. 12 можно увидеть, что рабочий процесс создается путем создания экземпляра созданного класса. В этом случае рабочий процесс был определен в конструкторе и скомпилирован в класс, производный от действия, который затем можно вызвать.
[TestMethod]
public void TestHelloFlowChart()
{
StringWriter tWriter = new StringWriter();
Console.SetOut(tWriter);
Workflows.HelloFlow wf = новые рабочие процессы.HelloFlow();
WorkflowInvoker.Invoke(wf);
Утверждения опущены
}
рис. 12. Тестирование блок-схемы
До сих пор я использовал переменные в рабочем процессе и использовал их для предоставления значений аргументам в действиях WriteLine. Рабочий процесс также может иметь аргументы, которые позволяют передавать данные в рабочий процесс при вызове и получать выходные данные при завершении рабочего процесса. Чтобы обновить блок-схему из предыдущего примера, удалите переменную "name" (выберите ее и нажмите клавишу DELETE) и создайте "имя" строки типа. Это происходит так же, за исключением кнопки "Аргументы" для просмотра редактора аргументов. Обратите внимание, что аргументы также могут иметь направление, и вам не нужно указать значение по умолчанию, так как значение будет передано в действие во время выполнения. Так как для переменной используется то же имя аргумента, что и для переменной, аргументы Text действий WriteLine остаются допустимыми. Теперь во время выполнения эти аргументы будут оценивать и разрешать значение аргумента name в рабочем процессе и использовать это значение. Добавьте дополнительный аргумент строки типа с направлением Out и именем fullGreeting; это возвращается в вызывающий код.
рис. 13. Определение аргументов для рабочего процесса
В блок-схеме добавьте действие Assign и подключите его к последнему действию WriteLine. Для аргумента To введите "fullGreeting" (без кавычки) и введите "приветствие & имя" (без кавычки). При этом будет назначено объединение переменной приветствия с аргументом имени для аргумента вывода fullGreeting.
Теперь в модульном тесте обновите код, чтобы указать аргумент при вызове рабочего процесса. Аргументы передаются в рабочий процесс в виде словаря<строки, объект> где ключ — имя аргумента. Это можно сделать, просто изменив вызов для вызова рабочего процесса, как показано на рис. 14. Обратите внимание, что выходные аргументы также содержатся в строке словаря<, объект> коллекцию, ключом к имени аргумента.
Строка IDictionary<, объект> результатов = WorkflowInvoker.Invoke(wf,
new Dictionary<string,object> {
{"name", "Bill" } }
);
string outValue = results["fullGreeting"]. ToString();
рис. 14. Передача аргументов в рабочий процесс
Теперь, когда вы ознакомились с основами объединения действий, переменных и аргументов, я поставлю вас по обзору действий, включенных в платформу, чтобы обеспечить более интересные рабочие процессы, ориентированные на бизнес-логику, а не на концепции низкого уровня.
Обзор палитры действий рабочего процесса
При использовании любого языка программирования предполагается наличие основных конструкций для определения логики приложения. При использовании платформы разработки более высокого уровня, например WF, это ожидание не изменяется. Так же, как и инструкции на языках .NET, таких как If/Else, Switch и While, для управления потоком управления также требуются те же возможности при определении логики в декларативном рабочем процессе. Эти возможности бывают в виде базовой библиотеки действий, которая поставляется с платформой. В этом разделе мы предоставим вам краткий обзор действий, которые поставляются с платформой, чтобы дать вам представление о функциональных возможностях, предоставляемых из коробки.
Примитивы действий и действия коллекции
При переходе к декларативной модели программирования легко начать задаваться вопросом, как выполнять распространенные задачи обработки объектов, которые являются второй природой при написании кода. Для таких задач, где вы находитесь в работе с объектами и требуется задать свойства, вызвать команды или управлять коллекцией элементов, существует набор действий, разработанных специально с учетом этих задач.
Активность | Описание |
---|---|
Назначать |
Назначает значение расположению — включение переменных. |
Задержка |
Задерживает путь выполнения в течение указанного периода времени. |
InvokeMethod |
Вызывает метод для объекта .NET или статического метода в типе .NET, при необходимости с возвращаемым типом T. |
WriteLine |
Записывает указанный текст в средство записи текста по умолчанию в Console.Out |
AddToCollection<T> |
Добавляет элемент в типизированной коллекции. |
RemoveFromCollection<T> |
Удаляет элемент из типизированной коллекции. |
ClearCollection<T> |
Удаляет все элементы из коллекции. |
СуществуетInCollection<T> |
Возвращает логическое значение, указывающее, существует ли указанный элемент в коллекции. |
Действия потока управления
При определении бизнес-логики или бизнес-процессов контроль над потоком выполнения имеет решающее значение. Действия потока управления включают основные принципы, такие как Последовательность, которая предоставляет общий контейнер, если необходимо выполнить шаги по порядку, и общую логику ветвления, например действия If и Switch. Действия потока управления также включают логику циклического цикла на основе данных (ForEach) и условий(в то время как). Наиболее важными для упрощения сложного программирования являются параллельные действия, которые позволяют одновременно выполнять несколько асинхронных действий.
Активность | Описание |
---|---|
Последовательность |
Для выполнения действий в рядах |
While/DoTime |
Выполняет дочернее действие, пока условие (выражение) имеет значение true |
ForEach<T> |
Выполняет итерацию по перечисленной коллекции и выполняет дочернее действие один раз для каждого элемента коллекции, ожидая завершения дочернего элемента перед началом следующей итерации. Предоставляет типизированный доступ к отдельному элементу, управляющего итерацией в виде именованного аргумента. |
Если |
Выполняет одно из двух дочерних действий в зависимости от результата условия (выражения). |
Переключение<T> |
Вычисляет выражение и планирует дочернее действие с соответствующим ключом. |
Параллельный |
Планирует все дочерние действия одновременно, но также предоставляет условие завершения, чтобы разрешить действие отменять все невыполненные дочерние действия при соблюдении определенных условий. |
ParallelForEach<T> |
Выполняет итерацию по перечисленной коллекции и выполняет дочернее действие один раз для каждого элемента в коллекции, а также выполняет планирование всех экземпляров одновременно. Как и forEach<T>, это действие обеспечивает доступ к текущему элементу данных в виде именованного аргумента. |
Выбирать |
Планирует все дочерние действия PickBranch и отменяет все, кроме первого, чтобы его триггер был завершен. Действие PickBranch имеет триггер и действие; каждое — это действие. После завершения действия триггера выбор отменяет все остальные дочерние действия. |
В двух примерах ниже показано несколько этих действий, используемых для иллюстрации создания этих действий вместе. Первый пример, рис. 15, включает использование ParallelForEach<T> для использования списка URL-адресов и асинхронного получения RSS-канала по указанному адресу. После возврата веб-канала<T> forEach используется для итерации элементов веб-канала и их обработки.
Обратите внимание, что код объявляет и определяет экземпляр VisualBasicSettings со ссылками на типы System.ServiceModel.Syndication. Затем этот объект параметров используется при объявлении VisualBasicValue<экземпляров T>, ссылающихся на типы переменных из этого пространства имен, чтобы включить разрешение типов для этих выражений. Кроме того, следует отметить, что текст действия ParallelForEach принимает ActivityAction, упомянутый в разделе о создании пользовательских действий. Эти действия используют ДелегатInArgument и ДелегатOutArgument так же, как действия используют InArgument и OutArgument.
рис. 15. Действия потока управления
Второй пример, рис. 16, — это распространенный шаблон ожидания ответа с временем ожидания. Например, ожидая утверждения запроса менеджером и отправки напоминания, если ответ не прибыл в выделенное время. Действие DoTime вызывает повторение ожидания ответа, пока действие выбора используется для выполнения действия ManagerResponse и действия задержки одновременно с триггерами. Когда задержка завершится сначала, напоминание отправляется и когда действие ManagerResponse завершается первым, флаг устанавливается для прерывания цикла DoTime.
рис. 16. Выбор и назначение действий
В примере на рис. 16 также показано, как закладки могут использоваться в рабочих процессах. Закладка создается действием, чтобы пометить место в рабочем процессе, чтобы обработка может возобновиться с этого момента позже. Действие "Задержка" имеет закладку, которая возобновляется после определенного периода времени. Действие ManagerResponse — это настраиваемое действие, которое ожидает ввода и возобновляет рабочий процесс после поступления данных. Действия обмена сообщениями, рассмотренные вскоре, являются основными действиями для выполнения закладок. Если рабочий процесс не активно обрабатывает работу, когда он ожидает возобновления закладок, он считается неактивным и может быть сохранен в устойчивом хранилище. Закладки подробно рассматриваются в разделе о создании пользовательских действий.
Миграция
Для разработчиков, использующих WF3, действие взаимодействия может играть важную роль в повторном использовании существующих ресурсов. Действие, найденное в сборке System.Workflow.Runtime, упаковывает существующий тип действия и отображает свойства действия в качестве аргументов в модели WF4. Так как свойства являются аргументами, можно использовать выражения для определения значений. На рисунке 17 показана конфигурация действия взаимодействия для вызова действия WF3. Входные аргументы определяются со ссылками на переменные в области в определении рабочего процесса. Действия, встроенные в WF3, имели свойства вместо аргументов, поэтому каждое свойство получает соответствующий входной и выходной аргумент, что позволяет различать данные, отправляемые в действие, и данные, которые вы ожидаете получить после выполнения действия. В новом проекте WF4 вы не найдете это действие на панели элементов, так как целевая платформа имеет значение .NET Framework 4 Client Profile. Измените целевую платформу в свойствах проекта на .NET Framework 4, а действие появится на панели элементов.
рис. 17. Конфигурация действий взаимодействия
Блок-схема
При проектировании рабочих процессов блок-схемы существует несколько конструкций, которые можно использовать для управления потоком выполнения в блок-схеме. Эти конструкции сами предоставляют простые шаги, простые точки принятия решений на основе одного условия или инструкции switch. Реальная сила блок-схемы — это возможность подключения этих типов узлов к требуемому потоку.
Конструктор или действие | Описание |
---|---|
Блок-схема |
Контейнер для ряда шагов потока каждый шаг потока может быть любым действием или одной из следующих конструкций, но его необходимо подключить в блок-схеме. |
FlowDecision |
Предоставляет логику ветвления на основе условия. |
FlowSwitch<T> |
Включает несколько ветвей на основе значения выражения. |
FlowStep |
Представляет шаг в блок-схеме с возможностью подключения к другим шагам. Этот тип не отображается на панели элементов, так как он неявно добавляется конструктором. Это действие упаковывает другие действия в рабочий процесс и предоставляет семантику навигации для следующих шагов в рабочем процессе. |
Важно отметить, что при наличии определенных действий для модели блок-схемы любые другие действия можно использовать в рабочем процессе. Аналогичным образом, действие блок-схемы можно добавить в другое действие, чтобы обеспечить семантику выполнения и проектирования блок-схемы в рамках этого рабочего процесса. Это означает, что у вас может быть последовательность с несколькими действиями и блок-схемой прямо в середине.
Действия обмена сообщениями
Одним из основных направлений в WF4 является более тесная интеграция между WF и WCF. С точки зрения рабочих процессов это означает действия для моделирования операций обмена сообщениями, таких как отправка и получение сообщений. На самом деле существует несколько различных действий, включенных в платформу для обмена сообщениями, каждый из которых имеет несколько различных функций и целей.
Активность | Описание |
---|---|
Отправка и получение |
Один из способов обмена сообщениями для отправки или получения сообщения. Эти же действия состоят в взаимодействии стилей запросов и ответов. |
ReceiveAndSendReply |
Моделирует операцию службы, которая получает сообщение и отправляет ответ обратно. |
SendAndReceiveReply |
Вызывает операцию службы и получает ответ. |
ИнициализацияCorrelation |
Разрешить инициализацию значений корреляции явным образом в рамках логики рабочего процесса, а не извлекать значения из сообщения. |
CorrelationScope |
Определяет область выполнения, в которой дескриптор корреляции доступен для получения и отправки действий, упрощающих настройку дескриптора, совместно используемого несколькими действиями обмена сообщениями. |
TransactedReceiveScope |
Позволяет включить логику рабочего процесса в ту же транзакцию, которая выполняется в операцию WCF с помощью действия получения. |
Чтобы вызвать операцию службы из рабочего процесса, выполните знакомые действия по добавлению ссылки на службу в проект рабочего процесса. Затем система проекта в Visual Studio будет использовать метаданные из службы и создать настраиваемое действие для каждой операции службы, найденной в контракте. Это можно представить как эквивалент рабочего процесса прокси-сервера WCF, который будет создан в проекте C# или Visual Basic. Например, принимая существующую службу, которая ищет резервирование отеля с контрактом на обслуживание, показанным на рис. 18; Добавление ссылки на службу к ней дает настраиваемое действие, показанное на рис. 19.
[ServiceContract]
общедоступный интерфейс IHotelService
{
[OperationContract]
Список<HotelSearchResult> SearchHotels(
HotelSearchRequest requestDetails);
}
рис. 18. Контракт службы
рис. 19. Настраиваемое действие WCF
Дополнительные сведения о создании рабочих процессов, предоставляемых как службы WCF, см. в разделе "Службы рабочих процессов" далее в этом документе.
Транзакции и обработка ошибок
Написание надежных систем может быть сложной задачей и требует, чтобы вы могли работать с ошибками и управлять ими, чтобы обеспечить согласованное состояние в приложении. Для рабочего процесса области для управления обработкой исключений и транзакций моделиируются с помощью действий со свойствами типа ActivityAction или Activity, чтобы позволить разработчикам моделировать логику обработки ошибок. Помимо этих распространенных шаблонов согласованности, WF4 также включает действия, которые позволяют моделировать длинную распределенную координацию с помощью компенсации и подтверждения.
Активность | Описание |
---|---|
ОтменаScope |
Используется, чтобы разрешить разработчику рабочего процесса реагировать на отмену текста работы. |
TransactionScope |
Включает семантику, аналогичную использованию области транзакций в коде, выполняя все дочерние действия в области в рамках транзакции. |
TryCatch/Catch<T> |
Используется для моделирования исключений и перехвата типизированных исключений. |
Бросать |
Можно использовать для создания исключения из действия. |
Повторное увеличение |
Используется для повторного создания исключения, как правило, обнаруженного с помощью действия TryCatch. |
CompensableActivity |
Определяет логику выполнения дочерних действий, которые могут потребоваться, чтобы их действия компенсировались после успешного выполнения. Предоставляет заполнитель для логики компенсации, логики подтверждения и обработки отмены. |
Компенсировать |
Вызывает логику обработки компенсации для компенсируемого действия. |
Подтверждать |
Вызывает логику подтверждения для компенируемого действия. |
Действие TryCatch предоставляет знакомый способ области работы для перехвата любых исключений, которые могут возникнуть, а действие Catch<T> предоставляет контейнер для логики обработки исключений. Пример использования этого действия на рис. 20. Каждый типизированный блок исключений определяется действием Catch<T>, предоставляя доступ к исключению с помощью именованного аргумента, показанного слева от каждого перехвата. Если возникает необходимость, действие rethrow можно использовать для повторного создания перехвата исключения или действия "Создать", чтобы создать новое исключение собственного.
рис. 20. Действие TryCatch
Транзакции помогают обеспечить согласованность в кратковременной работе, а действие TransactionScope обеспечивает тот же вид области, который можно получить в коде .NET с помощью класса TransactionScope.
Наконец, когда необходимо поддерживать согласованность, но не удается использовать атомарную транзакцию в ресурсах, можно использовать компенсацию. Компенсация позволяет определить набор работы, который, если он завершен, может иметь набор действий для компенсации внесенных изменений. В качестве простого примера рассмотрим действие compensable на рис. 21, где отправляется сообщение электронной почты. Если после завершения действия возникает исключение, логика компенсации может вызываться для возврата системы в согласованное состояние; В этом случае необходимо отозвать предыдущее сообщение по электронной почте.
рис. 21.
Создание и выполнение рабочих процессов
Как и в любом языке программирования, существует два основных способа работы с рабочими процессами: определение и их выполнение. В обоих случаях WF предоставляет несколько вариантов для обеспечения гибкости и управления.
Параметры проектирования рабочих процессов
При проектировании или определении рабочих процессов существует два основных варианта: код или XAML. XAML предоставляет действительно декларативный интерфейс и позволяет определить весь рабочий процесс в разметке XML, ссылаясь на действия и типы, созданные с помощью .NET. Большинство разработчиков, скорее всего, будут использовать конструктор рабочих процессов для создания рабочих процессов, что приведет к декларативному определению рабочего процесса XAML. Так как XAML — это просто XML, однако любое средство можно использовать для его создания, что является одной из причин, по которой это такая мощная модель для создания приложений. Например, XAML, показанный на рис. 22, был создан в простом текстовом редакторе и может быть компилирован или использоваться непосредственно для выполнения экземпляра определенного рабочего процесса.
<p:Activity x:Class="Workflows.HelloSeq" xmlns="https://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:p="https://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<x:Members>
<x:Property Name="greeting" Type="p:InArgument(x:String)" />
<x:Property Name="name" Type="p:InArgument(x:String)" />
</x:Members>
<p:Sequence>
<p:WriteLine>[приветствие & "from workflow"]</p:WriteLine>
<p:WriteLine>[имя]</p:WriteLine>
</p:Sequence>
</p:Activity>
рис. 22. Рабочий процесс, определенный в XAML
Этот же XAML создается конструктором и может создаваться пользовательскими средствами, что упрощает управление. Кроме того, можно также создать рабочие процессы с помощью кода, как показано ранее. Это включает создание иерархии действий с помощью различных действий в платформе и пользовательских действий. Вы видели несколько примеров уже рабочих процессов, определенных полностью в коде. В отличие от WF3, теперь можно создать рабочий процесс в коде и легко сериализовать его в XAML; обеспечивает большую гибкость в моделировании определений рабочих процессов и управлении ими.
Как показано, для выполнения рабочего процесса требуется действие, и это может быть экземпляр, встроенный в код или созданный из XAML. Конечным результатом каждого из методов моделирования является класс, производный от действия, или XML-представление, которое можно десериализировать или скомпилировать в действие.
Параметры выполнения рабочих процессов
Чтобы выполнить рабочий процесс, необходимо действие, определяющее рабочий процесс. Существует два типичных способа получения действия, которые можно выполнить: создать его в коде или прочитать в XAML-файле и десериализировать содержимое в действие. Первый вариант прост, и я уже показал несколько примеров. Чтобы загрузить XAML-файл, следует использовать класс ActivityXamlServices, который предоставляет статический метод Load. Просто передайте объект Stream или XamlReader и верните действие, представленное в XAML.
После того как у вас есть действие, самый простой способ его выполнения заключается в использовании класса WorkflowInvoker, как и в модульных тестах ранее. Метод Invoke этого класса имеет параметр типа Activity и возвращает строку IDictionary<, объект>. Если необходимо передать аргументы в рабочий процесс, сначала определите их в рабочем процессе, а затем передайте значения вместе с действием, в метод Invoke в виде словаря пар имен и значений. Аналогичным образом, все аргументы Out или In/Out, определенные в рабочем процессе, будут возвращены в результате выполнения метода. На рисунке 23 представлен пример загрузки рабочего процесса из XAML-файла, передачи аргументов в рабочий процесс и получения результирующего выходного аргумента.
MathWF действия;
using (Stream mathXaml = File.OpenRead("Math.xaml"))
{
mathWF = ActivityXamlServices.Load(mathXaml);
}
var outputs = WorkflowInvoker.Invoke(mathWF,
new Dictionary<string, object> {
{ "operand1", 5 },
{ "операнд2", 10 },
{ "operation", "add" } });
Assert.AreEqual<int>(15, (int)outputs["result"], "Неправильный результат возвращен");
рис. 23. Вызов рабочего процесса "Свободный XAML" с аргументами
Обратите внимание, что действие загружается из XAML-файла и по-прежнему может принимать и возвращать аргументы. Рабочий процесс был разработан с помощью конструктора в Visual Studio для простоты, но может быть разработан в пользовательском конструкторе и храниться в любом месте. XAML можно загрузить из базы данных, библиотеки SharePoint или другого хранилища перед передачей среде выполнения для выполнения.
Использование WorkflowInvoker предоставляет самый простой механизм для выполнения коротких рабочих процессов. По сути, рабочий процесс действует как вызов метода в приложении, что позволяет более легко воспользоваться всеми преимуществами WF без необходимости выполнять большую работу для размещения WF. Это особенно полезно при модульном тестировании действий и рабочих процессов, так как это уменьшает настройку теста, необходимую для выполнения компонента при тестировании.
Другой распространенный класс размещения — WorkflowApplication, который предоставляет безопасный дескриптор рабочего процесса, выполняемого в среде выполнения, и позволяет легко управлять длительными рабочими процессами. С помощью WorkflowApplication можно по-прежнему передавать аргументы в рабочий процесс так же, как и в WorkflowInvoker, но метод Run используется для фактического запуска рабочего процесса. На этом этапе рабочий процесс начинает выполняться в другом потоке и элементе управления возвращается в вызывающий код.
Так как рабочий процесс выполняется асинхронно, в коде размещения, скорее всего, нужно знать, когда рабочий процесс завершится, или если он создает исключение и т. д. Для этих типов уведомлений класс WorkflowApplication имеет набор свойств типа Action<T>, которые можно использовать как события для добавления кода для реагирования на определенные условия выполнения рабочего процесса, включая прерывание, необработанное исключение, завершенное, бездействующее и выгрузленное. При выполнении рабочего процесса с помощью WorkflowApplication можно использовать код, аналогичный приведенному на рис. 24, используя действия для обработки событий.
WorkflowApplication wf = new WorkflowApplication(new Flowchart1());
wf. Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
Console.WriteLine("Workflow {0} complete", e.InstanceId);
};
wf. Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
{
Console.WriteLine(e.Reason);
};
wf. OnUnhandledException =
делегат(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine(e.UnhandledException.ToString());
return UnhandledExceptionAction.Terminate;
};
wf. Run();
рис. 24. Действия WorkflowApplication
В этом примере цель кода узла , простого консольного приложения, заключается в том, чтобы уведомить пользователя через консоль после завершения рабочего процесса или при возникновении ошибки. В реальной системе узел будет заинтересован в этих событиях и, скорее всего, будет реагировать на них по-разному, чтобы предоставить администраторам информацию о сбоях или лучше управлять экземплярами.
Расширения рабочих процессов
Одним из основных функций WF, так как WF3 является то, что оно достаточно упрощено для размещения в любом домене приложения .NET. Так как среда выполнения может выполняться в разных доменах и может потребоваться настраиваемая семантика выполнения, различные аспекты поведения среды выполнения должны быть внешние из среды выполнения. Именно здесь вступают в игру расширения рабочих процессов. Расширения рабочих процессов позволяют разработчику писать код узла, если это так, чтобы добавить поведение в среду выполнения, расширив его с помощью пользовательского кода.
Два типа расширений, о которых известно среде выполнения WF, являются расширениями сохраняемости и отслеживания. Расширение сохраняемости предоставляет основные функции для сохранения состояния рабочего процесса в устойчивом хранилище и получения этого состояния при необходимости. Доставка расширений сохраняемости с платформой включает поддержку Microsoft SQL Server, но расширения можно записать для поддержки других систем баз данных или устойчивых хранилищ.
Упорство
Чтобы использовать расширение сохраняемости, необходимо сначала настроить базу данных для хранения состояния, которая может быть выполнена с помощью скриптов SQL, найденных в %windir%\Microsoft.NET\Framework\v4.0.30319\sql\<языка> (например, c:\windows\microsoft.net\framework\v4.0.30319\sql\en\). После создания базы данных выполните два сценария, чтобы создать структуру (SqlWorkflowInstanceStoreSchema.sql) и хранимые процедуры (SqlWorkflowInstanceStoreLogic.sql) в базе данных.
После настройки базы данных используйте SqlWorkflowInstanceStore вместе с классом WorkflowApplication. Сначала создайте хранилище и настройте его, а затем предоставьте его в WorkflowApplication, как показано на рис. 25.
Приложение WorkflowApplication = новое действие WorkflowApplication(activity);
InstanceStore instanceStore = new SqlWorkflowInstanceStore(
@"Data Source=.\\SQLEXPRESS;Integrated Security=True");
Представление InstanceView = instanceStore.Execute()
instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(),
TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view. InstanceOwner;
приложение. InstanceStore = instanceStore;
рис. 25. Добавление поставщика сохраняемости SQL
Существует два способа сохранения рабочего процесса. Первое — это прямое использование действия "Сохранение" в рабочем процессе. При выполнении этого действия состояние рабочего процесса сохраняется в базе данных, сохраняя текущее состояние рабочего процесса. Это позволяет автору рабочего процесса контролировать, когда важно сохранить текущее состояние рабочего процесса. Второй вариант позволяет приложению размещения сохранять состояние рабочего процесса при возникновении различных событий в экземпляре рабочего процесса; скорее всего, когда рабочий процесс неактивен. Зарегистрируя действие PersistableIdle в WorkflowApplication, код узла может ответить на событие, чтобы указать, следует ли сохранить, выгрузить или выгрузить экземпляр. На рисунке 26 показана регистрация ведущего приложения для получения уведомлений об простое рабочего процесса и его сохранение.
wf. PersistableIdle = (waie) => PersistableIdleAction.Persist;
рис. 26. Выгрузка рабочего процесса при простое
После простоя и сохранения рабочего процесса среда выполнения WF может выгрузить его из памяти, освобождая ресурсы для других рабочих процессов, которым требуется обработка. Вы можете выгрузить экземпляр рабочего процесса, возвращая соответствующее перечисление из действия PersistableIdle. После выгрузки рабочего процесса в какой-то момент узел хочет загрузить его обратно из хранилища сохраняемости, чтобы возобновить его. Для этого необходимо загрузить экземпляр рабочего процесса с помощью хранилища экземпляра и идентификатора экземпляра. В свою очередь это приведет к запросу на загрузку состояния в хранилище экземпляров. После загрузки состояния можно использовать метод Run в WorkflowApplication, или закладку можно возобновить, как показано на рис. 27. Дополнительные сведения о закладках см. в разделе настраиваемых действий.
Приложение WorkflowApplication = новое действие WorkflowApplication(activity);
приложение. InstanceStore = instanceStore;
приложение. Load(id);
приложение. ResumeBookmark(readLineBookmark, input);
рис. 27. Возобновление сохраняемого рабочего процесса
Одним из изменений в WF4 является сохранение состояния рабочих процессов и использование новых методов управления данными аргументов и переменных. Вместо сериализации всего дерева действий и поддержания состояния в течение всего времени существования рабочего процесса сохраняются только в переменных области и значений аргументов, а также некоторых данных среды выполнения, таких как сведения о закладках. Обратите внимание на рис. 27, что при перезагрузке сохраненного экземпляра помимо использования хранилища экземпляров также передается действие, определяющее рабочий процесс. По сути, состояние из базы данных применяется к предоставленному действию. Этот метод обеспечивает гораздо большую гибкость для управления версиями и приводит к повышению производительности, так как состояние имеет меньший объем памяти.
Слежение
Сохраняемость позволяет узлу поддерживать длительные процессы, балансировать нагрузку экземпляров между узлами и другими отказоустойчивыми поведениями. Однако после завершения экземпляра рабочего процесса состояние в базе данных часто удаляется, так как оно больше не полезно. В рабочей среде, имея сведения о том, что в настоящее время делает рабочий процесс, и то, что он сделал, является критически важным для управления рабочими процессами и получения сведений о бизнес-процессе. Возможность отслеживать то, что происходит в приложении, является одной из убедительных функций использования среды выполнения WF.
Отслеживание состоит из двух основных компонентов: отслеживания участников и профилей отслеживания. Профиль отслеживания определяет, какие события и данные нужно отслеживать во время выполнения. Профили могут включать три основных типа запросов:
- ActivityStateQuery — используется для выявления состояний действия (например, закрытых) и переменных или аргументов для извлечения данных
- WorkflowInstanceQuery — используется для идентификации событий рабочего процесса
- CustomTrackingQuery — используется для выявления явных вызовов для отслеживания данных, как правило, в пользовательских действиях
Например, на рисунке 28 показан созданный файл TrackingProfile, включающий ActivityStateQuery и WorkflowInstanceQuery. Обратите внимание, что запросы указывают как при сборе информации, так и о том, какие данные следует собирать. Для ActivityStateQuery я включил список переменных, которые должны извлекаться и добавляться в отслеживаемые данные.
Профиль TrackingProfile = новый Файл TrackingProfile
{
Name = SimpleProfile,
Запросы = {
new WorkflowInstanceQuery {
Состояния = { "*" }
},
new ActivityStateQuery {
ActivityName = "WriteLine",
States={ "*" },
Переменные = {"Text" }
}
}
};
рис. 28. Создание профиля отслеживания
Участник отслеживания — это расширение, которое можно добавить в среду выполнения и отвечает за обработку записей отслеживания по мере их создания. Базовый класс TrackingParticipant определяет свойство для предоставления объекта TrackingProfile и метода Track, обрабатывающего отслеживание. На рисунке 29 показан ограниченный пользователь отслеживания, который записывает данные в консоль. Чтобы использовать участника отслеживания, его необходимо инициализировать с помощью профиля отслеживания, а затем добавить в коллекцию расширений в экземпляре рабочего процесса.
public class ConsoleTrackingParticipant: TrackingParticipant
{
защищенное переопределение void Track(TrackingRecord record, TimeSpan timeout)
{
ActivityStateRecord aRecord = запись как ActivityStateRecord;
if (aRecord != null)
{
Console.WriteLine("{0} введенное состояние {1}",
aRecord.Activity.Name, aRecord.State);
foreach (var item in aRecord.Arguments)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Переменная:{0} имеет значение: {1}",
пункт. Ключ, элемент. Значение);
Console.ResetColor();
}
}
}
}
рис. 29. участника отслеживания консоли
WF поставляется с etwTrackingParticipant (ETW = Корпоративная трассировка для Windows), которую можно добавить в среду выполнения, чтобы обеспечить высокую производительность отслеживания данных. ETW — это система трассировки, которая является собственным компонентом в Windows и используется многими компонентами и службами в ОС, включая драйверы и другой код уровня ядра. Данные, записанные в ETW, можно использовать с помощью пользовательского кода или с помощью таких средств, как предстоящий Windows Server AppFabric. Для пользователей, перенесенных из WF3, это изменение будет изменено, так как в рамках платформы не будет выполняться отслеживание SQL. Однако Windows Server AppFabric будет отправляться с потребителями ETW, которые будут собирать данные ETW и хранить их в базе данных SQL. Аналогичным образом вы можете создать потребитель ETW и хранить данные в любом формате, который вы предпочитаете, или написать собственный участник отслеживания, в зависимости от того, что имеет больше смысла для вашей среды.
Создание настраиваемых действий
Хотя базовая библиотека действий включает в себя богатую палитру действий для взаимодействия со службами, объектами и коллекциями, она не предоставляет действия для взаимодействия с подсистемами, такими как базы данных, почтовые серверы или объекты и системы личного домена. Частью начала работы с WF4 будет определение основных действий, которые могут потребоваться при создании рабочих процессов. Отличное дело в том, что при создании основных единиц работы их можно объединить в более грубые зернистые действия, которые разработчики могут использовать в своих рабочих процессах.
Иерархия классов действий
Хотя действие является действием, это не так, что все действия имеют одинаковые требования или потребности. По этой причине вместо всех действий, производных непосредственно от действия, существует иерархия базовых классов действий, показанная на рис. 30, которую можно выбрать при создании пользовательского действия.
рис. 30. Иерархия классов действий
Для большинства пользовательских действий вы будете получать от AsyncCodeActivity, CodeActivity или NativeActivity (или одного из универсальных вариантов) или моделировать действие декларативно. На высоком уровне четыре базовых класса можно описать следующим образом:
- Действие — используется для моделирования действий путем создания других действий, обычно определенных с помощью XAML.
- CodeActivity — упрощенный базовый класс, когда необходимо написать код для выполнения работы.
- AsyncCodeActivity — используется при асинхронном выполнении некоторых действий.
- NativeActivity — если ваше действие требует доступа к внутренним компонентам среды выполнения, например для планирования других действий или создания закладок.
В следующих разделах я создаю несколько действий, чтобы узнать, как использовать каждый из этих базовых классов. Как правило, как вы думаете о том, какой базовый класс следует использовать, следует начать с базового класса Activity и узнать, можно ли создать действие с помощью декларативной логики, как показано в следующем разделе. Затем CodeActivity предоставляет упрощенную модель, если вы определяете, что необходимо написать код .NET для выполнения задачи и AsyncCodeActivity, если вы хотите, чтобы действие выполнялось асинхронно. Наконец, если вы пишете действия потока управления, такие как те, которые находятся в платформе (например, в то время как, switch, if), вам потребуется наследовать от базового класса NativeActivity, чтобы управлять дочерними действиями.
Создание действий с помощью конструктора действий
При создании проекта библиотеки действий или при добавлении нового элемента в проект WF и выборе шаблона действия получается XAML-файл с пустым элементом Activity. В конструкторе это представляет собой область конструктора, в которой можно создать тело действия. Чтобы приступить к работе с действием, которое будет иметь более одного шага, обычно это помогает перетащить действие Последовательности в качестве тела, а затем заполнить его фактической логикой действия, как тело представляет одно дочернее действие.
Вы можете думать о действии так же, как и метод компонента с аргументами. На самом действии можно определить аргументы, а также их направление, чтобы определить интерфейс действия. Переменные, которые вы хотите использовать в действии, должны быть определены в действиях, составляющих тело, например корневую последовательность, которую я упомянул ранее. Например, можно создать действие NotifyManager, состоящее из двух простых действий: GetManager и SendMail.
Сначала создайте проект ActivityLibrary в Visual Studio 2010 и переименуйте файл Activity1.xaml в NotifyManager.xaml. Затем перетащите действие Последовательности из панели элементов и добавьте его в конструктор. После установки последовательности его можно заполнить дочерними действиями, как показано на рис. 31. (Обратите внимание, что действия, используемые в этом примере, являются пользовательскими действиями в ссылаемом проекте и недоступны в платформе.)
рис. 31. Уведомление о действиях диспетчера
Это действие должно принимать аргументы для имени сотрудника и текста сообщения. Действие GetManager ищет руководителя сотрудника и предоставляет сообщение электронной почты в виде строки OutArgument<>. Наконец, действие SendMail отправляет сообщение руководителю. Следующим шагом является определение аргументов для действия путем расширения окна "Аргументы" в нижней части конструктора и ввода сведений для двух обязательных входных аргументов.
Чтобы создать эти элементы, необходимо передать входные аргументы, указанные в действии NotifyManager отдельным дочерним действиям. Для действия GetManager необходимо имя сотрудника, которое является аргументом для действия. Имя аргумента можно использовать в диалоговом окне свойств для аргумента EmployeeName в действии GetManager, как показано на рис. 31. Действие SendMail требует адреса электронной почты руководителя и сообщения. Для сообщения можно ввести имя аргумента, содержащего сообщение. Однако для адреса электронной почты необходимо передать аргумент из действия GetManager в аргумент для действия SendMail. Для этого вам нужна переменная.
После выделения действия последовательности можно использовать окно "Переменные" для определения переменной с именем mgrEmail. Теперь можно ввести это имя переменной как для аргумента ManagerEmail out в действии GetManager, так и аргумент To в действии SendMail. При выполнении действия GetManager выходные данные будут храниться в этой переменной и при выполнении действия SendMail будут считывать данные из этой переменной в качестве аргумента.
Описанный подход — это чисто декларативная модель для определения тела действия. В некоторых случаях может потребоваться указать текст действия в коде. Например, ваше действие может включать коллекцию свойств, которые, в свою очередь, управляют набором дочерних действий; Набор именованных значений, которые управляют созданием набора действий Assign, будет одним из вариантов использования кода. В таких случаях можно написать класс, производный от действия, и написать код в свойстве реализации, чтобы создать действие (и любые дочерние элементы), чтобы представить функциональные возможности действия. Конечный результат совпадает в обоих случаях: логика определяется путем создания других действий, только механизм определения тела отличается. На рисунке 32 показана та же активность NotifyManager, определяемая в коде.
public class NotifyManager: Activity
{
public InArgument<string> EmployeeName { get; set; }
public InArgument<string> Message { get; set; }
защищенное переопределение func<действие> реализации
{
Получить
{
return () =>
{
Строка<переменной> mgrEmail =
new Variable<string> { Name = "mgrEmail" };
Последовательности s = новая последовательность
{
Переменные = { mgrEmail },
Действия = {
new GetManager {
EmployeeName =
new VisualBasicValue<string>("EmployeeName"),
Result = mgrEmail,
},
new SendMail {
ToAddress = mgrEmail,
MailBody = новая строка VisualBasicValue<>("Сообщение"),
From = "test@contoso.com",
Тема = "Автоматизированная электронная почта"
}
}
};
возвращаемые значения;
};
}
set { base. Реализация = значение; }
}
}
рис. 32. Композиция действий с помощью кода
Обратите внимание, что базовый класс — Activity и свойство реализации — это действие Func<Activity> для возврата одного действия. Этот код очень похож на тот, который показан ранее при создании рабочих процессов, и это не должно быть удивительно, так как цель в обоих случаях заключается в создании действия. Кроме того, в аргументах подхода кода можно задать переменные или использовать выражения для подключения одного аргумента к другому, как показано для аргументов EmployeeName и Message, как они используются для двух дочерних действий.
Написание пользовательских классов действий
Если определить, что логика действия не может быть выполнена путем создания других действий, можно написать действие на основе кода. При написании действий в коде, производных от соответствующего класса, определите аргументы и переопределите метод Execute. Метод Execute вызывается средой выполнения, когда время выполнения действия выполняет свою работу.
Чтобы создать действие SendMail, используемое в предыдущем примере, сначала необходимо выбрать базовый тип. Хотя можно создать функциональные возможности действия SendMail с помощью базового класса Activity и создания TryCatch<T> и InvokeMethod, для большинства разработчиков будет более естественно выбирать между базовыми классами CodeActivity и NativeActivity и писать код для логики выполнения. Так как это действие не требуется создавать закладки или планировать другие действия, я могу получить производный от базового класса CodeActivity. Кроме того, поскольку это действие возвращает один выходной аргумент, он должен быть производным от CodeActivity<TResult>, который предоставляет<TResult><TResult. Первым шагом является определение нескольких входных аргументов для свойств электронной почты. Затем переопределите метод Execute, чтобы реализовать функциональные возможности действия. На рисунке 33 показан завершенный класс действий.
public class SendMail: CodeActivity<SmtpStatusCode>
{
public InArgument<string> To { get; set; }
public InArgument<string> From { get; set; }
public InArgument<string> Subject { get; set; }
public InArgument<string> Body { get; set; }
защищенное переопределение SmtpStatusCode Execute(
Контекст CodeActivityContext)
{
попытка
{
Клиент SmtpClient = новый smtpClient();
клиент. Send()
From.Get(context), ToAddress.Get(context),
Subject.Get(context), MailBody.Get(context));
}
catch (smtpException smtpEx)
{
возвращает smtpEx.StatusCode;
}
возврат smtpStatusCode.Ok;
}
}
рис. 33. Настраиваемое действие SendMail
Существует несколько вещей, которые следует заметить об использовании аргументов. Я объявил несколько стандартных свойств .NET с помощью типов InArgument<T>. Вот как действие на основе кода определяет входные и выходные аргументы. Однако в коде я не могу просто ссылаться на эти свойства, чтобы получить значение аргумента, необходимо использовать аргумент в качестве дескриптора для получения значения с помощью предоставленного ActivityContext; в этом случае CodeActivityContext. Метод Get класса аргументов используется для получения значения и метода Set для назначения значения аргументу.
После завершения действия метод Execute среда выполнения обнаруживает это и предполагает, что действие будет выполнено и закрывает его. Для асинхронной или длительной работы существуют способы изменения этого поведения, которые описаны в предстоящем разделе, но в этом случае после отправки сообщения электронной почты работа завершена.
Теперь рассмотрим действие с помощью базового класса NativeActivity для управления дочерними действиями. Я создаю действие Итератора, которое выполняет некоторые дочерние действия заданное количество раз. Во-первых, мне нужен аргумент для хранения количества итераций, свойства для выполнения действия и переменной для хранения текущего числа итераций. Метод Execute вызывает метод BeginIteration для запуска начальной итерации, а последующие итерации вызываются так же. Обратите внимание на рис. 34, что переменные обрабатываются так же, как и аргументы с помощью метода Get и Set.
public class Iterator : NativeActivity
{
public Activity Body { get; set; }
public InArgument<int> RequestedIterations { get; set; }
public Variable<int> CurrentIteration { get; set; }
public Iterator()
{
CurrentIteration = новая переменная<int> { Default = 0 };
}
защищенное переопределение void Execute(NativeActivityContext context)
{
BeginIteration(context);
}
private void BeginIteration(NativeActivityContext context)
{
if (RequestedIterations.Get(context) > CurrentIteration.Get(context))
{
контекст. ScheduleActivity(Body,
new CompletionCallback(ChildComplete),
new FaultCallback(ChildFaulted));
}
}
}
рис. 34. Итератор собственных действий
При внимательном просмотре метода BeginIteration вы заметите вызов метода ScheduleActivity. Это то, как действие может взаимодействовать со средой выполнения, чтобы запланировать другое действие для выполнения, и это связано с тем, что вам нужна эта функция, наследуемая от NativeActivity. Кроме того, при вызове метода ScheduleActivity в ActivityExecutionContext предоставляются два метода обратного вызова. Так как вы не знаете, какое действие будет использоваться в качестве основного текста или сколько времени потребуется для завершения, и поскольку WF — это сильно асинхронная среда программирования, обратные вызовы используются для уведомления о действиях при завершении дочернего действия, что позволяет писать код для реагирования.
Второй обратный вызов — это отказоустойчивый объект FaultCallback, который будет вызываться, если дочернее действие возникает, чтобы вызвать исключение. В этом обратном вызове вы получите исключение и сможете использовать ActivityFaultContext, чтобы указать среде выполнения, что ошибка была обработана, что позволяет ему от пузыря и выхода из действия. На рисунке 35 показаны методы обратного вызова для действия Итератора, в котором как запланировать следующую итерацию, так и ОшибкаCallback обрабатывает ошибку, чтобы разрешить выполнение продолжить следующую итерацию.
private void ChildComplete(NativeActivityContext context,
Экземпляр ActivityInstance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
BeginIteration(context);
}
private void ChildFaulted(NativeActivityFaultContext context, exception ex,
Экземпляр ActivityInstance)
{
CurrentIteration.Set(context, CurrentIteration.Get(context) + 1);
контекст. HandleFault();
}
рис. 35. Обратные вызовы действий
Если ваше действие должно выполняться долго, в идеале это может быть передано другому потоку, чтобы поток рабочего процесса использовался для продолжения обработки других действий или использования для обработки других рабочих процессов. Однако простое запуск потоков и возврат управления в среду выполнения может привести к проблемам, так как среда выполнения не отслеживает создаваемые потоки. Если среда выполнения определила, что рабочий процесс неактивен, рабочий процесс может выгрузить, удалить методы обратного вызова или среда выполнения может предположить, что действие выполняется и переходите к следующему действию в рабочем процессе. Для поддержки асинхронного программирования в действиях, производных от AsyncCodeActivity или AsyncCodeActivity<T>.
На рисунке 36 показан пример асинхронного действия, загружающего RSS-канал. Эта сигнатура метода execute отличается от асинхронных действий, возвращаемых IAsyncResult. Код записывается в методе выполнения, чтобы начать асинхронную операцию. Переопределите метод EndExecute для обработки обратного вызова при завершении асинхронной операции и возвратите результат выполнения.
общедоступный класс GetFeed: AsyncCodeActivity<SyndicationFeed>
{
public InArgument<URI> FeedUrl { get; set; }
защищенное переопределение IAsyncResult BeginExecute(
Контекст AsyncCodeActivityContext, обратный вызов AsyncCallback,
состояние объекта)
{
var req = (HttpWebRequest)HttpWebRequest.Create(
FeedUrl.Get(context));
req. Метод = GET;
контекст. UserState = req;
возврат req. BeginGetResponse(new AsyncCallback(callback), state);
}
защищенное переопределение SyndicationFeed EndExecute(
Контекст AsyncCodeActivityContext, результат IAsyncResult)
{
HttpWebRequest req = context. UserState как HttpWebRequest;
WebResponse wr = req. EndGetResponse(result);
SyndicationFeed localFeed = SyndicationFeed.Load(
XmlReader.Create(wr). GetResponseStream());
возвращает localFeed;
}
}
рис. 36. Создание асинхронных действий
Дополнительные понятия о действиях
Теперь, когда вы видели основы создания действий с помощью базовых классов, аргументов и переменных, а также управления выполнением; Я кратко коснуюсь некоторых более сложных функций, которые можно использовать в разработке действий.
Закладки позволяют автору действий создать точку возобновления в выполнении рабочего процесса. После создания закладки его можно возобновить, чтобы продолжить обработку рабочего процесса, из которой она не была отключена. Закладки сохраняются как часть состояния, поэтому в отличие от асинхронных действий после создания закладки, это не проблема для сохранения и выгрузки экземпляра рабочего процесса. Когда узел хочет возобновить рабочий процесс, он загружает экземпляр рабочего процесса и вызывает метод ResumeBookmark, чтобы возобновить экземпляр, из которого он остался. При возобновлении закладок данные также можно передать в действие. На рисунке 37 показано действие ReadLine, которое создает закладку для получения входных данных и регистрирует метод обратного вызова, вызываемый при поступлении данных. Среда выполнения знает, когда действие имеет невыполненные закладки и не закроет действие, пока закладка не будет возобновлена. Метод ResumeBookmark можно использовать в классе WorkflowApplication для отправки данных в именованную закладку и сигнализировать о ЗакладкеCallback.
public class ReadLine : NativeActivity<string>
{
public OutArgument<string> InputText { get; set; }
защищенное переопределение void Execute(NativeActivityContext context)
{
контекст. CreateBookmark("ReadLine",
new BookmarkCallback(BookmarkResumed));
}
private void BookmarkResumed(NativeActivityContext context,
Закладка bk, состояние объекта)
{
Result.Set(context, state);
}
}
рис. 37. Создание закладки
Еще одной мощной функцией для авторов действий является концепция ActivityAction. ActivityAction — это эквивалент рабочего процесса класса Action в императивном коде: описание общего делегата; но для некоторых, идея шаблона может быть проще понять. Рассмотрим действие ForEach<T>, которое позволяет выполнять итерацию по набору данных и планировать дочернее действие для каждого элемента данных. Вам нужен какой-то способ разрешить потребителю действия определить тело и иметь возможность использовать элемент данных типа T. По сути, вам потребуется некоторое действие, которое может принять элемент типа T и действовать над ним, ActivityAction<T> используется для включения этого сценария и предоставляет ссылку на аргумент и определение действия для обработки элемента. Вы можете использовать ActivityAction в пользовательских действиях, чтобы позволить потребителям действия добавлять собственные шаги в соответствующие точки. Это позволяет создавать шаблоны рабочих процессов или действий типа, где потребитель может заполнить соответствующие части, чтобы настроить выполнение для их использования. Вы также можете использовать ActivityFunc<TResult> и связанные альтернативные варианты, когда делегату необходимо вызвать значение.
Наконец, можно проверить действия, чтобы убедиться, что они настроены правильно, проверяя отдельные параметры, ограничивающие допустимые дочерние действия и т. д. Для общего требования к конкретному аргументу можно добавить атрибут RequiredArgument в объявление свойства в действии. Для более активной проверки в конструкторе действия создайте ограничение и добавьте его в коллекцию ограничений, отображаемых в классе Activity. Ограничение — это действие, записанное для проверки целевого действия и обеспечения действительности. На рисунке 38 показан конструктор для действия Итератора, который проверяет, задано ли свойство RequestedIterations. Наконец, переопределяя метод CacheMetadata, можно вызвать метод AddValidationError в параметре ActivityMetdata, чтобы добавить ошибки проверки для действия.
var act = new DelegateInArgument<Iterator> { Name = "constraintArg" };
var vctx = new DelegateInArgument<ValidationContext>();
Ограничения<итератора> минусы = новые<итератора>
{
Body = new ActivityAction<Iterator, ValidationContext>
{
Argument1 = act,
Argument2 = vctx,
Handler = new AssertValidation
{
Message = "Необходимо указать итерацию",
PropertyName = "RequestedIterations",
Утверждение = новый логический<inArgument>(
(e) => акт. Get(e). RequestedIterations != null)
}
}
};
основа. Constraints.Add(cons);
рис. 38. Ограничения
Конструкторы действий
Одним из преимуществ WF является то, что он позволяет программировать логику приложения декларативно, но большинство людей не хотят писать XAML вручную, поэтому конструктор в WF так важен. При создании пользовательских действий вам также потребуется создать конструктор, чтобы обеспечить взаимодействие с отображением и визуальным взаимодействием для потребителей вашего действия. Конструктор для WF основан на Windows Presentation Foundation, что означает, что у вас есть все возможности стилизации, триггеров, привязки данных и всех других отличных инструментов для создания богатого пользовательского интерфейса для конструктора. Кроме того, WF предоставляет некоторые пользовательские элементы управления, которые можно использовать в конструкторе для упрощения задачи отображения отдельного дочернего действия или коллекции действий. Четыре основных элемента управления:
- ActivityDesigner — корневой элемент управления WPF, используемый в конструкторах действий
- WorkflowItemPresenter — используется для отображения одного действия
- WorkflowItemsPresenter — используется для отображения коллекции дочерних действий
- ExpressionTextBox — используется для включения на месте редактирования выражений, таких как аргументы
WF4 содержит шаблон проекта ActivityDesignerLibrary и шаблон элемента ActivityDesigner, который можно использовать для первоначального создания артефактов. После инициализации конструктора можно начать с помощью приведенных выше элементов, чтобы настроить внешний вид и ощущение действия, указав способ отображения данных и визуальных представлений дочерних действий. В конструкторе есть доступ к ModelItem, который является абстракцией по фактическому действию, и отображение всех свойств действия.
Элемент управления WorkflowItemPresenter можно использовать для отображения одного действия, который является частью действия. Например, чтобы предоставить возможность перетащить действие на действие Итератора, показанное ранее, и отобразить действие, содержащееся в действии Iteractor, можно использовать XAML, показанный на рис. 39, привязав WorkflowItemPresenter к ModelItem.Body. На рисунке 40 показан конструктор, используемый в рабочей области конструктора рабочих процессов.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.IteratorDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
> сетки <
<BorderBrush="MidnightBlue" BorderThickness="3" CornerRadius="10">
<sap:WorkflowItemPresenter MinHeight="50"
Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
HintText="Добавить текст здесь" />
</Border>
</Grid>
</sap:ActivityDesigner>
рис. 39. WorkflowItemPresenter в конструкторе действий
рис. 40. Конструктор итератора
WorkflowItemsPresenter предоставляет аналогичные функции для отображения нескольких элементов, таких как дочерние элементы в последовательности. Вы можете определить шаблон и ориентацию для отображения элементов, а также шаблон пробелов для добавления визуальных элементов между действиями, такими как соединители, стрелки или что-то более интересное. На рисунке 41 показан простой конструктор для пользовательского действия последовательности, отображающего дочерние действия с горизонтальной линией между каждым.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.CustomSequenceDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation">
> сетки <
<StackPanel>
<BorderBrush="Goldenrod" BorderThickness="3">
<sap:WorkflowItemsPresenter HintText="Drop Activities here"
Items="{Binding Path=ModelItem.ChildActivities}">
<sap:WorkflowItemsPresenter.SpacerTemplate>
<DataTemplate>
<Rectangle Height="3" Width="40"
Fill="MidnightBlue" Margin="5" />
</DataTemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
<sap:WorkflowItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</sap:WorkflowItemsPresenter.ItemsPanel>
</sap:WorkflowItemsPresenter>
</Border>
</StackPanel>
</Grid>
</sap:ActivityDesigner>
рис. 41. Конструктор настраиваемых последовательностей с помощью WorkflowItemsPresenter
рис. 42. Конструктор последовательностей
Наконец, элемент управления ExpressionTextBox включает редактирование аргументов действий на месте в конструкторе в дополнение к сетке свойств. В Visual Studio 2010 это включает поддержку intellisense для введенных выражений. На рисунке 43 показан конструктор действия GetManager, созданного ранее, что позволяет изменять аргументы EmployeeName и ManagerEmail в конструкторе. Фактический конструктор показан на рис. 44.
<sap:ActivityDesigner x:Class="CustomActivities.Presentation.GetManagerDesigner"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;
assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;
assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;
assembly=System.Activities.Presentation">
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter
x:Key="ArgumentToExpressionConverter"
x:Uid="swdv:ArgumentToExpressionConverter_1" />
</sap:ActivityDesigner.Resources>
> сетки <
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center"
HorizontalAlignment="Center">Employee:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="1"
Expression="{Binding Path=ModelItem.EmployeeName, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=In}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50"
HintText="< Имя сотрудника>" />
<TextBlock Grid.Row="1" VerticalAlignment="Center"
HorizontalAlignment="Center">Mgr Email:</TextBlock>
<sapv:ExpressionTextBox Grid.Column="2" Grid.Row="1"
UseLocationExpression="True"
Expression="{Binding Path=ModelItem.ManagerEmail, Mode=TwoWay,
Converter={StaticResource ArgumentToExpressionConverter},
ConverterParameter=Out}" OwnerActivity="{Binding Path=ModelItem}"
MinLines="1" MaxLines="1" MinWidth="50" />
</Grid>
</sap:ActivityDesigner>
рис. 43. Использование ExpressionTextBox для изменения аргументов в конструкторе
рис. 44. Конструктор действий GetManager
Примеры конструкторов, представленных здесь, были просты, чтобы выделить конкретные функции, но полная мощность WPF находится в вашем распоряжении, чтобы упростить настройку и более естественное использование для ваших потребителей. После создания конструктора можно связать его с действием двумя способами: применение атрибута к классу действий или реализация интерфейса IRegisterMetadata. Чтобы определение действия двигало выбор конструктора, просто примените DesignerAttribute к классу действий и укажите тип для конструктора; это работает точно так же, как и в WF3. Для более слабо связанной реализации можно реализовать интерфейс IRegisterMetadata и определить атрибуты, которые необходимо применить к классу действий и свойствам. На рисунке 45 показан пример реализации для применения конструкторов для двух пользовательских действий. Visual Studio обнаруживает реализацию и вызывает ее автоматически, в то время как пользовательский узел конструктора, рассмотренный далее, будет явно вызывать метод.
метаданные общедоступного класса: IRegisterMetadata
{
public void Register()
{
Конструктор AttributeTableBuilder = new AttributeTableBuilder();
строитель. AddCustomAttributes(typeof(SendMail), new Attribute[] {
new DesignerAttribute(typeof(SendMailDesigner))});
строитель. AddCustomAttributes(typeof(GetManager), новый атрибут[]{
new DesignerAttribute(typeof(GetManagerDesigner))});
MetadataStore.AddAttributeTable(builder). CreateTable());
}
}
рис. 45. Регистрация конструкторов для действий
Повторное размещение конструктора рабочих процессов
В прошлом разработчики часто хотели предоставить пользователям возможность создавать или изменять рабочие процессы. В предыдущих версиях Windows Workflow Foundation это возможно, но не тривиальная задача. При переходе к WPF появился новый конструктор и повторное размещение было основным вариантом использования для команды разработчиков. Конструктор можно разместить в пользовательском приложении WPF, как показано на рис. 46, с несколькими строками XAML и несколькими строками кода.
рис. 46. Повторное размещение конструктора рабочих процессов
XAML для окна рис. 47 использует сетку для макета трех столбцов, а затем помещает контроль границы в каждую ячейку. В первой ячейке панель элементов создается декларативно, но также может быть создана в коде. Для самого представления конструктора и сетки свойств в каждой оставшейся ячейке есть два пустых элемента управления границами. Эти элементы управления добавляются в код.
<Window x:Class="WorkflowEditor.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System; assembly=mscorlib"
xmlns:sapt="clr-namespace:System.Activities.Presentation.Toolbox;
assembly=System.Activities.Presentation"
Title="Workflow Editor" Height="500" Width="700" >
<Window.Resources>
<sys:String x:Key="AssemblyName">System.Activities, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad36ad364e35</sys:String>
<sys:String x:Key="MyAssemblyName">CustomActivities</sys:String>
</Window.Resources>
<Grid x:Name="DesignerGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="7*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<границы>
<sapt:ToolboxControl>
<sapt:ToolboxControl.Categories>
<sapt:ToolboxCategory CategoryName="Basic">
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource AssemblyName}" >
<sapt:ToolboxItemWrapper.ToolName>
System.Activities.Statements.Sequence
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
<sapt:ToolboxItemWrapper
AssemblyName="{StaticResource MyAssemblyName}">
<sapt:ToolboxItemWrapper.ToolName>
CustomActivities.SendMail
</sapt:ToolboxItemWrapper.ToolName>
</sapt:ToolboxItemWrapper>
</sapt:ToolboxCategory>
</sapt:ToolboxControl.Categories>
</sapt:ToolboxControl>
</Border>
<Border Name="DesignerBorder" Grid.Column="1"
BorderBrush="Salmon" BorderThickness="3" />
<Border x:Name="PropertyGridExpander" Grid.Column="2" />
</Grid>
</Window>
рис. 47. Повторное размещение XAML
Код для инициализации конструктора и сетки свойств показан на рис. 48. Первым шагом в методе OnInitialized является регистрация конструкторов для всех действий, которые будут использоваться в конструкторе рабочих процессов. Класс DesignerMetadata регистрирует связанные конструкторы для действий, которые поставляются с платформой, и класс метаданных регистрирует конструкторы для моих пользовательских действий. Затем создайте класс WorkflowDesigner и используйте свойства View и PropertyInspectorView для доступа к объектам UIElement, необходимым для отрисовки. Конструктор инициализируется пустой последовательностью, но вы также можете добавить меню и загрузить XAML рабочего процесса из различных источников, включая файловую систему или базу данных.
public MainWindow()
{
InitializeComponent();
}
защищенное переопределение void OnInitialized(EventArgs e) {
основа. OnInitialized(e);
RegisterDesigners();
PlaceDesignerAndPropGrid();
}
private void RegisterDesigners() {
new DesignerMetadata(). Register();
new CustomActivities.Presentation.Metadata(). Register();
}
private void PlaceDesignerAndPropGrid() {
Конструктор WorkflowDesigner = new WorkflowDesigner();
дизайнер. Load(new System.Activities.Statements.Sequence());
DesignerBorder.Child = конструктор. Вид;
PropertyGridExpander.Child = designer. PropertyInspectorView;
}
рис. 48. Повторное размещение кода конструктора
С помощью этого небольшого фрагмента кода и разметки можно изменить рабочий процесс, перетащите действия из панели элементов, настроить переменные в рабочем процессе и управлять аргументами для действий. Вы также можете получить текст XAML, вызвав Flush в конструкторе, а затем ссылаясь на свойство Text workflowDesigner. Есть гораздо больше возможностей, и начало работы гораздо проще, чем раньше.
Службы рабочих процессов
Как упоминалось ранее, одним из крупных областей в этом выпуске Windows Workflow Foundation является более тесная интеграция с Windows Communication Foundation. В этом пошаговом руководстве по действиям показано, как вызвать службу из рабочего процесса, а в этом разделе описано, как предоставить рабочий процесс в качестве службы WCF.
Чтобы начать, создайте проект и выберите проект службы рабочих процессов WCF. После завершения шаблона вы найдете проект с файлом web.config и файлом с расширением XAMLX. XAMLX-файл является декларативной службой или службой рабочих процессов и, как и другие шаблоны WCF, предоставляет функционирующую, хотя и простую, реализацию службы, которая получает запрос и возвращает ответ. В этом примере я изменю контракт, чтобы создать операцию для получения отчета о расходах и вернуть индикатор получения отчета. Чтобы начать, добавьте класс ExpenseReport в проект, как показано на рис. 49.
[DataContract]
public class ExpenseReport
{
[DataMember]
public DateTime FirstDateOfTravel { get; set; }
[DataMember]
public double TotalAmount { get; set; }
[DataMember]
public string EmployeeName { get; set; }
}
рис. 49. Тип контракта отчета о расходах
Вернувшись в конструктор рабочих процессов, измените тип переменной "data" в переменных только что определенный тип ExpenseReport (может потребоваться создать проект для отображения в диалоговом окне выбора типов). Обновите действие получения, нажав кнопку "Содержимое" и изменив тип в диалоговом окне на тип ExpenseReport для сопоставления. Обновите свойства действия, чтобы определить новое имя операции и ServiceContractName, как показано на рис. 50.
рис. 50. Настройка расположения получения
Теперь действие SendReply может иметь значение True, чтобы вернуть индикатор получения. Очевидно, что в более сложном примере можно использовать полную мощность рабочего процесса для сохранения сведений в базе данных, проверки отчета и т. д. перед определением ответа. Просмотр службы с помощью приложения WCFTestClient показывает, как конфигурация действия определяет предоставленный контракт службы, как показано на рис. 51.
рис. 51. Контракт, созданный из службы рабочих процессов
После создания службы рабочего процесса необходимо разместить ее так же, как и в любой службе WCF. В предыдущем примере используется самый простой вариант для размещения: IIS. Чтобы разместить службу рабочего процесса в собственном процессе, необходимо использовать класс WorkflowServiceHost, найденный в сборке System.ServiceModel.Activities. Обратите внимание, что в сборке System.WorkflowServices есть другой класс, который используется для размещения служб рабочих процессов, созданных с помощью действий WF3. В самом простом случае самостоятельного размещения можно использовать код, как показано на рис. 52, для размещения службы и добавления конечных точек.
Узел WorkflowServiceHost = новый
WorkflowServiceHost(XamlServices.Load("ExpenseReportService.xamlx"),
new Uri("https://localhost:9897/Services/Expense"));
хозяин. AddDefaultEndpoints();
хозяин. Description.Behaviors.Add(
new ServiceMetadataBehavior { HttpGetEnabled = true });
хозяин. Open();
Console.WriteLine("Узел открыт");
Console.ReadLine();
рис. 52. Самостоятельное размещение службы рабочих процессов
Если вы хотите воспользоваться преимуществами управляемых параметров размещения в Windows, можно разместить службу в СЛУЖБАх IIS, скопируйв XAMLX-файл в каталог и указав все необходимые сведения о конфигурации для поведения, привязок, конечных точек и т. д. в файле web.config. Как показано ранее, при создании проекта в Visual Studio с помощью одного из шаблонов проектов декларативной службы рабочих процессов вы получите проект с файлом web.config и службой рабочих процессов, определенной в XAMLX, готовой к развертыванию.
При использовании класса WorkflowServiceHost для размещения службы рабочих процессов необходимо настроить и контролировать экземпляры рабочих процессов. Для управления службой существует новая стандартная конечная точка с именем WorkflowControlEndpoint. Класс-компаньон, WorkflowControlClient, предоставляет предварительно созданный прокси-сервер клиента. Вы можете предоставить workFlowControlEndpoint в службе и использовать WorkflowControlClient для создания и запуска новых экземпляров рабочих процессов или управления выполнением рабочих процессов. Эта же модель используется для управления службами на локальном компьютере или для управления службами на удаленном компьютере. На рисунке 53 показан обновленный пример предоставления конечной точки управления рабочим процессом в службе.
Узел WorkflowServiceHost = новый
WorkflowServiceHost("ExpenseReportService.xamlx",
new Uri("https://localhost:9897/Services/Expense"));
хозяин. AddDefaultEndpoints();
WorkflowControlEndpoint wce = new WorkflowControlEndpoint(
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/Expense/WCE"));
хозяин. AddServiceEndpoint(wce);
хозяин. Open();
рис. 53. Конечная точка управления рабочими процессами
Так как вы не создаете workflowInstance напрямую, существует два варианта добавления расширений в среду выполнения. Первым является то, что можно добавить некоторые расширения в WorkflowServiceHost, и они будут инициализировать правильно с экземплярами рабочих процессов. Во-вторых, используется конфигурация. Например, система отслеживания может быть полностью определена в файле конфигурации приложения. Вы определяете участников отслеживания и профили отслеживания в файле конфигурации и используете поведение, примените конкретного участника к службе, как показано на рис. 54.
<system.serviceModel>
> отслеживания <
<участников>
<добавить имя="EtwTrackingParticipant"
type="System.Activities.Tracking.EtwTrackingParticipant, System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
profileName="HealthMonitoring_Tracking_Profile"/>
</участников>
> профилей <
<trackingProfile name="HealthMonitoring_Tracking_Profile">
<действие рабочего процессаDefinitionId="*">
<workflowInstanceQuery>
> состояния <
<state name="Started"/>
<имя состояния="Завершено"/>
</states>
</workflowInstanceQuery>
</workflow>
</trackingProfile>
</profiles>
</tracking>
. . .
<поведения>
<serviceBehaviors>
<имя поведения="SampleTrackingSample.SampleWFBehavior">
<etwTracking profileName=" HealthMonitoring_Tracking_Profile" />
</behavior>
</serviceBehaviors>
</behaviors>
. . .
рис. 54. Настройка отслеживания служб
Существует множество новых улучшений для размещения и настройки служб WCF, которые можно использовать в рабочих процессах, таких как службы svc-less, или службы, которые не нуждаются в расширении файла, привязках по умолчанию и конечных точках по умолчанию только для названия нескольких. Дополнительные сведения об этих и других функциях WCF4 см. в документе-компаньоне о WCF, описанном в разделе "Дополнительные ресурсы".
Помимо улучшений размещения, Windows Workflow Foundation использует другие функции в WCF; некоторые напрямую и другие косвенно. Например, корреляция сообщений теперь является ключевой функцией в создании служб, которая позволяет связывать сообщения с заданным экземпляром рабочего процесса на основе данных в сообщении, например номера заказа или идентификатора сотрудника. Корреляция может быть настроена для различных действий обмена сообщениями как для запроса, так и ответа, и поддерживает корреляцию по нескольким значениям в сообщении. Опять же, дополнительные сведения об этих новых функциях можно найти в вспомогательном документе WCF4.
Заключение
Новая технология Windows Workflow Foundation, которая поставляется с .NET Framework 4, представляет собой значительное улучшение производительности и производительности разработчиков и реализует цель декларативной разработки приложений для бизнес-логики. Платформа предоставляет средства для упрощения коротких единиц сложной работы, а также для создания сложных длительных служб с коррелированным сообщением, сохраняемого состояния и полнофункциональные видимости приложений с помощью отслеживания.
Сведения о авторе
Мэтт является членом технического персонала в Pluralsight, где он фокусируется на технологиях подключенных систем (WCF, WF, BizTalk, AppFabric и платформе служб Azure). Мэтт также является независимым консультантом, специализирующимся на разработке и разработке приложений Microsoft .NET. Как писатель Мэтт способствовал нескольким журналам и журналам, включая MSDN Magazine, где он в настоящее время авторов содержимого рабочего процесса для столбца "Основы". Мэтт регулярно делится своей любовью к технологии, выступая на местных, региональных и международных конференциях, таких как Tech Ed. Корпорация Майкрософт признала Мэтта MVP своими вкладами сообщества в области технологий подключенных систем. Свяжитесь с Мэттом через свой блог: http://www.pluralsight.com/community/blogs/matt/default.aspx.