Documente Academic
Documente Profesional
Documente Cultură
In this FAQ we will quickly run through and get a feel of how WWF (Windows Workflow foundation) will help you in making custom work flows in your project.
Introduction
In this FAQ we will quickly run through and get a feel of how WWF (Windows Workflow foundation) will help you in making custom work flows in your project. In case you like the article or your think I need improvements please send me a message at http://www.questpond.com . I am sure every one needs improvements. Enjoy
Workflow namespace has all the necessary modules to develop any type of workflow.
What is a Workflow?
A Workflow is a set of activities, which is stored as model and they depict a process. Below figure depicts clearly the difference between Workflow and Activity. Every task is an activity and group of activity depicts a complete workflow. Workflow is run by the Workflow runtime engine.
Figure 1: - Work Flow Foundation Architecture Workflow model can be written in pure .NET code, pure XAML or Mix of XAML and .NET Code. A workflow model is compiled and can execute under windows, ASP.NET, Web services or windows services application.
There are two basics type of workflow Sequential Workflow and State machines workflow. A sequential workflow has clear start and finish boundaries. Workflow controls execution in Sequential workflow. In sequential execution, one task is executed after other. Sequential workflow is more rigid in format and execution path has a determistic nature. A State machine workflow is more dynamic in nature. Workflow has states and the state waits for events to help it move to next state. In State machine execution path is undetermestic nature. Below figure shows visual conceptualization of fundamentals. You can see in Sequential workflow the execution path is very determent. Shiv performs the entire task sequentially and these tasks are very determent. Now have a look at the second workflow. Every state goes to other state when it receives some external events. For instance when Shiv is seeing star trek there is an event of flashing news which triggers him to see the flashing new.
when should we use a sequential workflow and when should we use state machines?
If the workflow is very rigid then you go for sequential workflow and if the workflow is dynamic then go for State machine workflow. For instance you have placed an order and the order will not pass until your supervisor approves is a rigid flow. Because your order has to be approved by, a supervisor or else it will not be approved. But what if your order moves from one place to other place. For instance, it moves from approval to waiting and then clarification a state machine workflow model is more appropriate. Below is a simple code snippet which shows practically how to use sequential workflow. Let try to understand step by step as marked in the figure:1 - First you need to select System. Workflow namespace. 2, 3, and 4 - In these three steps we created code object and linked them with activity. 5, 6, 7, and 8 - We start the workflow and create workflow instance object to run the sequential workflow. You can see the output in 8. Depending on how you add the activity in section 3, it executes sequentially. Because we have added codeactivity1, first it executes the first activity first. The sequence on how you add the activity to the activities collection the activities are run.
Figure: - 3 Code snippet for workflow Note: - The above code snippet was developed with out using designer. The whole point was to make you understand what happens behind the scenes. In real projects you will be dependent on designer rather than coding manually. You can find the above code in SimpleWorkFlowSampleManual folder.
For instance in the current snapshot we have said that old1 method should execute if age > 21. The same procedure we need to follow for the second condition box. In the second condition box we have specified to execute young1 method if age < 21. Currently the second condition is not visible in the below snapshot. 3 - Workflow editor also provides a cool interface called as Rule Condition Editor, which can be used to specify conditions. Age is a public property in the behind code. You can also get the Age in the intelligence of rule condition editor. 4 - Both the condition will execute inside the condition activity group. We need to also specify when this conditionactivitygroup should exit. Therefore, we have made a function called as exit. If the user inputs age as -1 it will exit from the loop or else it will take inputs from user and continue evaluating depending on the two conditions.
activity, which points to a method called as raise Exception. Incase of exception in the workflow this path will be followed.
number wise:1 In order to create a XOML file you need to add sequential workflow with separation. Which means that XOML file will be created with a behind code. 2 Currently we have two activity code3 and code1. Below is the XOML file contents
<?Mapping XmlNamespace="ComponentModel" ClrNamespace="System.Workflow.ComponentModel" Assembly="System.Workflow.ComponentModel" ?> <?Mapping XmlNamespace="Compiler" ClrNamespace="System.Workflow.ComponentModel.Compiler" Assembly="System.Workflow.ComponentModel" ?> <?Mapping XmlNamespace="Activities" ClrNamespace="System.Workflow.Activities" Assembly="System.Workflow.Activities" ?> <?Mapping XmlNamespace="RuleConditions" ClrNamespace="System.Workflow.Activities.Rules" Assembly="System.Workflow.Activities" ?> <SequentialWorkflow x:Class="WorkflowSeq.Workflow1" x:CompileWith="Workflow1.xoml.cs" ID="Workflow1" xmlns:x="Definition" xmlns="Activities"> <Code ExecuteCode="Mycode3" ID="code3" /> <Code ExecuteCode="Mycode1" ID="code1" /> </SequentialWorkflow>
See the above snippet of the XOML file. You can see how the behind code is linked using the Compile With attribute. Code forms the element of the Sequential Workflow tag. One of the best thing with Markup is we can change the sequence just by changing the XOML file we do not need to compile the whole application again.
Figure 7:- XOML in action In the above snapshot, one of the things to now is 3, 4, and 5 numbered sections. These sections are not linked with the sample. But just to make you aware you can create serialize any workflow and deserialize them again using the text writer object.
Download
Introduction
Visual Studio 2010 released with workflow foundation 4.0. Workflow Foundation 4.0 is introduced a significant amount of change from the previous versions of the technology. The change varies from the core of the workflow foundation, runtime and tools. Workflow foundation 4.0 re-architected to improve the performance and productivity of the WF. One of the major changes is in Activity Designer. The new Activity Designer is based on WPF [Windows Presentation Framework], which gives lot of flexibility to the developer. Creation of a custom look and feel using the new Activity Designer is very easy.
Activity Designer
Before starting with the new Activity Designer, let us create an Activity Designer Library. Create a project of type Activity Designer Library.
Right click on the solution and Add an Activity Designer item to the project.
Activity Designer will create two file; one .xaml file and another .xaml.cs file. Designer level customizations are mainly performed in .xaml file. We can do some customization like, when a property change, change the value of another property by tracking the designer events in .xaml.cs file. In this article, we will look into the customization using .xaml file only. After adding the Activity Designer, add the custom activity class to the project. Following is the sample activity class derived from CodeActivity
[Designer(typeof(ActivityDesigner1))] public class ActivityDesigner : CodeActivity { protected override void Execute(CodeActivityContext context) { throw new NotImplementedException(); } }
The Designer attribute specified for the class bind the Activity with the specified Activity
Designer. Build the Project and add the Custom Activity to either FlowChart or Sequential workflow. The default design of the activity is as follows
The new Activity Designer with custom Icon will look like
Item attribute is bind to the child property of the corresponding Activity. Please refer the attached code for more details on the activity code. Prefix sap is defined as below
xmlns:sap="clrnamespace:System.Activities.Presentation;assembly=System.Activities.Presentat ion"
Drag any built-in or custom activity to the newly created custom activity. Here we dropped one WriteLine activity into our custom activity.
<DataTemplate> <Rectangle Fill="#FF1F6F6F" Width="10" Height="10" /> </DataTemplate> </sap:WorkflowItemsPresenter.SpacerTemplate> <sap:WorkflowItemsPresenter.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Vertical"/> </ItemsPanelTemplate> </sap:WorkflowItemsPresenter.ItemsPanel> </sap:WorkflowItemsPresenter>
In the following example, we added two activities to our Custom activity; one Assign Activity and another WriteLine Activity.
For more details about the user control, please refer the attached code sample. Now the activity designer will look like
Conclusion
Workflow Foundation 4.0 is giving the full flexibility to redefine the look and feel of a custom activity. You can define the activity to represent the functionality of the activity. This will make the custom activities more readable and understandable in the workflow. Also, the workflow design looks more readable and clear.
Custom Activity
WF4.0 supports the custom activity development. Custom Activity development involves two parts- designer development and execution logic implementation. I already discussed about the designer development in Activity Designer in Workflow Foundation 4.0 article. Part I : Introduction to Custom Activity Development Part II : Bookmark For developing the custom activity, you need to inherit from
NativeActivity base class of custom activities, which can have full control on runtime features. Code Activity- base class of custom activities, which dont have access to runtime features. AsynchCodeActivity Asynchronous Code execution Also, we have NativeActivity<T>, CodeActivity<T> and AsynchCodeActivity<T> base classes. In our sample custom activity, we will use the NativeActivity as base class
public class SampleCustomActivity:NativeActivity { protected override void Execute(NativeActivityContext context) { throw new NotImplementedException(); } }
How to Link Designer to the Activity? As I specified, activity designer is created separately using WPF. For binding the activity designer to the custom activity, we can use the following attribute on top of the activity
[Designer(typeof(ActivityDesigner.SampleCustomActivityDesigner))] public class SampleCustomActivity:NativeActivity
How to Prevent the expand/collapse nature? Drag our custom activity to the workflow designer and double click on the activity; it will expand to the next level. The built-in activities, like WriteLine, Delay and Assign, doesnt have expand and collapse behavior. If we need to implement the same behavior for our custom activity, which is not a container activity for other activities, use the following attribute
[ActivityDesignerOptions(AllowDrillIn = false)] [Designer(typeof(ActivityDesigner.SampleCustomActivityDesigner))] public class SampleCustomActivity:NativeActivity
How to pass data to the Activity? For passing data to the Activity, either we can use the InArguments or class level Properties. InArguments are special type of properties used for passing data to the Activity. InArguments is
related to an instance of the Activity and it required the particular activity instances context for accessing the data from InArgument. In the following Code we are defining an InArgument of type string with name Message
public class SampleCustomActivity:NativeActivity { public InArgument<string> Message{get;set;} protected override void Execute(NativeActivityContext context) { throw new NotImplementedException(); } }
When you drag and drop the custom activity to workflow designer, you can see the InArgument in the property grid.
We can get the value of the InArgument using any of the following statements. Variables message and message2 will contain the same value.
protected override void Execute(NativeActivityContext context) { string message = Message.Get(context); string message2=context.GetValue(Message); }
There are multiple ways to pass data from an activity to other activities or workflow. In the first example, we are defining an activity, which can return only one value. If an activity needs to return, only a single value, then inherit it from CodeActivity or NativeActivity or AsynchCodeActivity. Once you derive the custom activity from any of these classes, it defines an OutArgument with name as Result. We can set the value of this built-in OutArgument as specified below
public class SampleCustomActivity:NativeActivity<string> { public InArgument<string> Message { get; set; } protected override void Execute(NativeActivityContext context) { string message = Message.Get(context);
Result.Set(context,"Message from Custom Activity " + message); //context.SetValue(Result, "Message from Custom Activity " + message); } }
How to access the OutArgument Outside? Following picture shows, how we can access the OutArgument from outside the custom activity. Define a variable, which can hold the OutArgument; specify the same against the OutArgument in the property grid; use the variable in another activity.
This is the simple way of communication between activities or activity to host. There are other ways of communication using Extension methods, Workflow Properties, Bookmarks, WCF Services, etc. How to define an OutArgument? OutArgument is used for passing data out of an activity. We can define our own OutArguments as specified below
public class SampleCustomActivity:NativeActivity { public InArgument<string> Message { get; set; } public OutArgument<string> MessageFromActivity { get; set; }
protected override void Execute(NativeActivityContext context) { string message = Message.Get(context); context.SetValue(MessageFromActivity, "Message from Custom Activity " + message); } }
Conclusion
Here, we discussed about the custom activity development in Windows Workflow Foundation 4.0. This is an introduction to the custom activity development. In next articles, we will discuss more on the custom activity development and associated topics.
Bookmark
In WF4.0, we use different ways of communication between Host application and workflow. There are different approaches like Bookmark, Extension methods, WCF Workflow services, workflow level arguments, etc. Here, we will discuss about the simple way of communication between host application and workflow using Bookmarks. Bookmark is a pointer we create in the workflow execution. Bookmarks can be used for handling various events from Host application and also as data communication between Host and workflow. Bookmarks can be Non-blocking, None or Multi-Resume. Type of the Bookmark can be set using the BookmarkOptions enumerator. None: By default the bookmark will be of blocking type. When a bookmark encountered, the
execution of the workflow will go to the idle state. Workflow execution will wait for the resumption of one of the bookmarks. Non-blocking: Here, the execution of the workflow continues. Multi-Resume: This bookmark is of blocking type. We can resume the bookmark multiple times.
CanInduceIdle: Creation of Bookmark means, we are making the workflow idle. When your custom activity needs to make the workflow idle, we need to override the property CanInduceIdle and return true. By default the value of the CanInduceIdle is false. If we are not overriding this property, at runtime the custom activity will throw the exception.
private void HandleEvent(NativeActivityContext context, Bookmark bookmark, object obj) { if (obj != null) { Data.Set(context, obj.ToString()); } } }
Workflow
We defined the following workflow using our BookmarkSampleActivity. Specified the EventName as Complete and saved the output data to the Message variable. WriteLine activity will display the data from the Message variable.
Host Application
From the host application, we pass the data to the callback method using ResumeBookmak method. Here, we are passing a string data Hello from Host.
WorkflowApplication workflowApp = new WorkflowApplication(new TestWorkflow()); workflowApp.Run(); workflowApp.ResumeBookmark("Complete", "Hello from Host"); Console.ReadLine();
Result
Changed the Callback method to handle a Dictionary and add some value back to the dictionary.
private void HandleEvent(NativeActivityContext context, Bookmark bookmark, object obj) { if (obj != null) { Dictionary<string, object> values = obj as Dictionary<string, object>; values.Add("second", "Hello from Bookmark callback method."); } }
Host Application
workflowApp.Run(); Dictionary<string, object> Data = new Dictionary<string, object>(); Data.Add("first", "Hello from Host.");
workflowApp.ResumeBookmark("Complete", Data);
Once, it resumed, means the callback method executed, we will get the data back to the host application through the dictionary.
Result
But, this approach is having some issues. Once the workflow goes idle, then only the ResumeBookmark() method will get executed.
Dictionary<string, object> Data = new Dictionary<string, object>(); Data.Add("first", "Hello from Host."); workflowApp.Run(); workflowApp.ResumeBookmark("Complete", Data);
Change here is instead of looping through the Data dictionary, I am checking whether the dictionary is having the second value or not. As the checking is happening immediately after the ResumeBookmark, before the workflow went to idle mode, we wont get the data back to the Host application. The output will be empty. Here, we need to wait for the completion of the call back method before proceeding to the next statement. We can achieve it using one of the methods listed down.
1.Using AutoResetEvent
Using the AutoResetEvent, we can synchronize the execution of both the Host application and workflow application.
WorkflowApplication workflowApp = new WorkflowApplication(new TestWorkflow()); AutoResetEvent synchEvent = new AutoResetEvent(false); workflowApp.Completed = (WorkflowApplicationCompletedEventArgs) => { synchEvent.Set(); };
workflowApp.Run();
Dictionary<string, object> Data = new Dictionary<string, object>(); Data.Add("first", "Hello from Host."); workflowApp.ResumeBookmark("Complete", Data); synchEvent.WaitOne();
Create the AutoResetEvent and block the current thread after the ResumeBookmark. Once the workflow completes, signal the event. Here, we are receiving the data from the call back method after completion of the workflow.
2. Using delegate
Another way is to pass a delegate to the callback method and trigger it from the callback method.
Host Application
public delegate void BookmarkResumeDelegate(Dictionary<string, object> Data); class Program { static WorkflowApplication workflowApp; [STAThread] static void Main(string[] args) { workflowApp = new WorkflowApplication(new TestWorkflow()); workflowApp.Run();
BookmarkResumeDelegate myDelegate = new BookmarkResumeDelegate(BookmarkResumeHandler); Dictionary<string, object> Data = new Dictionary<string, object>(); Data.Add("first", "Hello from Host."); Data.Add("myDelegate", myDelegate); workflowApp.ResumeBookmark("Complete", Data); }
static void BookmarkResumeHandler(Dictionary<string, object> Data) { if (Data.ContainsKey("second")) { Console.WriteLine("second : " + Data["second"]); } Console.ReadLine(); }
Custom Activity
Changed the Callback method to handle the delegate send from the Host application
private void HandleEvent(NativeActivityContext context, Bookmark bookmark, object obj) { if (obj != null) { Dictionary<string, object> values = obj as Dictionary<string, object>; values.Add("second", "Hello from Bookmark callback method."); if (values.ContainsKey("myDelegate")) {
3. Using Event
Same as delegates, we can pass an event to the call back method and raise the same. Conclusion Here, we are discussed about the Bookmark used in custom activity development in Windows Workflow Foundation 4.0. Bookmarks are used for handling various events from Host application and for data communication between Host and workflow.
Part I : Introduction to Custom Activity Development Part II : Bookmark Part III : WorkflowDataContext
WorkflowDataContext
As specified in Part II, we can pass data from host application to custom activities using Bookmarks. Bookmarks are event driven mechanism. Whenever we define a bookmark, workflow will go for idle state and wait for the resumption of the bookmark. Here, we will discuss about another way of passing common data to all child activities defined in a workflow. If you want to pass some common data to all the activities defined in the workflow, we can use the WorkflowDataContext associated with the workflow. Custom Activity In Custom Activity, we can get the workflow level data using WorkflowDataContext. Properties associated with the WorkflowDataContext represent the workflow level Arguments and variables. In the following example, we extracted the properties from WorkflowDataContext and assigned to our OutArgument called DataFromHost.
public class CustomActivity:NativeActivity {
protected override void Execute(NativeActivityContext context) { string dataFromHost = string.Empty; WorkflowDataContext dataContext = context.DataContext; PropertyDescriptorCollection propertyDescriptorCollection = dataContext.GetProperties(); foreach (PropertyDescriptor propertyDesc in propertyDescriptorCollection) { dataFromHost += propertyDesc.Name + " : " + propertyDesc.GetValue(dataContext) + "\n"; }
DataFromHost.Set(context, dataFromHost); } }
Host Application Define a dictionary for passing data from host to workflow. Add key value pairs to the dictionary. The keys should be same as the InArguments defined in the workflow. In our case, it will be data1 and data2.
Dictionary<string, object> Data = new Dictionary<string, object>(); Data.Add("data1", "Hello from Host.");
Data.Add("data2", "Color.Blue");
Console.ReadLine();
Dictionary of data is specified along with the workflow object for creating the WorkflowApplication. Dictionary values are added to the WorkflowDataContext and this can be accessed by any child Activity using the context.DataContext. Result
You may be noted that, even though we passed only two values, data1 and data2, it displaying a third key called dataFromHost. As I specified, WorkflowDataContext contains all Arguments and Variables defined in workflow level. dataFromHost is the variable defined in the Workflow for handling output from our CustomActivity.
Conclusion
Here, we discussed about another way of passing data from host application to workflow. This method is used for passing common value to all child activities defined in the workflow.
Extension
Extension is a normal class with public methods and properties. Once we add the extension class to the workflow application, we can use the same in any child activities. For each instance of the workflow application will have one instance of its extension class. Using the context associated with the custom activity, we can access the extensions.
Sample Extension
For our sample, I am using a simple extension with one property and a Method. The dictionary property is used to pass data from host application to child activity. Same is used for communicating to another child activity.
public class SimpleExtension { private Dictionary<string, object> data; public Dictionary<string, object> Data { get { if (data == null) {
public void AddData(string key, object value) { if (string.IsNullOrEmpty(key)) return; Data.Add(key, value); } }
SimpleExtension extensionObj = context.GetExtension<SimpleExtension>(); if (extensionObj != null) { // extract the data added from host application
//Create the extension object SimpleExtension extensionObj = new SimpleExtension(); extensionObj.AddData("Host", " Host Application Data ");
//When the workflow complete, display the data from extension wfApp.Completed=delegate(WorkflowApplicationCompletedEventArgs a) { foreach (string key in extensionObj.Data.Keys) { Console.WriteLine("{0} : {1}", key, extensionObj.Data[key].ToString()); } };
Console.Read(); }
Result
From the result screen, you can observe the messages from both the custom activities and host application. Custom activity message is appended by the host application message.
Conclusion
Here, we discussed about the Extension used in custom activity development in Windows Workflow Foundation 4.0. Extensions are used for communication between the host and workflow, communication between the workflow and its child activities and communication between child activities.
Workflow Foundation 4.0 is introduced a significant amount of changes from the previous versions of the technology. One of the main features of workflow is Persistence. In this article we will be discussing about Persistence.
Persistence
Persistence means storing the current state of a workflow. For better understanding, we will consider a document processing scenario. In document processing, one user uploads the document and the workflow gets started. Then the document will go to the reviewer. Reviewer will review the document, either accepts it or reject with comments. In this scenario, reviewing of the document can happen in a different application or can be done after many months. When we deal with this kind of scenario, we will go for persistence. After uploading the document, the corresponding workflow state is stored in an InstanceStore. When the review starts, we can reload and continue execution of the workflow from the last executed state. In our scenario, the reviewer can use another application, which can reload the workflow from the InstanceStore and continue execution. Also, as we are storing the workflow to InstanceStore, we can manage the long running workflow effectively.
SqlWorkflowInstanceStoreSchema.sql SqlWorkflowInstanceStoreLogic.sql
You can find these two scripts under C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en We can create our own custom InstanceStore by inheriting from System.Runtime.DurableInstancing.InstanceStore class. We will discuss about the custom InstanceStore latter.
Note that, if you want to reload the workflow after sometime, we need to store the workflowId.
Guid persistId = app.Id;
In our document processing scenario, we can save the workflow Id as part of the assigned person Id and when the user login, reload the workflow. One more thing to note, Persist () will persists the current state of the workflow to the specified InstanceStore. It wont reload your workflow from the memory. If you have some critical data or state transition, call the Persist to save the current state to InstanceStore and continue execution. But, in our scenario, where the reviewer will login in a separate application or will login after one or two months, we can remove the workflow from memory. In this scenario, we need to call the Unload method instead of Persist.
app.Unload();
Unload will persists the current state of the workflow to InstanceStore and unload the workflow object from memory too.
Conclusion
Here, we discussed about the Persistence, one of the main feature of workflow which implements the concept of separating the When and How of the logic. We can use persistence to run a workflow across multiple channels and across multiple users.
WCF Integration
When we create a new project, under the workflow project templates, we have the option to select the WCF Workflow Service Application.
Once, we create a WCF Workflow Service application, it will create a default WCF integrated workflow. Note that the extension of our new workflow file is not .xaml, it is .xamlx. The default WCF workflow will look like
4. Click on the Define clause against the Content. The Content Definition window will open. Define your method parameters here. In our sample, I defined a variable called Name, which I am passing to the operation GetMessage.
5. Now right click on the Receive activity and select Create SendReply option.
This will create the corresponding SendReply activity. Also, workflow will add a new variable for correlating both Receive and Send activities and will link both the activities. SendReply will act as the method which sends the result to the caller. 6. Again, click on the Define clause next to the Content of SendReply activity and define the result. Here I specified a message concatenating my input Name.
7. Added one Assign activity in between the Receive and Send to manipulate our data. Now, our Custom workflow is ready.
8. Let us verify it using the WCF Test client. Here I passed the value Susan to Name parameter and observed the result as "Hello Mr/Ms. Susan". Mr/Ms. Clause is added to the Name by the Assign activity and the Hello is added by the Send activity.
Host Application
Now, let us see, how we can use the workflow in a host application. Add the WCF workflow reference as the service reference to the host application. We can create the service client object and call the operation just like any other WCF service.
static void Main(string[] args) { ServiceReference1.Service1Client obj = new ServiceReference1.Service1Client(); string name="Susan"; obj.GetMessage(ref name); Console.WriteLine(name);
Console.Read(); }
Result
Also, just like any other WCF service, we can browse the WCF workflow in browser.
Conclusion
Here, we discussed about the WCF integration with workflow foundation 4.0, how we can test a WCF workflow using WCF Test client and how we can call the WCF workflow from a host application.
We can either use the TypeConverter or PropertyValueEditor for managing the properties and associated editors in PropertyGrid. TypeConverters are same as those in WPF. PropertyValueEditors are similar to the PropertyEditors in WPF, which provides custom editor for our property. In this article we will discuss about the PropertyValueEditor.
PropertyValueEditor
WF4.0 contains mainly three types of editors defined under System.Activities.Presentation.PropertyEditing namespace.
1. PropertyValueEditor 2. DialogPropertyValueEditor 3. ExtendedPropertyValueEditor
For our sample and understanding, I am using the DialogPropertyValueEditor with Simple implementation. In another article we will look into how we can use the DialogPropertyValueEditor to show a dictionary of values and also will have a discussion on another type of editors.
Template
For defining any kind of Editors, we need one Template file which define the look and feel appear in the Propertygrid and the class file contains the editor logic. For our sample, I am using the following template with one textbox and an EditModeSwitchButton. We can define the DataTemplate in a ResourceDictionary file or as part of a UserControl.
<DataTemplate x:Key="FileBrowserInlineEditorTemplate"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" Text="{Binding StringValue}"/> <PropertyEditing:EditModeSwitchButton Grid.Column="1"/> </Grid>
</DataTemplate>
PropertyValueEditor
Next is the implementation of the Custom PropertValueEditor. Custom propertyValueEditor should be inherited from one of the above three classes. In our sample, we are inheriting our Custom PropertyValueEditor from the DialogPropertyValueEditor class. Specify the InlineEditorTemplate in the Constructor. In our example, FileEditorResources is the ResourceDictionary, where we defined the template for the PropertyValueEditor. Override the ShowDialog method and implement our custom PropertyValueEditor behavior. In our example, we are using the OpenFileDialog to select an XML file.
class FileBrowserDialogPropertyValueEditor : System.Activities.Presentation.PropertyEditing.DialogPropertyValueEditor { private FileEditorResources res = new FileEditorResources();
public override void ShowDialog(PropertyValue propertyValue, IInputElement commandSource) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "XML files (*.xml)|*.xml"; ofd.Title = "Select XML file"; ofd.Multiselect = false;
if (ofd.ShowDialog() == true) {
propertyValue.StringValue = ofd.FileName; } } }
Test Activity
Let us use our Custom PropertyValueEditor in our Custom Activity. Attach the Editor attribute with the custom activity property.
public class TestActivity:CodeActivity { private string myFile;
[Browsable(true)] [Editor(typeof(FileBrowserDialogPropertyValueEditor), typeof(DialogPropertyValueEditor))] public string MyFile { get { return myFile; } set { myFile = value; }
{ MessageBox.Show(MyFile); } }
Result
Drag and drop the custom activity to workflow and observe the properties associated with it. Our MyFile property will appear with a textbox & Selection button. Once we click on the button, it will open the File Open dialog with title as Select XML file and filtered by XML files.
Conclusion
Window workflow foundation 4.0 is having full support for custom activity development. In custom activity development, we are dealing with various kinds of properties. A good Property value editor will increase the user friendliness of the custom activity. Here, we discussed about a simple custom property value editor. We will look more on the editors and type converters in another article.
Introduction
We already discussed about Persistence and Extensions in workflow foundation 4.0. Persistence is the way of storing the current state of a workflow into an instance store. Extensions are special classes for sharing data among child activities, workflow and Host application. In case of normal Extension, the data shared using the Extension wont be stored to the instance store as part of the Persistence. In real business scenario, we may need to store the Extension data as part of the workflow state. For this purpose, workflow foundation 4.0 is providing a special kind of Extension. In this article we will discuss about this special extension and how we can use the same.
PersistenceParticipant Extension
This special extension is a class which inherited from the System.Activities.Persistence.PersistenceParticipant base class. For implementing the PersistenceParticipant, we need to override two methods CollectValues and PublishValues. Here, for our sample we have small Persistence Participant extension which stores a collection of strings in an Items list. Same way we have a normal extension class to store the data in an Items list.
Normal Extension
For more details on Extension, please refer Workflow Foundation 4.0 - Extension
public class NormalExtension { private List<object> items = new List<object>();
public List<object> Items { get { if (items == null) { items = new List<object>(); } return items; } } }
PersistenceParticipant Extension
As the Items list is a read write property, in CollectValues method, we need to assign the Item value to readWriteValues and assign null to writeOnlyValues. In PublishValues method, extract the Items value from readwriteValues dictionary and assign to Items property.
public class MyPersistenceParticipant:PersistenceParticipant { static XNamespace myNamespace = XNamespace.Get("MyApp"); static XName currentItems = myNamespace.GetName("Items");
protected override void CollectValues(out IDictionary<XName, object> readWriteValues, out IDictionary<XName, object> writeOnlyValues) { readWriteValues = new Dictionary<XName, object>(1) { { currentItems, this.items } }; writeOnlyValues = null; }
protected override void PublishValues(IDictionary<XName, object> readWriteValues) { object loadedData; if (readWriteValues.TryGetValue(currentItems, out loadedData)) { items = (List<object>)loadedData; } }
Workflow
For understanding the difference between the normal extension and Persistence participant, we have a small workflow.
Here Close_Bookmark is a custom activity to create bookmark with name Close. For more details on Bookmark, please refer Workflow Foundation 4.0 Bookmark.
Host application
For verifying our Persistence Participant extension, we will use the following Host application code. Create an Instance of the workflow application and set the Instance Store. After that, add the objects of Normal as well as PersistenceParticipant extensions with one Item added to the Items property to the Extension collection of our workflow application. For our sample, we need to unload the instance completely from memory, so called the Unload method instead of Persist method. This will store the workflow state to instance store and remove the workflow application object from memory. Now, for reloading the workflow from instance store, create the workflow application object, setup instance store and add extension objects to the workflow application object. After reloading, loop through the Items collection of both normal extension and PersistenceParticipant extension and display the items.
static void Main(string[] args) { WorkflowApplication wfApp = new WorkflowApplication(new Workflow1()); InstanceStore store = new SqlWorkflowInstanceStore(@"Data Source=.;Initial Catalog=SqlWorkflowInstanceStore;Integrated Security=True");
wfApp.InstanceStore = store;
wfApp.Extensions.Add(normalObj); wfApp.Extensions.Add(obj);
wfApp.Unload();
wfApp1.Extensions.Add(normalObjAfter); wfApp1.Extensions.Add(objAfter);
wfApp1.Load(persistId);
{ Console.WriteLine(s); }
wfApp1.ResumeBookmark("Close", null);
Console.Read(); }
Result
From the result screen, we can observe that the data added to the PersistenceParticipant extension before Persist operation persisted as part of workflow state information. And the data added to the normal extension before Persist operation is not available after reloading of the workflow.
Conclusion
PersistenceParticipant is a special extension for storing additional data along with the workflow state to the Instance store on Persisting a workflow. We can use normal extension for sharing data across different activities, workflow and host application. If the data needs to be persisted along with workflow state, then use the PersistenceParticipant Extension.
Introduction
Workflow Foundation 4.0 is introduced a significant amount of change in the Activity Designer. In WF4.0, Activity Designers are defined using WPF. Property management and display is one of the important aspects of a custom Activity Designer. We can use the PropertyValueEditor for providing meaningful editor for the Property. Also, we can use the TypeConverters for providing the expected values of a property.
Wf4.0 is using the WPF Type Converters. In this article we will discuss about the WPF TypeConverter and how we can use the same in WF4.0 custom activity.
Type Converter
Type Converters are used to pre-populate the expected values of a Property. We can either allow the user to edit new values or can restrict the user to only select from the given values using Type Converter. Let us look into a simple Type converter to populate the WorkingDay property. Our Type Converter is inherited from StringConverter and we override three methods. As we override the GetStandardValuesExclusive() method and return true, this Type Converter restrict the user only to select values from the list. If it return false or not overridden, then it display the list and also allow the user to enter another value.
Type Converter
public class WorkingDayConverter : StringConverter { private static StandardValuesCollection svc;
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { ArrayList values = new ArrayList(); values.Add("Monday");
MessageBox.Show(WorkingDay); } }
{ return true; } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { ModelService modelService = (ModelService)context.GetService(typeof(ModelService)); IEnumerable<ModelItem> activityCollection = modelService.Find(modelService.Root, typeof(WriteLine));
ArrayList writeLines = new ArrayList(); foreach (ModelItem shv in activityCollection) { writeLines.Add((shv.GetCurrentValue() as WriteLine).DisplayName); } return new StandardValuesCollection(writeLines); } }
this.wrLine = value; } }
Workflow Designer
Conclusion
WF4.0 Activity designers are defined using WPF. WPF integration gives full support for customization of our custom designer. In this article we discussed about the Type Converters used as part of custom properties in Activity Designer.
Workflow Foundation 4.0 has very good support for custom activity development. One of the areas in custom activity development is validating the properties and its values in design time. In this article we will discuss about various validation options available with workflow foundation 4.0.
Argument Validation
As we discussed earlier, the data model in WF4 is based on InArguments, OutArguments and InOutArguments. Here, we will discuss about the various ways to do the argument validation. RequiredArgument If the InArgument is a required argument, decorate the same with RequiredArgument attribute. Custom Activity
public class CustomActivity:NativeActivity { [RequiredArgument()] public InArgument<string> InputData { get; set; }
Observe the design time validation error on the activity as well as in error window.
Note: Validation errors are not compilation errors. Project with validation errors will compile successfully. OverloadGroup When you want to group the arguments use the OverloadGroup attribute. This will group the attributes and only one group of attribute should be valid. If you specify value for attributes belonging to two groups, it will throw validation error. Custom Activity
public class CustomActivity:NativeActivity {
set; }
Now, we specify the values for InputData and Data arguments, which belong to two groups. Observe the validation error.
Property Validation
Even though the WF4 data model is based on arguments, sometimes we may need to use properties. Property validations are mainly done using the CacheMetadata method. Override the cacheMetadata method and perform the validation Custom Activity
public class CustomActivity:NativeActivity { public string Data { get; set;
We can add any kind of validations inside CacheMetadata. In our example, verified whether the property value is not null or empty. If it is empty, throw the validation error.
Conclusion
WF4.0 has various methods for performing the design time validation for arguments and properties.