Jul 7, 2009

WF runtime

 public class WorkflowRuntime
  {
    public WorkflowRuntime();

    public void AddService(object service);
    public void RemoveService(object service);

    public void StartRuntime();
    public void StopRuntime();

    public WorkflowInstance CreateWorkflow(XmlReader reader);
    public WorkflowInstance GetWorkflow(Guid instanceId);

    /* *** other members *** */
  }

 public sealed class WorkflowInstance
  {
    public Guid InstanceId { get; }

    public void Start();
    public void Load();
    public void Unload();

    public void EnqueueItem(IComparable queueName,
      object item, IPendingWork pendingWork, object workItem);

    /* *** other members *** */
  }

 class Program
  {
    static void Main()
    {
     using(WorkflowRuntime runtime = new WorkflowRuntime())
     {
       TypeProvider typeProvider = new TypeProvider(runtime);
       typeProvider.AddAssemblyReference("EssentialWF.dll");
       runtime.AddService(typeProvider);
       runtime.StartRuntime();

       WorkflowInstance instance = null;
       using (XmlTextReader reader =
         new XmlTextReader("OpenSesame.xoml"))
       {
         instance = runtime.CreateWorkflow(reader);
         instance.Start();
       }

       string s = Console.ReadLine();
       instance.EnqueueItem("r1", s, null, null);

       // Prevent Main from exiting before
       // the WF program instance completes
       Console.ReadLine();

       runtime.StopRuntime();
     }
    }
  }

When the Start method is called on the WorkflowInstance, the WF runtime runs the WF program asynchronously. But other threading models are supported by the WF runtime. When a ReadLine activity executes, it creates a WF program queue. When our console application (which is playing the role of a listener) reads a string from the console, it resumes the execution of the bookmark established by the ReadLine by enqueuing the string. The name of the WF program queue is the same name, "r1", that we gave to the ReadLine activity (per the execution logic of ReadLine).

In order to illustrate the mechanics of passivation, we can write two different console applications. The first one begins the execution of an instance of the Open, Sesame program.

  class FirstProgram
  {
    static string ConnectionString =
      "Initial Catalog=SqlPersistenceService;Data
       Source=localhost;Integrated Security=SSPI;";
    static void Main()
    {
      using (WorkflowRuntime runtime = new WorkflowRuntime())
      {
        SqlWorkflowPersistenceService persistenceService =
          new SqlWorkflowPersistenceService(ConnectionString);
        runtime.AddService(persistenceService);

        TypeProvider typeProvider = new TypeProvider(runtime);
        typeProvider.AddAssemblyReference("EssentialWF.dll");
        runtime.AddService(typeProvider);

        runtime.StartRuntime();

        WorkflowInstance instance = null;
        using (XmlTextReader reader =
          new XmlTextReader("OpenSesame.xoml"))
        {
          instance = runtime.CreateWorkflow(reader);
          instance.Start();
        }

        Guid durableHandle = instance.InstanceId;

        // save the Guid...
        instance.Unload();

        runtime.StopRuntime();
      }
    }
  }

The WF program instance never completes because it is expecting to receive a string after it prints the key, and we do not provide it with any input. When the WorkflowInstance.Unload method is called,[2] the instance is passivated. Inspection of the SQL Server database table that holds passivated WF program instances will show us a row representing the idle Open, Sesame program instance.

In order to resume the passivated instance in another CLR application domain, we need to have some way of identifying the instance. That is precisely the purpose of the InstanceId property of WorkflowInstance. This globally unique identifier can be saved and then later passed as a parameter to the WorkflowRuntime.GetWorkflow method in order to obtain a fresh WorkflowInstance for the WF program instance carrying that identifier.

 class SecondProgram
  {
    static string ConnectionString =
      "Initial Catalog=SqlPersistenceService;Data
       Source=localhost;Integrated Security=SSPI;";

    static void Main()
    {
      using (WorkflowRuntime runtime = new WorkflowRuntime())
      {
        SqlWorkflowPersistenceService persistenceService =
          new SqlWorkflowPersistenceService(ConnectionString);
        runtime.AddService(persistenceService);

        TypeProvider typeProvider = new TypeProvider(runtime);
        typeProvider.AddAssemblyReference("EssentialWF.dll");
        runtime.AddService(typeProvider);

        runtime.StartRuntime();

        // get the identifier we had saved
        Guid id = "saveed from first program";
        WorkflowInstance instance = runtime.GetWorkflow(id);

        // user must enter the key that was printed
        // during the execution of the first part of
        // the Open, Sesame program
        string s = Console.ReadLine();
        instance.EnqueueItem("r1", s, null, null);

        // Prevent Main from exiting before
        // the WF program instance completes
        Console.ReadLine();

        runtime.StopRuntime();
      }
    }
  }

The passivated (bookmarked) WF program instance picks up where it left off, and writes its result to the console after we provide the second string.