Pull to refresh

Последовательные процессы в Workflow Foundation

Reading time10 min
Views6.7K
Всем привет! Сегодня мы наконец-то перейдем к практической части нашей мини-программы по изучению Workflow Foundation. В этой статье я немного подробнее остановлюсь на последовательных процессах (Sequential Workflow) и опишу пример создания приложения для резервного копирования файлов. Напомню, что это скорее пример работы с редактором, чем описание реального применения. Все описанное в практическом примере можно и нужно делать без использования WF. =)

Задача


Сегодня нашей задачей будет сделать программу, которая осуществляет копирование новых и изменившихся файлов из папки источника в папку назначения. Вот как будет выглядеть блок-схема разрабатываемого приложения:



Ничего сложного в этой программе нет и реализовать ее можно с помощью десятка строчек на C#:

  if(Directory.Exists(from))<br>      {<br>        if(!Directory.Exists(to))<br>          Directory.CreateDirectory(to);<br><br>        new DirectoryInfo(from).GetFiles().Where(<br>          f =><br>          !File.Exists(to + f.Name) ||<br>          f.LastWriteTime > new FileInfo(to + f.Name).LastWriteTime).ToList().<br>          ForEach(file => File.Copy(from+file.Name,to + file.Name,true));<br>      }<br><br>* This source code was highlighted with Source Code Highlighter.


Но наша задача состоит не в том, чтобы переписать файлы, а в том, чтобы познакомиться с основами WF. Давайте рассмотрим основные элементы (activity), которые нам понадобятся.

Code Activity




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

IfElse Activity




Как можно догадаться из названия и изображения IfElseActivity представляет собой блок проверки условия. Он может состоять из одного или больше разветвлений, каждое из которых выполняет вложенный код при выполнении заданного условия. Добавить ветви можно выбрав пункт Add Branch в контекстном меню. Последняя ветвь может не иметь условия и выполнится при невыполнении других условий. Восклицательный знак в этом случае означает отсутствие заданного условия. В качестве условия может выступать логическое выражение, заданное в дизайнере или метод, принимающий в качестве аргументов (object sender, ConditionalEventArgs e). Изменить тип условия можно в параметрах ветви:



При выборе Declarative Rule Condition необходимо также указать имя задаваемого правила. В дальнейшем его можно использовать в других блоках проверки.

While Activity




Не менее красноречивое название позволяет определить, что этот блок выполняет функции конструкции while. Так же, как и в ветвях IfElse, для того, чтобы пропал восклицательный знак необходимо задать условие в дизайнере или коде. Внутрь блока можно поместить только один элемент. Поэтому зачастую бывает необходимо поместить туда Sequence Activity.

Sequence Activity




Элемент Sequence Activity может содержать в себе любые другие элементы. Он нам пригодится в цикле while, который позволяет добавить в себя только один элемент.

Пример


Перейдем непосредственно к примеру. У нас будет два проекта. Один проект будет библиотекой с процессами, а второй — консольным приложением, которое будет эту библиотеку использовать.

Создание библиотеки



Давайте начнем с создания нового пустого Workflow Project:



И добавим к нему новый Sequential Workflow с названием SyncWorkflow:



В результате должен открыться дизайнер, предлагающий нам перетащить Activity для создания последовательного процесса:



И мы сразу же воспользуемся этим предложением, поместив туда IfElse Activity. С помощью панели Properties задайте элементу ifElse имя checkDirectory, а его разветвлениям ifSourceDirectoryExists и ifSourceDirectoryNotExists соответственно. В результате checkDirectory должен выглядеть следующим образом:



Теперь нам необходимо проверить, существует ли исходная директория. Для этого нам понадобится переменная, хранящая путь до папки источника, а также еще одна для папки назначения. Для этого перейдем в редактор кода, нажав F7 в дизайнере или выбрав View Code в контекстном меню файла. Сразу после конструктора создадим два свойства:

 //path to source folder<br>    public string From { get; set; }<br>    //path to destination folder<br>    public string To { get; set; }<br><br>* This source code was highlighted with Source Code Highlighter.


Теперь мы можем перейти к проверке на существование папки источника. Вернемся в окно дизайнера и выберем ветвь ifSourceDirectoryExists, у которой должен гореть восклицательный знак. У значения Condition в окне Properties выберем Declarative Rule Condition и нажмем на появившийся плюсик.



ConditionName установим равным directoryExists и нажмем на кнопку с многоточием, расположенную в поле Expression. Откроется редактор, в котором необходимо ввести логическое выражение для проверки существования папки источника:



После нажатия кнопки ОК восклицательный знак в элементе ifSourceDirectoryExists должен пропасть.
Теперь давайте перетащим Code Activity в ветку ifSourceDirectoryExists и назовем его LoadDirectoryInfo. Двойной щелчок мыши перенесет нас в автоматически сгенерированный метод, который будет вызван тогда, когда наступит время выполнения этого элемента. В этом методе мы подготовим информацию, необходимую для переписывания файлов. Для этого нам понадобятся несколько дополнительных полей. Вот как выглядит измененный код:

    private int currentFileIndex;<br>    private int fileCount;<br>    private FileInfo[] files;<br><br>    private void LoadDirectoryInfo_ExecuteCode(object sender, EventArgs e)<br>    {<br>      //intialize while cycle<br>      currentFileIndex = 0;<br>      files = new DirectoryInfo(From).GetFiles();<br>      fileCount = files.Count();<br><br>      //create the backup folder if not exists<br>      Directory.CreateDirectory(To);<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


Мы определили начальные переменные для цикла while. Настало время добавить и сам цикл. Для этого мы перетащим While Activity в первую ветку цикла и назовем его whileHaveFiles:



Ради разнообразия создадим условие для цикла в коде. Для этого поместим следующий метод в редактор:

private void CheckFileIndex(object sender, ConditionalEventArgs e)<br>    {<br>      e.Result = currentFileIndex < fileCount;<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


В дизайнере установим значение Condition равным Code Condition и в выпадающем списке выберем только что созданный метод:



Для того чтобы цикл не был вечным нам понадобится еще один Code Activity. Но если мы сразу же добавим его в блок while, то, как мы уже упоминали ранее, не сможем туда больше добавить ни одного элемента. Для этого мы перетащим сначала Sequence Activity и назовем его copyFile. А уже в нем создадим Code Activity под названием IncrementFileIndex со следующим кодом:

    private void IncrementFileIndex_ExecuteCode(object sender, EventArgs e)<br>    {<br>      currentFileIndex++;<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


Осталось дело за малым: переписать нужные файлы. Перед IncrementFileIndex создадим ifElseBlock с одной ветвью и назовем их checkFile и ifNeedCopy соответственно:



Добавим следующий метод проверки:

private void CheckDestinationFile(object sender, ConditionalEventArgs e)<br>    {<br>      var destinationFileName = To + files[ currentFileIndex ].Name;<br>      //check if file not exists<br>      e.Result = !File.Exists(destinationFileName) ||<br>        //or modified<br>            new FileInfo(destinationFileName).LastWriteTime <<br>            files[ currentFileIndex ].LastWriteTime;<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


А так же элемент, выполняющий копирование файла:

private void Copy_ExecuteCode(object sender, EventArgs e)<br>    {<br>      File.Copy(files[currentFileIndex].FullName,<br>           To + files[currentFileIndex].Name,<br>           true);<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


Последним штрихом будет вывод в консоль сообщения о ненайденной папке-источнике. В конечном итоге у нас должна получиться следующая схема:



Создание консольного приложения


Вторым этапом будет интеграция нашего процесса в приложение. Добавим проект консольного приложения:



К нему необходимо добавить ссылку на проект WorkflowProject:



А также ссылки на три библиотеки, необходимые для выполнения рабочих процессов:

  • System.Workflow.Activities
  • System.Workflow.ComponentModel
  • System.Workflow.Runtime




Давайте теперь откроем файл Program.cs и изменим метод Main следующим образом:

private static void Main(string[] args)<br>    {<br>      using(var workflowRuntime = new WorkflowRuntime())<br>      {<br>        //AutoResetEvent for thread synchronization<br>        var waitHandle = new AutoResetEvent(false);<br>        <br>        //join when completed<br>        workflowRuntime.WorkflowCompleted +=<br>          ((sender, e) => waitHandle.Set());<br>        //join when termination occurs        <br>        workflowRuntime.WorkflowTerminated +=<br>          delegate(object sender, WorkflowTerminatedEventArgs e)<br>          {<br>            Console.WriteLine(e.Exception.Message);<br>            waitHandle.Set();<br>          };<br><br>        //parameter for the workflow<br>        var parameters = new Dictionary<string, object><br>                 {{"From", @"C:\test\"}, {"To", @"C:\backup\"}};<br><br>        //create workflow instance and parameters to it<br>        var instance =<br>          workflowRuntime.CreateWorkflow(typeof(WorkflowProject.SyncWorkflow),<br>                          parameters);<br>        <br>        //start forkflow<br>        instance.Start();<br><br>        //wait workflow for finish<br>        waitHandle.WaitOne();<br>      }<br>    }<br><br>* This source code was highlighted with Source Code Highlighter.


В начале создается объект AutoResetEvent для синхронизации рабочего процесса с основной программой. Это происходит при вызове waitHandle.Set() в событии завершения процесса. Хочу заметить, что эти события наступают при завершении любого процесса в workflowRuntime. Поэтому необходимо проверять к какому процессу относится событие.

Параметры передаются через словарь <string,object>, в котором ключи совпадают с названиями общедоступных свойств объекта. Если такого свойства не существует, то будет сгенерировано исключение.

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

Для получения законченной программы предлагаю использовать аргументы командной строки для передачи параметров:

var parameters = new Dictionary<string, object><br>         {{"From", args[0]}, {"To", args[1]}};<br><br>* This source code was highlighted with Source Code Highlighter.


Теперь утилиту можно запустить из командной строки, указав папку источник и папку назначения.

Заключение


В этой статье мы ознакомились с основными техническими принципами конструирования последовательных процессов и процессов в WF в принципе. В следующей статье мы создадим процесс на основе конечных автоматов (State machine workflow), который будет использовать результат сегодняшней работы в качестве составного компонента.

Исходные файлы проекта


HabrWorkflowProject.zip

Необходимые компоненты


Для работы с WF необходимо наличие .net Framework 3 или выше. Также понадобится Visual Studio 2008 или Visual Studio 2005 с установленными WF extensions.

Tags:
Hubs:
+37
Comments22

Articles