Advanced Workflow Services Talk (Demo 1 of 4)

So, last week I wrapped up a conversation at TechReady, our internal conference, where I was talking about the integration between WF and WCF in .NET 3.5.  This talk was somewhat bittersweet, it's the last conference where I'm scheduled to talk about WF 3.0/3.5, I'll start talking about WF 4.0 at PDC this fall. 

There are a series of 4 demos that we'll talk about in this series:

  1. Basic Context Management
  2. Simple Duplex
  3. Long Running Work Pattern
  4. Conversations Pattern

I've gotten a lot of requests to post the code samples, so I want to do that here:

Sample 1, Basic Management of Context

The goal of this sample is to show the way that the context channel works, and how to interact with it from imperative code.

Ingredients:

  • One basic workflow service that simply has two Receive activities bound to the same operation inside of a sequence.
  • image
  • Inside each Receive, I have placed a Code Activity that simply outputs a little bit of info (the vars declared on lines 1 and 2 are used by the Receive activities:

 

  •     1:  public String returnValue = default(System.String);
    
        2:  public String inputMessage = default(System.String);
    
        3:   
    
        4:  private void codeActivity1_ExecuteCode(object sender, EventArgs e)
    
        5:  {
    
        6:      returnValue = string.Format("first activity {0}", inputMessage);
    
        7:      Output(inputMessage + " Activity 1");
    
        8:  }
    
        9:   
    
       10:  private void Output(string message)
    
       11:  {
    
       12:      Console.WriteLine("Workflow {0} : Message {1}", this.WorkflowInstanceId, message);
    
       13:  }
    
       14:   
    
       15:  private void codeActivity2_ExecuteCode(object sender, EventArgs e)
    
       16:  {
    
       17:      returnValue = string.Format("second activity {0}", inputMessage);
    
       18:      Output(inputMessage + " Activity 2");
    
       19:  }
    

 

Instructions:

  • Create a client type that will call the service for us

     class IWorkflowClient : ClientBase<Intro1.IWorkflow1>, Intro1.IWorkflow1
    {
        public IWorkflowClient() : base() { }
        public IWorkflowClient(Binding binding, EndpointAddress address) : base(binding, address) { }
        public string Hello(string message)
        {
            return base.Channel.Hello(message);
        }
    }
    
  • Create a utility function CheckAndPrintContext()

     private static void CheckAndPrintContext(IContextManager icm)
    {
        if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
        if (null != icm)
        {
            if (icm.GetContext().Count > 0)
            {
                foreach (string xmlName in icm.GetContext().Keys)
                {
                    Console.WriteLine("key : {0}", xmlName);
                    Console.WriteLine("value : {0}", icm.GetContext()[xmlName]);
                }
            }
        }
    }
    
    • The thing to note here is that we need to traverse the dictionary, since there could be more than one key in here, although there won't be in this sample.
  • Now, let's run the three different bits of code, we want to first show the happy path, show how to break it, and then show how to explicitly manage the context token

  • Scenario 1: The Happy Path

        1:  private static void DemoOne()
    
        2:  {
    
        3:      Console.WriteLine("Press Enter to Send a Message and reuse proxy");
    
        4:      // Console.ReadLine();
    
        5:      Debugger.Break();
    
        6:      IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
    
        7:          new EndpointAddress("net.tcp://localhost:10001/Intro1"));
    
        8:      IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
        9:      if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
    
       10:      string s = iwc.Hello("message1");
    
       11:      Console.WriteLine("the service returned the message '{0}'", s);
    
       12:      CheckAndPrintContext(icm);
    
       13:      s = iwc.Hello("message2");
    
       14:      icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       15:      CheckAndPrintContext(icm);
    
       16:      Console.WriteLine("the service returned the message '{0}'", s);
    
       17:      Console.WriteLine("Press Enter to Continue");
    
       18:  }
    
    • What's going on here?
      • Line 5, a more convenient way in demos to hit a breakpoint
      • Line 10: Call the service
      • Line 12: CheckAndPrint the Context Token.  In this case, this will print the Guid of the initiated workflow that is contained in the token
      • Line 13: Call the service a second time
        • Look at the service window, you'll see that this message has been routed to the same instance of the workflow.
        • You can also see in Line 16 that the second activities return message is included.
  • Scenario 2: The Path Grows Darker

        1:  // show this not working using a second client
    
        2:  private static void DemoTwo()
    
        3:  {
    
        4:      Console.WriteLine("Press Enter to Send a Message (it will break this time)");
    
        5:      //Console.ReadLine();
    
        6:      Debugger.Break();
    
        7:      IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
    
        8:          new EndpointAddress("net.tcp://localhost:10001/Intro1"));
    
        9:      IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       10:      if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
    
       11:      string s = iwc.Hello("message1");
    
       12:      Console.WriteLine("the service returned the message '{0}'", s);
    
       13:      CheckAndPrintContext(icm);
    
       14:      iwc = new IWorkflowClient(new NetTcpContextBinding(),
    
       15:         new EndpointAddress("net.tcp://localhost:10001/Intro1"));
    
       16:      s = iwc.Hello("message2");
    
       17:      Console.WriteLine("the service returned the message '{0}'", s);
    
       18:      icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       19:      CheckAndPrintContext(icm);
    
       20:      Console.WriteLine("Press Enter to Continue");
    
       21:  }
    
    • What's going on here? (Same until line 14)
      • Line 14: Let's create a new proxy. 
      • Line 15: Call the service using the new proxy.  You'll note on the server side that a second workflow instance has been created.  This is where we break.
      • Line 19: On the client side, you'll see that the second GUID being returned
  • Scenario 3: Finding the Light

        1:  // show this working with a second client by caching the context
    
        2:  private static void DemoThree()
    
        3:  {
    
        4:      Console.WriteLine("Press Enter to Send a Message (we'll cache the context and apply it to the new proxy)");
    
        5:      // Console.ReadLine();
    
        6:      Debugger.Break();
    
        7:      IWorkflowClient iwc = new IWorkflowClient(new NetTcpContextBinding(),
    
        8:          new EndpointAddress("net.tcp://localhost:10001/Intro1"));
    
        9:      IContextManager icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       10:      if (null != icm) Console.WriteLine("Context contains {0} elements", icm.GetContext().Count);
    
       11:      string s = iwc.Hello("message1");
    
       12:      Console.WriteLine("the service returned the message '{0}'", s);
    
       13:      CheckAndPrintContext(icm);
    
       14:      IDictionary<string, string> context = icm.GetContext();
    
       15:      icm = null;
    
       16:      iwc = new IWorkflowClient(new NetTcpContextBinding(),
    
       17:         new EndpointAddress("net.tcp://localhost:10001/Intro1"));
    
       18:      icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       19:      icm.SetContext(context);
    
       20:      s = iwc.Hello("message2");
    
       21:      Console.WriteLine("the service returned the message '{0}'", s);
    
       22:      icm = iwc.InnerChannel.GetProperty<IContextManager>();
    
       23:      CheckAndPrintContext(icm);
    
       24:      Console.WriteLine("Press Enter to Exit");
    
       25:   
    
       26:  }
    
    • Line 14 is where the magic happens, here' we grab the context token from the IContextManager. 
    • Line 19 is where the magic completes, we apply this token to the new proxy.  Note, this proxy could be running on different machine somewhere, but one I get the context token, I can use it to communicate with the same workflow instance that the first call did.

So, what have we shown:

  • Manipulating context in workflow and imperative code

    • How to extract the context token
    • How to explicitly set the context token
  • The caching behavior of the context channel (as seen in Scenario 1)

  • The behavior of the context channel to return the context token only on the activating message