• mwinkle.blog

    More Workflow Service Questions

    • 1 Comments

    In response to this post, Anderson raised the following question.

    Definitely too hard and not service-oriented.  I like the idea of sending it via the headers or some other such implementation, but that would also require implementation on the other side in the form of an agreed-upon place and format of the context information or a custom binding implementation that you either distribute or have the other side of the fence reimplement.

    The first time I did this I spent days working on it saying to myself "surely you don't have to do this... surely you don't have to tell the target service which *activity* you want it to talk to next".  But you do.  It makes me sad inside.

    matt:  Our general goal is not to introduce more misery into the world.  At this point in time, doing duplex requires the explicit management of the context token.  We're working to make it better, so hopefully around PDC time, you will no longer be sad inside. 

    One thing I've not seen implemented yet in a demo.  I really like the feature of State Machine super states and substates.  The ability to take a group of 3 states and put them in another state called "Cancellable" or some other such interrupty business function is really kickass.

    Let's say you are in state "Processing" which has an event-driven that listens for the service method "FinishedProcessing".  "Processing" is also in a superstate called "Cancellable" with an event-driven that listens for "CancelProcess".  How could you implement this when you have to tell the other service about the context identifier of the target receive activity?  Do you have to send both contexts?  I fear your answer will be yes.

    matt:  In that case, provided you are not listening for the same operation in two places, you can get away with grabbing the context token from either FinishedProcessing or CancelProcess and send that along.  The context token will be the same fore either of them, it will only consist of the Workflow Instance Id.  The Instance Id + the OperationName is the combination used to enqueue an inbound message, unless you've gotten clever as in the Conversations sample and told the Receive activities to create queues based on an additional conversation id. 

     

    Also, it sounds like Anderson is delivering a talk tomorrow night in Dallas about WCF /WF integration.  If you're in the area, head on over and check it out.

  • mwinkle.blog

    Advanced Workflow Service Talk (Demo 4 of 4)

    • 3 Comments

    When we start doing this two way style of messaging, we now open up to start modeling some interesting business problems.  In the previous post, you'll note that I did not include the code, because I mentioned we needed to be more clever in scenarios where we listen in parallel. 

    First, a brief diversion into how the Receive activity works.  Everybody remembers the workflow queues, the technology that underlies all communication between a host and a workflow instance.  The Receive activity works by creating a queue that the WorkflowServiceHost (specifically the WorkflowOperationInvoker) will use to send the message received off the wire into the workflow.  Now, the Receive activity normally just creates a queue that is named the same as the operation the Receive activity is bound to.  However, if we have two Receive activities listening for the same operation at the same time, no longer is a single queue useful to route responses back as we want to route to the correct Receive activity instance. 

    There is property on the Receive activity called ContextToken.  Normally this is null in the simple case.  However, when we want our Receive activity to operate in parallel, we need to indicate that it needs to be smarter when it creates a queue. 

    image

    By setting this property (you can just type in a name, and then select the common owner all of the parallel receive's share.  This will cause the Receive activity to create a queue named [OperationName] +[ConversationId], the conversation ID takes the form of a GUID, and is the second element inside a context token. 

    The sample that I show for this talk is simply the conversations sample inside the SDK.  This is the sample to check out to understand all sorts of interesting ways to use the context tokens to model your processes.

     

    Conversations Sample Architecture

     

    Now, there are two conversation patterns here.  One is the one shown above, which I refer to as an n-party conversation where n is fixed at design time.  We can accomplish this with the parallel activity.  The other is where n is arbitrary (imagine you send out to business partners stored in the database).  The way to do this is to use the Replicator activity.  The Replicator is a little known gem shipped in 3.0 that essentially gives you "ForEach" semantics.  But, by flipping the ExecutionType switch to parallel, I now get the behavior of a parallel, but operating with an arbitrary n branches.

    So, in order to enable conversations, we need to tell our receive activity to be a little smarter about how it generates its queue name, and then we simply follow the duplex pattern we discussed in the last two posts.  Once we do that, we're in good shape to start modeling some more interesting communication patterns between multiple parties. 

    Where can we go from here?


    We can just make the patterns more interesting.  One interesting one would be the combination of the long running work with cancellation and a Voting activity in order to coordinate the responses and allow for progress to be made when some of the branches complete (if I have 3 yes votes, I can proceed).  The power of building composite activities is that it gives me a uniform programming model (and a single threaded one to boot) in order to handle the coordination of different units of work.  Get out there and write some workflows :-)

  • mwinkle.blog

    Advanced Workflow Services Talk (Demo 3 of 4)

    • 2 Comments

    So, we've seen in part 1 how to manage context, we saw in part 2 how we can take that basic knowledge to do duplex messaging.  Once we start doing duplex work, there are some interesting patterns, and the first one is one that we like to call "long running work".  Why are we interested in this?  Well, as you probably know, the execution of a workflow is single threaded (this is a feature, not a bug).  We also don't have a mechanism to force the workflow to be "pinned" in memory.  What this means is that things like the asynchronous programming model  (APM), can't be used, since there isn't a guarantee that there will be something to call back when we are done.  What this means is that the send activity can not take advantage of the APM to be more thread friendly.

    We may want to do things in parallel, like this

    image

    If each of these branches takes 3 seconds, the whole of this workflow will complete in about 9 seconds.  The general expectation is that in parallel, this would happen at the length of the longest branch + some minor delta for overhead.  The trouble is, APM programming is tricky, especially relative to the layout above.

    In order to model APM style service calls, but allowing for the service operations to be extremely long running, where extremely is defined as "long enough to where I would want to be able to persist."  The approach then is to model this as disjoint send and receive activities.

    image

    One intermediate step is to simply use one way messaging, but the problem there is that in a lot of cases, I'm looking for some information being sent back to me. 

    I'll hold off on the code for the above, the fact we are listening in parallel for the same operation requires us to be a little more clever.

    Let's look first at our contract, and then our service implementation:

       1:  namespace Long_Running_Work
       2:  {
       3:      [ServiceContract]
       4:      public interface ILongRunningWork
       5:      {
       6:          [OperationContract]
       7:          string TakeAWhile(int i);
       8:   
       9:          [OperationContract(IsOneWay = true)]
      10:          void OneWayTakeAWhile( int i);
      11:          
      12:          [OperationContract(IsOneWay = true)]
      13:          void TakeAWhileAndTellMeLater(IDictionary<string,string> contextToken, int i);
      14:      }
      15:   
      16:   
      17:      [ServiceContract]
      18:      public interface IReverseContract
      19:      {
      20:          [OperationContract(IsOneWay = true)]
      21:          void TakeAWhileAndTellMeLaterDone(string s);
      22:      }
      23:     
      24:  }

    And now for the implementation of these;

       1:  namespace Long_Running_Work
       2:  {
       3:     public class Service1 : ILongRunningWork
       4:      {
       5:   
       6:          public Service1()
       7:          {
       8:             
       9:          }
      10:   
      11:          #region ILongRunningWork Members
      12:   
      13:          public string TakeAWhile(int i)
      14:          {
      15:              Console.WriteLine("Starting TakeAWhile");
      16:              System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
      17:              return i.ToString();
      18:          }
      19:   
      20:   
      21:   
      22:          public void OneWayTakeAWhile( int i)
      23:          {
      24:              Console.WriteLine("Starting One Way TakeAWhile");
      25:              System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
      26:              Console.WriteLine("Ending One Way TakeAWhile");
      27:   
      28:   
      29:          }
      30:   
      31:   
      32:          public void TakeAWhileAndTellMeLater(IDictionary<string, string> context, int i)
      33:          {
      34:              Console.WriteLine("Received the context Token");
      35:              System.Threading.Thread.Sleep(new TimeSpan(0, 0, 3));
      36:              Console.WriteLine("Need to Message Back Now {0}", i.ToString());
      37:              // could investigate a more useful pooling of these if we 
      38:              // really wanted to worry about perf
      39:              IReverseContractClient ircc = new IReverseContractClient(
      40:                  new NetTcpContextBinding(),
      41:                  new EndpointAddress("net.tcp://localhost:10003/ReverseContract")
      42:                  );
      43:              IContextManager icm = ircc.InnerChannel.GetProperty<IContextManager>();
      44:              icm.SetContext(context);
      45:              ircc.TakeAWhileAndTellMeLaterDone(i.ToString());
      46:          }
      47:   
      48:   
      49:   
      50:          #endregion
      51:      } 
      52:   
      53:     public class IReverseContractClient : ClientBase<IReverseContract>, IReverseContract
      54:     {
      55:          public IReverseContractClient() : base(){}
      56:          public IReverseContractClient(System.ServiceModel.Channels.Binding binding, EndpointAddress address) : base(binding, address) { }
      57:   
      58:  #region IReverseContract Members
      59:   
      60:   
      61:   
      62:         public void TakeAWhileAndTellMeLaterDone(string s)
      63:         {
      64:             base.Channel.TakeAWhileAndTellMeLaterDone(s);
      65:         }
      66:   
      67:         #endregion
      68:     }
      69:   
      70:  }

    Basically, we sit around and wait.  You'll also note in the TakeAWhileAndTellMeLater, we take in a context token (similar to our previous approach), and we will use that to new up a client at the end and call back in after setting the context.  Look at lines 39-44 above.  The nice thing about this is that my above workflow client can actually go idle, persist, and react to a message being delivered later on.

    One thing to note is that one should not place a delay between any of the Send and Receives.  This could cause the workflow to go idle, which may allow you to miss messages.  This is generally considered, a bad thing.  The reason this occurs is that the WorkflowOperationInvoker will use EnqueueOnIdle which means that when teh workflow goes idle, the message will be enqueued.  If the queue hasn't been created by the Receive activity, the message will not get delivered.

    For the final workflow above (the TakeAWhileAndTellMeLater workflow), I will need to spin this up in a WorkflowServiceHost (a la the Duplex Sample in part 2).

    using (WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(CallLongRunningComponents.WorkflowWithmessaging)))
    {
        wsh.AddServiceEndpoint(
                typeof(Long_Running_Work.IReverseContract),
                new NetTcpContextBinding(),
               "net.tcp://localhost:10003/ReverseContract"
                );
        // don't forget to open up the wsh
        WorkflowRuntime wr = wsh.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime;
    
        wsh.Open();
    
    
        WorkflowInstance wi = wr.CreateWorkflow(
            typeof(CallLongRunningComponents.WorkflowWithmessaging));
        wr.WorkflowCompleted += ((o, e) => waitHandle.Set());
        wr.WorkflowIdled += ((o, e) => Console.WriteLine("We're idled"));
            
    
        wi.Start();
    
    
    
    
        waitHandle.WaitOne();
    
    }

    Why do I think this is cool?

    Two reasons:

    • If I assume that I can modify the called service to callback to me (or put such a wrapper at a runtime service level), this is easier to model than the APM (that code included at the end of this post)
    • This gives me a natural way to start exposing more advanced control over a service call.  Rather than just a send and receive, I can use a send and a listen, and in the listen have a receive, a cancel message receive, and a delay in order to expose more fine grained control points for my workflow, and model the way the process should work very explicitly and declaratively.

    image

     

    Code for APM approach:

    call some services and wait:

       1:  Console.WriteLine("Press <enter> to execute APM approach");
       2:  Console.ReadLine();
       3:  waitHandle = new AutoResetEvent(false);
       4:  Stopwatch sw = new Stopwatch();
       5:  sw.Start();
       6:  lrwc = new WorkflowHost.ServiceReference1.LongRunningWorkClient();
       7:  lrwc.BeginTakeAWhile(1, HandleClientReturn, "one");
       8:  lrwc.BeginTakeAWhile(2, HandleClientReturn, "two");
       9:  lrwc.BeginTakeAWhile(3, HandleClientReturn, "three");
      10:  lrwc.BeginTakeAWhile(4, HandleClientReturn, "four");
      11:  while (!areDone)
      12:  {
      13:      System.Threading.Thread.Sleep(25);
      14:  }
      15:  Console.WriteLine("APM approach compelted in {0} milliseconds", sw.ElapsedMilliseconds);
      16:  Console.WriteLine("All Done, press <enter> to exit");
      17:  Console.ReadLine();

    Ignore the busy wait on line 11, I should use a waithandle here but was having trouble getting it to work correctly (this is hard code).

    The callback and respective state:

       1:  static ServiceReference1.LongRunningWorkClient lrwc;
       2:  static Int32 countOfFinished = 0;
       3:   
       4:  static void HandleClientReturn(IAsyncResult result)
       5:  {
       6:      string s = (string)result.AsyncState;
       7:      string resultString = lrwc.EndTakeAWhile(result);
       8:      Console.WriteLine("received {0}", resultString);
       9:      if (Interlocked.Increment(ref countOfFinished) == 4)
      10:      {
      11:          areDone = true;
      12:      }
      13:  }

    I have had some people say that line 9 should use Interlocked.CompareExchange in order to do this correctly, but the point is that this is tricky code, that modeling in WF is pretty nice.  [ignoring for the moment the work required to realize the assumption that we can make the service message back.] 

  • mwinkle.blog

    Q & A on Advanced Workflow Services talk

    • 1 Comments

    Martin posted an interesting question here on my last post:

    <quote>

    The first thing that we need to do in order to enable this duplex messaging to occur is that the "client" workflow has to explicitly provide its context token to the service so that the service can address the appropriate instance of the client workflow.

    Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information.

    </quote>

    Shouldn't we have this built into a custom binding? (or an extra binding element) So with every call from the client the (WF)context information is included. And the developer is not required to follow a (artificial) state machine.

    Note, at the time, when the service calls back, the endpoint (and the binding) of the client may have changed... So we may need dynamic name-endpoint resolution (sounds like DNS?)

    Martin

    The question here is generally also asked as "wait, why do I need to explicitly provide a context token and change the contract to have this context thing?"  This is a common question, as changing the contract to reflect implementation details is generally a no-no.  There's one part I left out as well, so let me add that here:

    In the real world, one may also wish to not change the contract (or may not have the ability to).  In that case, we still need to explicitly provide the context token and endpoint information in order to allow me to call back.  There are a few ways to do this, of varying complexity and implication:

    • Put this into the message header and have the other side extract this information and use it the same way.
      • There are two downsides to this approach:
        • It still requires management of the other side to agree upon where the context token is being place.
        • The WF Messaging activities don't give me an easy way to reach in and get to header information, but one could certainly look at some infrastructure level extensions to manage this.  This idea of making duplex easier is one thing that Ed will be talking about in his Oslo workflow services talk at PDC
    • Create a custom binding element.
      • There is one downside with this approach:
        • You're creating a custom channel, custom binding element, and all the other stuff that goes along with creating a channel.  This is very hard work.  If the answer is "you've got to write a channel to do it," we need to do a better job making it easier (see earlier point about Ed's talk).
      • If that's the behavior that you want, you are certainly welcome do go down that path, it would be great to hear about your experiences doing it!
      • The upside to this approach:
        • You're creating a layer of near infinite extensibility, allowing you to handle simple things to the complex dynamic endpoint resolution behavior, once you invest the cost once to create the channel that would sit there and do that.

    This is also the same approach one could take with using an implicit, or content based correlation scheme.  In that case, you create an intermediary that is responsible for translating message contents into the "addressing" information for the instance.  That intermediary can be a service, it could be a channel, and once you put that intermediary in place, you are free to do as sophisticated or as simple work as possible.

  • mwinkle.blog

    Advanced Workflow Services Talk (Demo 2 of 4)

    • 2 Comments

    A continuation of my series of demos from my advanced workflow services talk.  Here we focus on duplex message exchange patterns.

    Duplex messaging is something that we model at the application level (as opposed to the infrastructure level) because we want to model that message exchange at the level of the application.  Here's some scenarios where I could use duplex messaging:

    • [concrete] I submit an order, and you tell me when it ships
    • [abstract] I ask you do to do some long running work, let me know when it is done
    • [abstract] I ask you to start doing something, you update me on the status

    One may ask the question, "But, what about the wsHttpDualBinding, or WCF duplex bindings."  That's a valid question, but it's important to point out that those bindings are really used to describe the behavior of a given proxy instance (and associated service artifacts).  When my proxy dies, or the underlying connection goes away, I lose the ability for the service to call back to me.  Additionally, this binds me to listen in the same way that I sent out the initial message. 

    By modeling this at the application layer, we do lose some of the "automagicity" of the WCF duplex behavior, but I get more flexibility, and I get the ability to sustain potentially repeated recycling of the services and clients.  Also, you could imagine a service that I call that turns around and calls a third party service.  That third party service could call back directly to the client that made the initial call.  Note, once we start doing duplex communication (and we'll encounter this in part 4, conversations), is that the definition of "client" and "service" become a bit muddier.

     

    So, to the code:

     

    Ingredients:

    • My service workflow, I listen for three different messages (start, add item, complete ), and then I will send the message back to the client:
    • image
      • You'll note that there is a loop so that we can keep adding items until we eventually get the complete order message and we then exit the loop.
    • A "client" workflow, which will call this service:
    • image
      • You'll note, some of the magic happens here.  After I start, add and complete the order, you'll see that instead of sending messages, I'll now flip around and wait on the receive in order to receive the shipping cost from the service.

     

    Details

    The first thing that we need to do in order to enable this duplex messaging to occur is that the "client" workflow has to explicitly provide its context token to the service so that the service can address the appropriate instance of the client workflow.

    Note, in the real world, you'll probably need to supply more than just the context token, you will need some address and binding information.

    Let's look at the contract of the service:

    [ServiceContract(Namespace ="http://microsoft.com/dpe/samples/duplex")]
    public interface  IOrderProcessing
    {
        [OperationContract()]
        void SubmitOrder(string customerName, IDictionary<string, string> context);
    
        [OperationContract(IsOneWay = true )]
        void AddItem(OrderItem orderItem);
    
        [OperationContract(IsOneWay = true )]
        void CompleteOrder();
    }

    You'll note that on the SubmitOrder method, I pass in a context token.  This is my callback correlation identifier, this is how I will figure out what instance on the client side I want to talk to.  Now, I need to do some work to get the context token in order to send, so let's look at how we do this:

    On the client side, on the first Send activity, let's hook the BeforeSend event.

    image

     

    Let's look at the implementation of GrabToken:

       1:  private void GrabToken(object sender, SendActivityEventArgs e)
       2:  {
       3:      ContextToSend = receiveActivity1.Context;
       4:      Console.WriteLine("Received token to send along");
       5:  }
       6:   
       7:  public static DependencyProperty ContextToSendProperty = DependencyProperty.Register("ContextToSend", typeof(System.Collections.Generic.IDictionary<string, System.String>), typeof(OrderSubmitter.Workflow1));
       8:   
       9:  [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
      10:  [BrowsableAttribute(true)]
      11:  [CategoryAttribute("Parameters")]
      12:  public System.Collections.Generic.IDictionary<string, String> ContextToSend
      13:  {
      14:      get
      15:      {
      16:          return ((System.Collections.Generic.IDictionary<string, string>)(base.GetValue(OrderSubmitter.Workflow1.ContextToSendProperty)));
      17:      }
      18:      set
      19:      {
      20:          base.SetValue(OrderSubmitter.Workflow1.ContextToSendProperty, value);
      21:      }
      22:  }

    First, note that on lines 7-22 we declare a dependency property call ContextToSend.  Think of this simply as a bindable storage space.  On line 3, we go and assign to that the value of receiveActivity1.Context.  "But Matt, couldn't I just build a context token off the workflow ID?"  You could, but you're only going to be correct in the "simple scenario."   You can see we then take that ContextToSend, and pass that into the context parameter for the service operation. Always walk up and ask a Receive activity for its context token, don't try to build one on your own.

    Now, on the service side, we need to extract that, and we need to apply the value to the send activity in the service workflow that needs to call back.  We basically can do the reverse:

    private void codeActivity1_ExecuteCode(object sender, EventArgs e)
    {
        //set callback context
        sendActivity1.Context = callbackContext;
    }

    This is inside a code activity in the first receive activity.  callbackContext is a dependency property that is bound to the inbound context on the Receive activity. 

     

    The final trick is that both workflows have to be hosted inside a WorkflowServiceHost.  This makes sense for the "service" workflow, since it will be message activated.  On the client side, we have to do a little bit of work in order to get to the workflow runtime to spin up a workflow instance.  In the early betas, we had an easy way to get to the runtime, WorkflowServiceHost.WorkflowRuntime.  In order to conform more with the extensibility of WCF, this has been moved to the extensions of the service host.  We get there by:

       1:  static void Main(string[] args)
       2:  {
       3:      using (WorkflowServiceHost wsh = new WorkflowServiceHost(typeof(Workflow1)))
       4:      {
       5:          Console.WriteLine("Press <ENTER> to start the workflow");
       6:          Console.ReadLine();
       7:          wsh.Open();
       8:          WorkflowRuntime wr = wsh.Description.Behaviors.Find<WorkflowRuntimeBehavior>().WorkflowRuntime;
       9:          WorkflowInstance wi = wr.CreateWorkflow(typeof(Workflow1));
      10:          AutoResetEvent waitHandle = new AutoResetEvent(false);
      11:          wr.WorkflowCompleted += delegate { waitHandle.Set(); };
      12:          wr.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine("error {0}", e); waitHandle.Set(); };
      13:          wi.Start();
      14:          waitHandle.WaitOne();
      15:          Console.WriteLine("Workflow Completed");
      16:          Console.ReadLine();
      17:      }
      18:  }

    On line 3, you'll see we new up a WorkflowServiceHost based on the service type (it will do this to find and open the respective endpoints).  On line 8, we reach in and grab the WorkflowRuntimeBehavior and get the WorkflowRuntime, and we use that to create an instance of the workflow. 

    So, here's what we have done:

    • Figure out how to grab the context token from a Receive activity
    • Modify the contract to explicitly send the "callback info" to the service
    • On the service side, figure out how to grab that and apply it to a Send activity
    • Finally, on the client side, how to manually kick off workflows, rather than waiting for them to be message activated (the usual path we have is the infrastructure creating the workflow instance). 
  • mwinkle.blog

    Advanced Workflow Services Talk (Demo 1 of 4)

    • 5 Comments

    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
  • mwinkle.blog

    Oslo PDC Sessions Posted

    • 1 Comments

    Late last night, the PDC team posted an additional "bunch" of sessions, including one I'm particularly interested in:

    Extending Windows Workflow Foundation v.Next with Custom Activities

    Presenter: Matt Winkler

    Windows Workflow Foundation (WF) coordinates and manages individual units of work, encapsulated into activities. The next version of WF comes with a library of activities, including Database and PowerShell. Learn how to extend this library by encapsulating your own APIs with custom activities. See how to compose those basic activities into higher level units using rules, flowchart, or state machine control flow styles. Learn how to extend beyond WF control styles by building your own. Learn how to customize and re-host the workflow authoring experience using the new WF designer framework.

    Tags:

    Advanced, WF

    They also posted a number of other interesting Oslo sessions that folks might be interested in, these cover a wide range of the things that our group is doing:

    • "Oslo:" The Language - Don Box and David Langworthy
      • "Oslo" provides a language for creating schemas, queries, views, and values. Learn the key features of the language, including its type system, instance construction, and query. Understand supporting runtime features such as dynamic construction and compilation, SQL generation, and deployment. Learn to author content for the "Oslo" repository and understand how to programmatically construct and process the content.

    • "Oslo:" Customizing and Extending the Visual Design Experience - Florian Voss
      • "Oslo" provides visual tools for writing data-driven applications and services. Learn how to provide a great experience over domain-specific schemas, and explore the basic user model, data-driven viewer construction, user-defined queries, and custom commands. See how the design experience itself is an "Oslo" application and is driven by content stored in the "Oslo" repository.

    • Hosting Workflows and Services in "Oslo" - Ford McKinstry
      • "Oslo" builds on Windows Workflow (WF) and Windows Communication Foundation (WCF) to provide a feature-rich middle-tier execution and deployment environment. Learn about the architecture of "Oslo" and the features that simplify deployment, management, and troubleshooting of workflows and services.

    • "Oslo" Repository and Schemas - Martin Gudgin, Chris Sells
      • "Oslo" uses schematized data stored in the "Oslo" repository to drive the development and execution of applications and services. Tour the schemas and see how user-defined content can be created and related to them. Learn how to utilize platform schemas, such as worflow, services, and hosting. Also, learn how to extend the repository and how to use repository-extended SQL database services to support critical lifecycle capabilities such as versioning, security, and deployment.

    It'll be a good time, can't wait to see ya there!

    Page 1 of 1 (7 items)