Sending your own objects to a WF4 workflow

In the previous two blog posts (here and here) I showed how to create and expose a Windows Workflow Foundation 4 workflow via WCF and have both a workflow and a regular C# client work with it. But the parameter and return value where real simple with just a string each. So how about passing some more complex data.

 

To show how to do so I will replace the singe string with a person object. Admittedly not the most complex person with only an id, first and last name and their birth date but enough to demonstrate the principal.

 

The main of the service, which uses a WorkflowServiceHost, stays exactly the same so see the first blog post for that part. The CreateWorkflow() function will change now reflecting the fact that the service will no longer expect a string but a person object.

private static WorkflowElement CreateWorkflow()


{


    var result = new Sequence();


    var input = new Variable<Person>();


    result.Variables.Add(input);


    XNamespace ns = "http://tempuri.org";


 


    var handle = new Variable<CorrelationHandle>();


    result.Variables.Add(handle);


 


    var receive = new Receive()


    {


        OperationName = "Operation1",


        ServiceContractName = ns + "MyService",


        Value = new OutArgument<Person>(input),


        AdditionalCorrelations = {                   


           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}


        },


        CanCreateInstance = true


    };


    result.Activities.Add(receive);


 


    var write = new WriteLine()


    {


        Text = new InArgument<string>(env => string.Format("\tThe workflow was called with '{0}'.", input.Get(env)))


    };


    result.Activities.Add(write);


 


    var reply = new SendReply()


    {


        Request = receive,


        Value = new InArgument<string>("The result")


    };


    result.Activities.Add(reply);


 


    return result;


}

Not a big change really as we only have to change the input variable from Variable<string> to Variable<Person> and do the same with the Value property of the Receive activity which goes from new OutArgument<string>(input) to new OutArgument<Person>(input).

 

That leaves the Person class and this is just straightforward WCF work by turning the basic class into a data contract.

[DataContract(Namespace = "urn:WF4Sample:person")]


public class Person


{


    [DataMember]


    public int Id { get; set; }


    [DataMember]


    public string FirstName { get; set; }


    [DataMember]


    public string LastName { get; set; }


    [DataMember]


    public DateTime BirthDate { get; set; }


 


    public override string ToString()


    {


        return string.Format("{0} {1} was born on {2} (ID: {3})", FirstName, LastName, BirthDate, Id);


    }


}

Again no big deal [:)]

 

So with the service done we need to make the same changes to the two client applications. First the workflow client using the Send activity.

The Person class is exactly the same as on the server so just copy the code as is. Again the main in the workflow client doesn’t change, see the first blog post for that part. The part that does change a little is the CreateWorkflow() function so it creates and sends a Person instead of a string. The code goes as follows:

private static WorkflowElement CreateWorkflow()


{


    var result = new Sequence();


    XNamespace ns = "http://tempuri.org";


 


    var endpoint = new Endpoint()


    {


        Uri = new Uri("http://localhost:8090/Sequence1/Operation1"),


        Binding = new BasicHttpBinding()


    };


 


    var response = new Variable<string>();


    result.Variables.Add(response);


 


    var handle = new Variable<CorrelationHandle>();


    result.Variables.Add(handle);


 


    var person = new Person()


    {


        Id = 7,


        FirstName = "Maurice",


        LastName = "de Beijer",


        BirthDate = new DateTime(1962, 8, 24)


    };


 


    var request = new Send()


    {


        OperationName = "Operation1",


        Endpoint = endpoint,


        AdditionalCorrelations =


        {


           {"ChannelBasedCorrelation", new InArgument<CorrelationHandle>(handle)}


        },


        ServiceContractName = ns + "MyService",


        Value = new InArgument<Person>(person)


    };


    result.Activities.Add(request);


 


 


    var reply = new ReceiveReply()


    {


        Request = request,


        Value = new OutArgument<string>(response)


    };


    result.Activities.Add(reply);


 


    var write = new WriteLine()


    {


        Text = new InArgument<string>(response)


    };


    result.Activities.Add(write);


 


    return result;


}

Again a very minor change.

 

So how about the C# console client from the second blog post?

The service contract changes a little and is now:

[ServiceContract]


interface MyService


{


    [OperationContract]


    Operation1Response Operation1(Person request);


}

 

The person class is very different from the service though and looks like this:

[MessageContract(WrapperNamespace = "urn:WF4Sample:person")]


internal class Person


{


    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]


    public int Id { get; set; }


    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]


    public string FirstName { get; set; }


    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]


    public string LastName { get; set; }


    [MessageBodyMember(Namespace = "urn:WF4Sample:person")]


    public DateTime BirthDate { get; set; }


}

But besides the difference from using a message contract as opposed to a data contract this isn’t a big deal.

The main program changes a bit but not a whole lot. Again the only changes are due to the fact we are passing in a Person instead of a string.

static void Main(string[] args)


{


    var binding = new BasicHttpBinding();


    var endpoint = new EndpointAddress("http://localhost:8090/Sequence1/Operation1");


    var factory = new ChannelFactory<MyService>(binding, endpoint);


    var proxy = factory.CreateChannel();


 


    var request = new Person()


    {


        Id = 23,


        FirstName = "Joe",


        LastName = "Regular",


        BirthDate = new DateTime(1975, 7, 1)


    };


    var result = proxy.Operation1(request);


    Console.WriteLine(result.Value);


 


    Console.WriteLine("Regular client is done.");


    Console.ReadLine();


}



 



All together not bad.



 











But so far we have only used a very simple workflow with a single message send to it. Suppose you want to send multiple messages to the same workflow? Well in that case we need to use message correlation as we will see next time.



 



Enjoy!



[f1]
[f2]

4 thoughts on “Sending your own objects to a WF4 workflow

  1. Did you achieve this without using Add Service Reference? I tried to do the same in xaml but couldn’t get it to work. If I use Add Service Reference, and use the generated custom type, it works but if I use my own, annotated with the MessageContractAttribute, it doesn’t. Any chance you could revisit this using XAML?

  2. @Simon

    I am duplicating the Person on both sides. One thing to make sure of is that the SOAP namespaces match up so I explicitly specify them on both ends. This example was done without using the Add Service Reference as I only needed the Person class but doing so is certainly a valid option.

    With Beta 1 the designer is somewhat unstable so I am developing mos workflows using code. When we get beta 2 I expect to be doing more with the designer and XAML.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>