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!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu