Workflow Foundation 4.0 is introduced a significant amount of change from the previous versions of the technology. This article will be the second in a series of articles. In this article we will be discussing about the Bookmark and related topics used in Custom Activity Development.
Part I : Introduction to Custom Activity Development
Part II : Bookmark
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.
How to Create a Bookmark?
When you create a bookmark, the custom activity should be derived from the NativeActivity. As I specified in Part I of the article, when we are processing the workflow runtime, we need to use the NativeActivity. Following custom activity will create a “Next” bookmark.
public class BookmarkSampleActivity:NativeActivity
{
protected override bool CanInduceIdle
{
get
{
return true;
}
}
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark("Next");
}
}
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.
How to Resume the Bookmark?
When the workflow goes idle, we need to resume the bookmark to restart the execution.
WorkflowApplication workflowApp = new WorkflowApplication(new TestWorkflow());
workflowApp.Run();
workflowApp.ResumeBookmark("Next", null);
How to pass data to the Bookmark?
For passing data to the bookmark, define the BookmarkCallBack method, which will get executed when the ResumeBookmark method executes.
Custom Activity Code
There are few modification in our custom activity code.
-
Added an InArgument as EventName. This value is used for creating the Bookmark. As the value is passed to the activity, we can use the same activity to create different bookmarks.
-
Added an OutArgument called Data. OutArgument is used to pass data out of the activity. In this case we are setting the OutArgument with the data passed from the host application to the BookmarkCallBack method.
-
Added the BookmarkCallbackMethod called HandleEvent. When the host application calls the ResumeBookmark, BookmarkCallbackMethod will get executed. Inside the callback method, we are getting the value from the host application as the last parameter.
public class BookmarkSampleActivity:NativeActivity
{
protected override bool CanInduceIdle
{
get
{
return true;
}
}
public InArgument<string> EventName
{ get; set; }
public OutArgument<string> Data
{ get; set; }
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(EventName.Get(context), new BookmarkCallback(HandleEvent));
}
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
How to get data from activity after ResumeBookmark?
We can get the data back to the host using the same BookmarkCallback method.
Custom Activity
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
From host, we are passing the dictionary to the callback method.
WorkflowApplication workflowApp = new WorkflowApplication(new TestWorkflow());
workflowApp.Run();
Dictionary<string, object> Data = new Dictionary<string, object>();
Data.Add("first", "Hello from Host.");
workflowApp.ResumeBookmark("Complete", Data);
foreach (string key in Data.Keys)
{
Console.WriteLine(key + " : " + Data[key]);
}
Console.ReadLine();
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.
Consider the above scenario with following Host application code
WorkflowApplication workflowApp = new WorkflowApplication(new TestWorkflow());
Dictionary<string, object> Data = new Dictionary<string, object>();
Data.Add("first", "Hello from Host.");
workflowApp.Run();
workflowApp.ResumeBookmark("Complete", Data);
if (Data.ContainsKey("second"))
{
Console.WriteLine("second : " + Data["second"]);
}
Console.ReadLine();
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 won’t 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();
if (Data.ContainsKey("second"))
{
Console.WriteLine("second : " + Data["second"]);
}
Console.ReadLine();
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"))
{
BookmarkResumeDelegate myDelegate = values["myDelegate"] as BookmarkResumeDelegate;
myDelegate(values);
}
}
}
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.