Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Silverlight V1.1: Talking to a Workflow

Blogs

Mike Taulty's Blog

Elsewhere

Archives

I was thinking about how you'd get Silverlight to drive interaction with a Windows Workflow and thought I'd make a stab at that here.

Keeping it very simple, imagine that I have 5 images on my web server image1 to image5 and I want a Silverlight client to display them in order, one after another and I want to control this with my Workflow.

The Workflow instance will be activated by Silverlight and it'll return the first image name and then Silverlight will call back into the Workflow instance and the instance will move the client through image 2,3,4,5 and then the instance will end.

Whilst this isn't a very complicated scenario, it does model the idea that we have a web client that we cannot contact (i.e. it's pull only from the client end) and so the client is going to have to poll and we want to make sure that the client is directed back into the same Workflow instance that it started originally.

First off, I built a WCF interface to model this. This looks like;

namespace ImageWorkflowLibrary
{
  [ServiceContract(Namespace="")]
  public interface ISelectPicture
  {
    [OperationContract]
    [WebInvoke(
      Method = "POST", // For the moment - not sure if I can make "GET" work
      BodyStyle = WebMessageBodyStyle.Bare, 
      RequestFormat = WebMessageFormat.Json,
      ResponseFormat = WebMessageFormat.Json)]
    string GetNextPicture();
  }
}

And then I can go and build a simple Workflow around that which looks like this;

image

Now, let me explain some things about this Workflow. It's just a loop. So, that first activity is a Receive activity. It receives a GetNextPicture message and it responds with a string (I have bound this to a property called ReturnImageName of type string in the Workflow). In the workflow, this is just an array;

    private static string[] images = new string[] 
      "image1.jpg",
      "image2.jpg",
      "image3.jpg",
      "image4.jpg",
      "image5.jpg"
    };

and the Workflow has a member of type int called currentIndex and all it's doing is returning the items from that array one by one as it gets called. That loop on the right hand side will loop up to 4 times to index into that array and the parallel delay on the left hand side will kill the Workflow after 2 minutes elapse.

So, that's all fine and dandy.

I now want to deploy this Workflow inside of IIS so I add a new WCF web service project to my solution (remembering to run VS as administrator) and I add a reference to my Workflow library from there and I edit the file Service.svc to point at my Workflow and the WorkflowServiceHostFactory.

<%@ ServiceHost Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" Language="C#" Debug="true" Service="ImageWorkflowLibrary.Workflow" %>

I start to hit a few "problems". I want to use the context stuff inside of Workflow which will automatically route WCF messages to the correct Workflow instance based on either a SOAP header or an HTTP cookie. In trying to put this together with the webHttpBinding that I'm using which makes WCF speak "JSON" I find that, whilst there is a netTcpContextBinding and a wsHttpContextBinding, there isn't a webHttpContextBinding so there's nothing that puts together the "HTTP-ness" along with the "contextual-ness" :-)

I figure that I'll try and make my own custom binding which does this and I do ultimately come up with a configuration that seems to work;

    <system.serviceModel>
        <services>
            <service name="ImageWorkflowLibrary.Workflow" behaviorConfiguration="ServiceBehavior">
                <endpoint address="" 
                  binding="customBinding" 
                  contract="ImageWorkflowLibrary.ISelectPicture"
                  bindingConfiguration="myBinding"
                  behaviorConfiguration="myBehaviour"/>
            </service>
        </services>
    <bindings>
      <customBinding>
        <binding name="myBinding">
          <context contextExchangeMechanism="HttpCookie"/>
          <webMessageEncoding/>
          <httpTransport manualAddressing="true"/>
        </binding>
      </customBinding>
    </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">

                    <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
                    <serviceMetadata httpGetEnabled="true"/>
                    <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                    <serviceDebug includeExceptionDetailInFaults="false"/>
          
                </behavior>
            </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="myBehaviour">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
        </behaviors>
    </system.serviceModel>

So, you can see that I've tried to make a custom binding which does http + webMessageEncoding + context and that context is trying to use an HttpCookie to exchange the context token (i.e. identifying the workflow instance and the particular receive activity) and I've also added the webHttp behaviour.

With that in place, I managed to get a WSDL out of the service so I go and build a Silverlight client for it.

I add a new Silverlight project, make a link from the web site to the Silverlight project and move the .html and .js files from the Silverlight project to the web one to ensure that I'm always running the web site rather than the Silverlight project (to avoid all those "cross-site" hiccups).

I then go and do an Add Web Reference from my Silverlight project to my web service and........."it works". I was kind of amazed by that but there you go. Especially because VS raised a lot of scary dialogs doing it.

However, with that web reference in place I realise I have two problems with the proxy I've got;

  1. It's derived from SoapHttpClientProtocol and it seems to use HTTP POST and there doesn't appear to be a "don't do that" setting. So, I reconfigure my service interface to accept post. That is, the ISelectPicture interface you see above was originally marked up with WebGet and that didn't work and I moved to WebInvoke(Method="POST"). You'll see in a moment that I could move it back to "GET" because, ultimately, I end up avoiding SoapHttpClientProtocol.
  2. It's derived from SoapHttpClientProtocol and it seems that there's no CookieContainer so, no matter what the Worfklow service sends back to me, I'm not going to be able to use it :-(

At this point, I decide to ditch "Add Web Reference" and go for my own version using BrowserHttpWebRequest.

Having moved to BrowserHttpWebRequest, it doesn't look like it deals with Cookies either. There's a CookieContainer property on BrowserHttpWebRequest but that looks to be of type object so I'm not sure what to do with it - turns out that this is not implemented at this point.

The response that you get back from a BrowserHttpWebRequest, the HttpWebResponse, does not seem to have a cookies collection at all so it's not clear how to use that either.

In the end I spent quite a bit of time on cookie handling here. As far as I know;

  1. You will not have success in using BrowserHttpWebRequest at the moment to set cookies for 2 reasons;
    1. If you go down the route of BrowserHttpWebRequest.Headers[HttpRequestHeader.Cookie] you'll find that you get an Expect header that you weren't expecting (sorry, I couldn't resist :-)). It seems that there's something a bit broken in the mapping of that enumeration to headers right now.
    2. Whether you do (1) or simply start to do BrowserHttpWebRequest.Headers["Cookie"] you'll still struggle and I got some very good advice that it might be because of this bug (http://support.microsoft.com/?id=234486). Essentially, to get around this bug you would need to set the Cookie value twice. That's impossible to do from Silverlight because the underlying code builds up its own collection of headers which it will set on the real IE object just before it makes the request (i.e. once and at a point where it's gone from my control). This means that you can't set the property twice and so you'll always hit the bug :-(
  2. You can grab the cookie from HttpWebResponse.Headers["Cookie"] - I wrote some hacky code to parse out only what I needed and it seemed to hold together just enough to work.
  3. You can set cookies via HtmlPage.Cookies["Cookie"] = "myCookie=value".
    1. For working with Workflow this proved a bit problematic because the workflow is controlling its lifetime. So, there comes a point at which I call the workflow from the Silverlight client and the workflow bits respond with "Sorry, the workflow that your cookie represents has gone". At this point I simply clear the cookie on the client such that when the client calls again the workflow bits will create a new instance of the workflow for the client.

So, in the end it works out ok with a bit of stress-and-strain along the way. The client bits ended up looking like this;

image

And I've dropped the projects that I use onto the website here for download. Bear in mind, this is nearer to hacking than it is to "best practise" but there are perhaps some Alpha things that needed to be worked around. There are 3 projects - Workflow, Silverlight and the Web Site itself which I was using from IIS.


Posted Mon, Aug 13 2007 7:30 AM by mtaulty

Comments

Sam Gentile wrote New and Notable 180
on Mon, Aug 13 2007 10:28 PM
So, I am totally thrilled in my new role at Neudesic , where today, I got to spend quite a bit of time
Jason Haley wrote Interesting Finds: August 14, 2007
on Tue, Aug 14 2007 6:47 AM
Christopher Steen wrote Link Listing - August 16, 2007
on Thu, Aug 16 2007 8:32 PM
Link Listing - August 16, 2007
A Word about Link Blogs « Random Musings wrote A Word about Link Blogs &laquo; Random Musings
on Wed, Sep 12 2007 11:36 AM