Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Workflow, HTTP GET and POX

Blogs

Mike Taulty's Blog

Elsewhere

Archives

I was thinking about how the new WCF support enables people to pretty easily invoke a workflow so I thought I'd have a stab at putting something together that offered a Workflow over HTTP GET and returned POX.

Say I want to build a Workflow that exposes some data from my Northwind Customer's table and it'll make some logic choice as to which bits of data to return (UK customers/all customers) based on some flag passed to it.

So, perhaps a simplistic interface (WCF contract) such as;

namespace Contracts
{
  [ServiceContract]
  public interface IGetCustomerData
  {
    [OperationContract]
    [HttpTransferContract(Method = "GET")]
    Customer[] GetData(bool allData);
  }
}
 

Note that the return type here is Customer[] rather than List<Customer> because I don't think generics play too well with Workflow.

Note also, the type Customer comes from a referenced assembly (LinqHelpers) which has a LINQ to SQL file with the Northwind Customers table dragged onto it. Also, that referenced assembly has two helper functions as below. I did this because I haven't worked out how to get the Workflow compiler to do a C# V3.0 compilation and, therefore, be able to put LINQ stuff straight into a Workflow project directly.

  public static Customer[] GetAllCustomers()
    {
      Northwind ctx = new Northwind("server=.;database=northwind");
      ctx.DeferredLoadingEnabled = false;

      var query = from c in ctx.Customers                  
                  select c;

      return (query.ToArray());
    }
    public static Customer[] GetSomeCustomers()
    {
      Northwind ctx = new Northwind("server=.;database=northwind");
      ctx.DeferredLoadingEnabled = false;

      var query = from c in ctx.Customers
                  where c.Country == "UK"
                  select c;

      return (query.ToArray());
    }

Now, I want to implement the WCF contract with Workflow so I go off and design myself a Workflow, drop a WCF Receive Activity onto it and configure that Receive Activity to use my IGetCustomerData contract.

 

image

This makes the allData parameter and the ReturnValue bindable for my Workflow;

image

so I'll bind those to new properties called (say) AllData and Result and then I'll drop an IF statement in there;

image

and I'll just use code activities (cheap and cheerful) to populate Result with either LinqHelpers.GetAllCustomers or LinqHelpers.GetSomeCustomers as in;

    private void getAllData_ExecuteCode(object sender, EventArgs e)
    {
      Result = Helpers.GetAllCustomers();
    }

    private void getSomeData_ExecuteCode(object sender, EventArgs e)
    {
      Result = Helpers.GetSomeCustomers();
    }

 

Ok, now to get this thing hosted in IIS using an HTTP GET.

I spent an awfully long time on this. First off, I created an IIS WCF Web Service project and got rid of most of the code from it.

Now, I know that I want to offer my Workflow service over HTTP GET and, as far as I know, this means using the WorkflowServiceHost rather than a ServiceHost and so I need to use the WorkflowServiceHostFactory rather than the regular ServiceHostFactory inside my .svc file.

However...I also know that I need to add a HttpTransferEndpointBehavior to my endpoint behaviours for WCF in order to make this work and, so far, I've not found out how to do this from config so that means more code which, I guess means that I need to derive something from WorkflowServiceHostFactory in order to get a chance to initialise my WorkflowServiceHost. So, I went and did;

public class WebWorkflowServiceHostFactory : WorkflowServiceHostFactory
{
  public override System.ServiceModel.ServiceHostBase CreateServiceHost(
    string constructorString, Uri[] baseAddresses)
  {
    ServiceHostBase host = base.CreateServiceHost(constructorString,
      baseAddresses);

    host.Description.Endpoints[0].Behaviors.Add(
     new HttpTransferEndpointBehavior());

    return (host);
  }
}

 

and I authored my .svc file as;

<%@ ServiceHost Factory="WebWorkflowServiceHostFactory" Language=C# Debug="true" Service="Workflows.GetDataWorkflow" CodeBehind="~/App_Code/Service1.cs" %>

and I then set about trying to configure the thing up. Here's what I ended up with;

  <system.serviceModel>
    <services>
      <service name="Workflows.GetDataWorkflow">
        <endpoint 
          address="Service"
          contract="Contracts.IGetCustomerData" 
          binding="webHttpBinding" 
          bindingConfiguration="poxBinding"/>
      </service>
    </services>
    <bindings>
      <webHttpBinding>
        <binding name="poxBinding" messageEncoding="Text"/>
      </webHttpBinding>
    </bindings>
    <extensions>
      <bindingExtensions>
        <add name="webHttpBinding" type="System.ServiceModel.Configuration.WebHttpBindingCollectionElement, System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      </bindingExtensions>
    </extensions>
  </system.serviceModel>

 

Note - The reason for using the address of Service here is to avoid the problem that I encountered here where you can't easily have a service address ending in .svc right now if you want to use HTTP GET.

Note - I find that I need to include the binding extension myself for webHttpBinding - not sure what's happening there as I'd expect it to "just work" but something in the config system on my machine is stopping that from happening so I did it myself.

With all that in place, I'm expecting to be able to invoke my workflow with an HTTP GET to a URL such as;

http://localhost/WFOverPoxService/DataService.svc/Service/GetData?allData=True

However, I've forgotten something. Invoking this service gives me an exception that my Customer type that's coming back from my Workflow isn't serializable. In the first instance, I figured that was ok as I could just tell the service contract that I wanted to use the Xml Serializer as in;

  [ServiceContract(Namespace="")]
  [XmlSerializerFormat]
  public interface IGetCustomerData
  {
    [OperationContract]
    [HttpTransferContract(Method = "GET")]
    Customer[] GetData(bool allData);
  }

However, that doesn't work in that if I go down that route then I end up with an exception;

Additional information: Envelope Version 'EnvelopeNone (http://schemas.microsoft.com/ws/2005/05/envelope/none)' is not supported.

Presumably this is saying "I can't do XML serialization without some kind of envelope to put it in". Wonder if that's a bug or by design?

So, I figure that I'll revisit my Customer type and see if I can get the code generation tool for LINQ to SQL (sqlmetal.exe) to generate me types that serialize nicely in the first place. Originally, I'd been using the diagramming tool but I don't think that you can control serialization options from there so I go and run sqlmetal.exe with;

sqlmetal.exe /server:. /database:northwind /code:northwind.cs /serialization:Unidirectional /pluralize /namespace:LinqHelpers

and out pops a Customer type with DataContract/DataMember all over it.

With the serialization problem fixed, I can finally go and visit my endpoint;

http://localhost/WFOverPoxService/DataService.svc/Service/GetData?allData=True

http://localhost/WFOverPoxService/DataService.svc/Service/GetData?allData=False

and have a Workflow run and XML data returned from it.

Now, clearly, the assumption here is that the operations that my Workflow performs are "quick enough" (quoted because that idea is a little conceptually flawed) in order to run them synchronously when a caller makes an HTTP request. If there was something long running in any of what I'm doing then it'd get more complicated and we'd need to do something to pass around identifiers for the Workflow instances and so on.

However, in this case I get away with it. Things I've learnt;

  1. There's some problems in the beta 1 (not surprising). Specifically, not being able to use XmlSerializer hurt me and the "HTTP+GET+.svc+IIS" problem (here) hurt me too.
  2. Not thinking about serialization of my Customer type up front hurt as well - if the XmlSerializer stuff was working then it'd have been a relatively easy fix but, because DataContract is an opt-in model it means quite major changes to the Customer type so I had to go back and revisit my code generation for LINQ to SQL.
  3. It'd be nice to find a way of adding HttpTransferEndpointBehavior without writing code - pretty sure there's a way to do that and I'm just missing it.
  4. Would be nice if Workflow "got" generics.
  5. Not sure how to make the WF compiler be a C# 3.0 compiler rather than a C# 2.0 compiler

I've put the source code for all this here. There's a web site in there that would need deploying to IIS in order to make it work. Note, there's no client, I was just testing it in the browser but the next natural step would be to build an AJAX client or a Silverlight client or something.


Posted Fri, Jun 8 2007 10:14 AM by mtaulty

Comments

Mike Taulty's Blog wrote Simple LINQ and an HTTP Handler
on Fri, Jun 8 2007 10:40 AM
All that messing with WCF and POX and so on&amp;nbsp;left me feeling a bit battered and bruised so I thought...
Christopher Steen wrote Link Listing - June 10, 2007
on Sun, Jun 10 2007 8:13 PM
Give it a REST! [Via: Anil John ] GWT a Year Later: Was it the correct level of abstraction? [Via: Dietrich...