Search

Locations of visitors to this page

Categories

On this page

Duplex communication with .Net 3.5 Workflow Services
WCF Behavior Ordering
Pass-through SAML tokens & Secure Conversation (Part1)

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 50
This Year: 6
This Month: 1
This Week: 1
Comments: 69

Sign In

 Friday, June 05, 2009
Friday, June 05, 2009 9:28:52 PM (GMT Standard Time, UTC+00:00) ( WF )

Workflow Services was introduced as part of .Net 3.5 and enables us to use WF workflows as implementation of our WCF services. Workflows are long running by nature so the usual WCF duplex messaging constructs (where correlation between client & server is done based on actual physical connection/socket) are not suitable for most cases. Primarily because it is simply not possible or practical to keep a socket open until workflow finishes its processing (which could potentially take days to produce any meaningful response).

So channel based WCF duplex messaging is not good for long running scenarios. So how can we achieve duplex messaging in long running world?

The general guidance is to use an explicit callback channel (sometime known as “back channel”) for notifications. You can argue that addressability of the client (who may be behind NAT/Firewall) is a real blocker for explicit back channel & I totally agree. However with Relay technologies like .Net Service Bus, reaching to the client endpoints is not an issue anymore hence explicit back channel is much more practical/feasible option now.

Anyways in this post I will show you, how we can achieve durable duplex messaging using Workflow Services. Here is a basic scenario:

Frond end service (orange box) is a long running workflow service – actual service implementation is a WF workflow so it can go idle, persist, sit in the database & brought back into memory if and when needed (all the usual stuff).

Now with above setup if we do usual request-reply messaging – Send activity will simply wait for the reply, workflow will stay in memory & ultimately underlying WCF call will timeout and bad things will happen (workflow might terminate etc).

So first change we have to do is to move the “wait for reply logic” out of the workflow into the host. With this change workflow can go idle, persist and ultimately gets unloaded while host is still listening for the response. On the response, host can bring workflow back into memory and resume the execution.  Important takeaway is that we don’t have to do all this ourselves. Workflow Services & associated “context-exchange protocol” can do most of this for us.

Following is the implementation of front end service...

Dowork is a one-way call. As part of Dowork I’m also passing instanceId of the current workflow along with a callback address (using the standard WS-Addressing ReplyTo header - for protocol purists: this might not be the correct use of ReplyTo header but I’m using it here to keep things simple). So essentially I’m saying “please call me back on this number, when you are done.” J

 Dowork call returns immediately by triggering long running work in the Backend service. Workflow will become idle & can potentially persist.

To override the value of default replyTo header, I have created a simple message inspector.

class ReplyToInspector : IClientMessageInspector

{

    EndpointAddress replyTo;

    public ReplyToInspector(EndpointAddress replyTo)

    {

        this.replyTo = replyTo;

 

    }

    #region IClientMessageInspector Members

 

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

    {

       

    }

 

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)

    {

        request.Headers.ReplyTo = replyTo;

        return null;

    }

 

    #endregion

}

And this can be configured in config file as:

        <endpointBehaviors>

          <behavior name="reply">

            <replyTo address="http://localhost:43211/Callback/"/>

          </behavior>

        </endpointBehaviors>

Backend service is standard WCF service – doing callbacks using the usual ChannelFactory stuff. The important bit (highlighted) is the explicit context management.

public void Dowork(Guid instanceId)

{

    Console.WriteLine("Doing long running work...");

    for (int i = 0; i < 10; i++)

    {

        Console.Write("...");

        Thread.Sleep(1000);

    }

    Console.WriteLine();

  

 

    try

    {

        Console.WriteLine("Sending response...");

        SendResults(instanceId, OperationContext.Current.IncomingMessageHeaders.ReplyTo);

    }

    catch (Exception exp)

    {

        Console.WriteLine("Failed: " + exp.ToString());

    }

}

 

private void SendResults(Guid instanceId, EndpointAddress remoteAddress)

{

    var binding = new BasicHttpContextBinding();

    var cf = new ChannelFactory<IBackendCallback>(binding, remoteAddress);

    var proxy = cf.CreateChannel();

    var cc = proxy as IContextChannel;

 

    using (new OperationContextScope(cc))

    {

        var cmp = new ContextMessageProperty();

        var cm = cc.GetProperty<IContextManager>();

 

        cm.Enabled = false;

        cmp.Context["instanceId"] = instanceId.ToString();

 

       

        OperationContext.Current.OutgoingMessageProperties.Add(ContextMessageProperty.Name, cmp);

 

        proxy.LongRunningOperationCompleted(new OperationOutput { Id = 22, Status = "Done.." });

    }

}

In Dowork, I’m passing instanceId as method’s signature however you can easily pass this data as part of context header (remmeber context header is property bag -- IDictionary<string,string>)

In the SendResults – I’m creating approparite context header – so that WorflowServiceHost can connect this callback to the correct instance & correct activity inside that instance. WorkflowServiceHost will also load the workflow if it got persisted.

I’m attaching complete solution with this. In next post, I will talk about durable messaging enhancements added in .Net 4.0 Beta 1.

Stay tuned...

 

Download: DurableMessaging.zip

Comments [0] | | # 
 Tuesday, June 02, 2009
Tuesday, June 02, 2009 1:39:43 PM (GMT Standard Time, UTC+00:00) ( WCF )

WCF behaviours are applied based on a particular evaluation order. Exact details are available here 

The ServiceHost applies behaviors in the following order:

1.     Contract

2.     Operation

3.     Endpoint

  1. Service

Please note within any collection of behaviors, ordering is NOT guaranteed. Because of that sometimes when you configure some aspects of runtime in your custom behavior (for example, TransactionRequired property of the operation) – they get overwritten by default WCF behavior. For example, following will not work and TransactionRequired will always be set to false (default value).

class TestBv : Attribute, IOperationBehavior

{

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation){}

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)

    {

        dispatchOperation.TransactionRequired = true;

    }

 

    public void Validate(OperationDescription operationDescription){}

}

Why? Because here WCF is executing default OperationBehavior after the execution of your custom behavior. Please note, missing OperationBehaviorAttribute doesn’t mean that behavior won’t be applied at all rather it mean a default OperationBehavior will be applied.

So how to fix this?

Use explicit ordering: Remove the bahavior from the collection and apply it at a particular time yourself. Here is the modified behavior which will work correctly.

class TestBv : Attribute, IOperationBehavior

{

    IOperationBehavior orignal;

 

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation){}

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)

    {

        orignal.ApplyDispatchBehavior(operationDescription, dispatchOperation);

        dispatchOperation.TransactionRequired = true;

    }

 

    public void Validate(OperationDescription operationDescription)

    {

        this.orignal = operationDescription.Behaviors.Find<OperationBehaviorAttribute>();

        if (orignal != null)

             operationDescription.Behaviors.Remove(orignal);

    }

}

HTH...

Comments [0] | | # 
 Friday, May 08, 2009
Friday, May 08, 2009 10:36:33 PM (GMT Standard Time, UTC+00:00) ( )
Part 2

Let’s say you have implemented federated security and now you have bunch of services all of them requires a token from a trusted STS to provide any service. Now some of these services are logically part of same security realm but are still distinct entities.  To optimize performance, you probably want to do some short circuiting so that when a user presents a token (issued by trusted STS) to one of these services – then this frontend service should simply be able to forward this incoming token to backend services within its logical security boundary (akin to trusted delegation). Here is a diagrammatic view.

On the surface, this sounds like a simple scenario which can be implemented as:

·         Reach into incoming service security context

·         Extract the incoming token

·         Pass it on to backend service

The trouble here is: wsFederationHttpBinding wisely uses secure conversation, to avoid token acquisition overhead for each call. Due to this optimization, we never get to see the actual bootstrap token (token used to establish secure conversation session) inside our service methods.

 In the next post, I will show you how to extend WCF security framework to enable this scenario?

At a very high level, we need to hook into secure conversation handshake, extract the incoming SAML token and save it somewhere for future use.

 

Comments [0] | | #