📜 ⬆️ ⬇️

Workflow Foundation sequential processes

Hello! Today we will finally move on to the practical part of our mini-program for studying the Workflow Foundation. In this article I will briefly dwell on sequential processes (Sequential Workflow) and describe an example of creating an application for backing up files. Let me remind you that this is more an example of working with an editor than a description of real use. Everything described in the practical example can and should be done without using WF. =)

Task


Today our task will be to make a program that copies new and changed files from the source folder to the destination folder. Here is how the flowchart of the application being developed will look like:



There is nothing difficult in this program and you can implement it with the help of a dozen lines in 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 .


But our task is not to rewrite files, but to get acquainted with the basics of WF. Let's look at the basic elements (activity) that we need.

Code Activity




CodeActivity allows you to execute any code. This can be a log entry or database access. The red exclamation point in the WF designer means that there are mandatory element properties that were left unspecified. In the case of CodeActivity, this warning appeared due to the missing ExecuteCode handler, which is actually a method that executes user code.

IfElse Activity




As you can guess from the name and image, IfElseActivity is a block for checking conditions. It may consist of one or more branches, each of which executes nested code when a given condition is met. You can add branches by choosing Add Branch in the context menu. The latter branch may not have conditions and will be executed if other conditions are not met. The exclamation mark in this case means the absence of a given condition. The condition can be a logical expression specified in the designer or a method that takes as arguments (object sender, ConditionalEventArgs e) . You can change the condition type in the branch parameters:



If you select Declarative Rule Condition, you must also specify the name of the rule to be set. In the future, it can be used in other check blocks.

While activity




No less eloquent title allows you to determine that this block performs the functions of the while construct. Just as in the IfElse branches, in order for an exclamation mark to disappear, you must specify a condition in the designer or code. Only one element can be placed inside the block. Therefore, it is often necessary to place a Sequence Activity there .

Sequence activity




The Sequence Activity element can contain any other elements. It is useful to us in the while loop , which allows you to add only one element to yourself.

Example


Let's go directly to the example. We will have two projects. One project will be a library with processes, and the second will be a console application that will use this library.

Creating a library



Let's start by creating a new empty Workflow Project :



And add to it a new Sequential Workflow called SyncWorkflow :



As a result, the designer should open, suggesting that we drag the Activity to create a sequential process:



And we will immediately take advantage of this offer by placing the IfElse Activity there. Using the Properties panel, set the ifElse element to the name checkDirectory, and its branches ifSourceDirectoryExists and ifSourceDirectoryNotExists, respectively. As a result, checkDirectory should look like this:



Now we need to check if the source directory exists. For this we need a variable that stores the path to the source folder, as well as another one for the destination folder. To do this, go to the code editor by pressing F7 in the designer or selecting View Code in the context menu of the file. Right after the constructor, we will create two properties:

//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 .


Now we can proceed to checking for the existence of the source folder. Let's go back to the designer window and select the ifSourceDirectoryExists branch, which should have an exclamation mark on it. For the Condition value in the Properties window, select the Declarative Rule Condition and click on the appeared plus sign.



ConditionName is set equal to directoryExists and click on the button with ellipsis located in the Expression field. An editor opens in which you need to enter a logical expression to verify the existence of the source folder:



After clicking the OK button, the exclamation point in the ifSourceDirectoryExists element should disappear.
Now let's drag the Code Activity into the ifSourceDirectoryExists branch and name it LoadDirectoryInfo . Double click of the mouse will take us to the automatically generated method, which will be called when it is time to execute this element. In this method we will prepare the information necessary for rewriting files. For this we need several additional fields. Here is what the modified code looks like:

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 .


We defined the initial variables for the while loop . It is time to add the cycle itself. To do this, we will drag the While Activity into the first branch of the loop and name it whileHaveFiles :



For the sake of diversity, we create a condition for a loop in the code. To do this, put the following method in the editor:

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 .


In the designer, set the value of Conditio n to Code Condition and select the newly created method in the drop-down list:



In order for the cycle not to be eternal, we will need another Code Activity. But if we immediately add it to the while block, then, as we mentioned earlier, we will not be able to add a single element there anymore. To do this, we first drag the Sequence Activity and name it copyFile . And in it we will create a Code Activity called IncrementFileIndex with the following code:

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


It remains the case for small: rewrite the necessary files. Before IncrementFileIndex, we will create ifElseBlock with one branch and name them checkFile and ifNeedCopy respectively:



Add the following verification method:

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 .


As well as the element that copies the file:

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 .


The final touch is to display in the console a message about the missing source folder. In the end, we should have the following scheme:



Creating a console application


The second step will be the integration of our process into the application. Add a console application project:



To it you need to add a link to the project WorkflowProject :



As well as links to the three libraries needed to run workflows:





Let's now open the Program.cs file and change the Main method as follows:

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 .


First, an AutoResetEvent object is created to synchronize the workflow with the main program. This happens when waitHandle.Set() called in the process termination event. I want to note that these events occur at the completion of any process in workflowRuntime . Therefore, it is necessary to check which process the event belongs to.

Parameters are passed through the <string,object> dictionary, in which the keys match the names of the public properties of the object. If such a property does not exist, an exception will be generated.

The working process instance is created by the CreateWorkflow method, taking the type of the process and its parameters as arguments. After that it is started by the Start method.

To get a complete program, I suggest using command line arguments to pass parameters:

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


Now the utility can be run from the command line, specifying the source folder and destination folder.

Conclusion


In this article, we familiarized with the basic technical principles of designing sequential processes and processes in WF in principle. In the next article, we will create a state machine-based workflow process that will use the result of today's work as an integral component.

Project Source Files


HabrWorkflowProject.zip

Required components


To work with WF, you must have .net Framework 3 or higher. You will also need Visual Studio 2008 or Visual Studio 2005 with WF extensions installed.

Source: https://habr.com/ru/post/47172/


All Articles