0
votes

Scenario:

  • Windows Workflow Foundation (WF) under .NET 4
  • Workflow deployed as a WCF service
  • Multiple Receive operations, all with the same parameters
  • Operation name matches activity name
  • Using SQL Server workflow persistence
  • (No SharePoint)

By default, knowledge of the workflow is embedded in the clients when you add a service reference and generate the proxy. The client knows what WCF methods are available.

I want to decouple the workflow from the clients, essentially creating a "generic" client that can work with any workflow matching certain conventions. The client would query the SQL instance store to determine what activity/operation/bookmark(s) a given instance was waiting for (this is already a standard column - ActiveBookmarks), then present that choice to the user.

This way the workflow can be changed without having to recompile/redeploy the clients. Several commercial BPM systems work this way; you can add new human client activities and they show up automatically in the client's work queue. Everything is dynamically discoverable.

How can this be done? Does it require using Reflection.Emit to generate a proxy on the fly? Would it be easier if each operation used a different service contract?

1

1 Answers

1
votes

I have done this in several projects and it works just fine. And no need to use Reflection.Emit as WCF has all the required infrastructure in place.

For example the following code will call the default workflow service template, all you need to do is provide the right URL.

class Program
{
    static void Main(string[] args)
    {

        var factory = new ChannelFactory<IMyService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:9199/Service1.xamlx"));
        var proxy = factory.CreateChannel();
        var response = proxy.GetData(new GetDataRequest() { Value = 42 });
        Console.WriteLine(response.Value);
        Console.ReadLine();
    }
}

[ServiceContract(Name = "IService")]
interface IMyService
{
    [OperationContract]
    GetDataResponse GetData(GetDataRequest request);
}

[MessageContract(IsWrapped = false)]
class GetDataRequest
{
    [MessageBodyMember(Name = "int", 
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/")]
    public int Value { get; set; }
}

[MessageContract(IsWrapped = false)]
class GetDataResponse
{
    [MessageBodyMember(Name = "string", 
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/")]
    public string Value { get; set; }
}

If you need more flexibility you can also create a ServiceContract with type Message in and out and a single OperationContract with Name="*" and you can handcraft your WCF message as required.