WF 4.0: Long Running Custom Activities with Bookmarks and NativeActivity

WF 4.0: Long Running Custom Activities Bookmarks NativeActivity

This is another post in my WF 4.0: Custom Activities series. In a previous post I have talked about creating a code only custom activity that performs a simple task. In this post I’ll talk about creating a more complex activities that takes inputs from the calling program during their execution.

In the previous post WF 4.0: Code Only Custom Activities for Atomic Actions | CodeActivity, CodeActivity<T> I implemented the following activity for reading a string from the console.

public class ReadString2 : CodeActivity<string>

{

  protected override void Execute(CodeActivityContext context)

  {

    string value = Console.ReadLine();

    context.SetValue(Result, value);

  }

}

While this activity will perform the task it is meant to do, it is blocking the calling thread and bad for scalability. In addition to that It is bound to getting the value from the console only, and it is not generic enough to support another input methods.

Enter Bookmarks.

Using NativeActivity for Complex Activities

To create a long running activity, add a new item of type Workflow Element item to the project. Notice that the default activity inherits from CodeActivity.

public class ReadString3 : CodeActivity

{

  protected override void Execute(CodeActivityContext context)

  {

  }

}

To create complex activities such as long running activities, me need to change the base class and inherit from NativeActivity or NativeActivity<T>. In addition to that, there is a difference between the type of the parameter that the CodeActivity takes for the Execute method and the type that NativeActivity takes. Change the input parameter type from CodeActivityContext to ActivityExecutionContext.

public class ReadString3 : NativeActivity

{

  protected override void Execute(ActivityExecutionContext context)

  {

    throw new NotImplementedException();

  }

}

Enter Bookmarks

In previous versions of Windows Workflow Foundation we had the ExternalDataExchangeService and WorkflowQueue when we wanted to build a simple long running activity. In WF 4.0 this is much more simple to do using Bookmarks.

To use bookmarks we need to:

  • Create a named bookmark and Implement a callback method
  • Signal from the caller program

Register a Named Bookmak

The following activity registers a bookmark called “input” with a callback method called OnBookmarkCallback.

public class ReadString3 : NativeActivity

{

  OutArgument<string> name;

  public OutArgument<string> Name

  {

    get { return this.name; }

    set

    {

      ThrowIfOpen();

      this.name = value;

    }

  }

 

  protected override void Execute(ActivityExecutionContext context)

  {

    context.CreateNamedBookmark("input", new BookmarkCallback(this.OnBookmarkCallback));

  }

 

  void OnBookmarkCallback(ActivityExecutionContext context, Bookmark bookmark, object obj)

  {

    this.Name.Set(context, (string)obj);

  }

}

Signal the workflow from the caller program

In a previous post I have talked about the code that is needed to execute a workflow. Since we are now talking about long running workflows we will start with the default code that is generated for us when we create a new sequential workflow console application that uses the WorkflowInstance that lets know when the workflow instance is idle, and to resume a bookmark:

...

myInstance.Run();

 

//Get a string from the console and resume the bookmark called “input”

string input = Console.ReadLine();

myInstance.ResumeBookmark("input", input);

 

syncEvent.WaitOne();
...

The main program performs the input and delivers the value to the workflow. Using this approach:

  • The main program is flexible in how it wants to get values from the user and any other input source
  • The workflow becomes idle when it is waiting for the input and during that time not consuming any resources from the machine.

To query the available bookmarks in a workflow instance, we can use the following code:

IList bookmarks = myInstance.GetAllBookmarks();

foreach (BookmarkInfo info in bookmarks)

{

  Console.Write(info.BookmarkName);

}

Enjoy!