December 2009 - Posts
Note: This blog post is written using the .NET framework 4.0 Beta 2
With Windows Workflow Foundation 3 it was possible to rehost the workflow designer in your own application. But possible is about all there was to say about it as it was pretty hard to do anything beyond the basics.
With Windows Workflow Foundation 4 live has become much better on the rehosting front
In fact it is possible to create the fully functional and useful workflow editor below in about 200 lines of code. Now that is more like it!
The WorkflowDesigner
The WorkflowDesigner is the main class to work with. This exposes the actual design surface through the View property and the linked property sheet through the PropertyInspectorView property. Both these properties point to ready to use WPF UIElement’s so, as long as the host used WPF, adding them to a form is easy. And loading or saving a workflow is easy to, all it takes is a Load() and Save() function pointing to a XAML file. The code that does this part is below:
_workflowDesigner = new WorkflowDesigner();
_workflowDesigner.Load(_fileName);
var view = _workflowDesigner.View;
Grid.SetColumn(view, 1);
Grid.SetRow(view, 1);
LayoutGrid.Children.Add(view);
var propInspector = _workflowDesigner.PropertyInspectorView;
Grid.SetColumn(propInspector, 2);
Grid.SetRow(propInspector, 1);
LayoutGrid.Children.Add(propInspector);
DesignerMetadata
One thing that is needed is to register the workflow activity designer metadata. It is just a single call but leaving it out means all activities are just a small collapsed shape and won’t expand.
new DesignerMetadata().Register();
Displaying the toolbox with activities
The toolbox on the left if another standard WPF control called the ToolboxControl. Again easy to add to any WPF form. Add to it ToolboxItemWrapper with the activity type to add and you are good to go. Dragging controls onto the design surface just works, no extra work required. In the code below I just scan through all types in a few assemblies and if they are valid activities I add them to the toolbox. Again nice and easy.
var toolbox = new ToolboxControl();
var cat = new ToolboxCategory("Standard Activities");
var assemblies = new List<Assembly>();
assemblies.Add(typeof(Send).Assembly);
assemblies.Add(typeof(Delay).Assembly);
assemblies.Add(typeof(ReceiveAndSendReplyFactory).Assembly);
var query = from asm in assemblies
from type in asm.GetTypes()
where type.IsPublic &&
!type.IsNested &&
!type.IsAbstract &&
!type.ContainsGenericParameters &&
(typeof(Activity).IsAssignableFrom(type) ||
typeof(IActivityTemplateFactory).IsAssignableFrom(type))
orderby type.Name
select new ToolboxItemWrapper(type);
query.ToList().ForEach(ti => cat.Add(ti));
toolbox.Categories.Add(cat);
Grid.SetColumn(toolbox, 0);
Grid.SetRow(toolbox, 1);
LayoutGrid.Children.Add(toolbox);
The current selection
At the top of the form I display the currently selected activity and its parents. The WorkflowDesigner has an Items collection with a bunch of useful objects in there. One of these is a Selection object. We could periodically check this Selection but its even easier to subscribe to chances using the Subscribe() function and passing in a handler that is called whenever the selection changes. Setting up the subscription is another one-liner.
_workflowDesigner.Context.Items.Subscribe<Selection>(SelectionChanged);
The handler itself isn’t very complex either.
private void SelectionChanged(Selection selection)
{
var modelItem = selection.PrimarySelection;
var sb = new StringBuilder();
while (modelItem != null)
{
var displayName = modelItem.Properties["DisplayName"];
if (displayName != null)
{
if (sb.Length > 0)
sb.Insert(0, " - ");
sb.Insert(0, displayName.ComputedValue);
}
modelItem = modelItem.Parent;
}
CurrentActivityName.Text = sb.ToString();
}
Validating the workflow
It would be nice to let the user know if the workflow is valid. Again quite easy to do by adding a IValidationErrorService to the WorkflowDesigner services collection. In this case I added a listbox to the form and let the IValidationErrorService add each error to the ListBox items. No need to call anything, the IValidationErrorService is automatically called as soon as anything in the workflow changes.
var validationErrorService = new ValidationErrorService(WorkflowErrors.Items);
_workflowDesigner.Context.Services.Publish<IValidationErrorService>(validationErrorService);
The IValidationErrorService consists of a single function that gets the list of errors as a parameter.
public class ValidationErrorService : IValidationErrorService
{
private IList _errorList;
public ValidationErrorService(IList errorList)
{
_errorList = errorList;
}
public void ShowValidationErrors(IList<ValidationErrorInfo> errors)
{
_errorList.Clear();
foreach (var error in errors)
{
_errorList.Add(error.Message);
}
}
}
Running the workflow
Just to be able to run the workflow I added a bit of code that uses a WorkflowApplication to run the workflow. Loading it is also easy, just call the ActivityXamlServices.Load() passing in the file and it will return a DynamicActivity ready to run.
var writer = new StringWriter();
var workflow = ActivityXamlServices.Load(_fileName);
var wa = new WorkflowApplication(workflow);
wa.Extensions.Add(writer);
wa.Completed = WorkflowCompleted;
wa.OnUnhandledException = WorkflowUnhandledException;
wa.Run();
So all together I have a working application that I can use to edit and run workflows. So who still needs Visual Studio 2010?
Sweet 
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
One of the things that wasn’t possible in Windows Workflow Foundation 3 was flowing transaction over a WCF service request into a workflow. We could have transactions on the client, we could have transactions on the server but they would not be able to cooperate.
Fortunately that is one the problems that Windows Workflow Foundation 4 solves for us. [:)]
In WF4 we can use the ReceiveAndSendreply template to configure a workflow as a service and accept WCF calls. That works just fine but doesn’t allow for transactions. If we want to use transactions we need to add the TransactedReceiveScope to the workflow. Move the ReceiveRequest activity into the Request part and the SendResponse activity into the Body part. Don’t forget the last part because leaving the SendResponse below the TransactedReceiveScope means things will not work and the error message will be less that helpful [:(]
This is enough to make all work done during the message receive part of a transaction. To show what is happening with transactions I am using the following code to record and return the transactional status:
using System.Text;
using System.Transactions;
public class TransactionReport
{
public static string GetReport()
{
var sb = new StringBuilder();
if (Transaction.Current == null)
{
sb.Append("No transaction");
}
else
{
sb.AppendFormat("TransactionInformation\n");
sb.AppendFormat("\tLocalIdentifier {0}\n", Transaction.Current.TransactionInformation.LocalIdentifier);
sb.AppendFormat("\tDistributedIdentifier {0}\n", Transaction.Current.TransactionInformation.DistributedIdentifier);
sb.AppendFormat("\tStatus {0}\n", Transaction.Current.TransactionInformation.Status);
sb.AppendFormat("\tIsolationLevel {0}\n", Transaction.Current.IsolationLevel);
}
return sb.ToString();
}
}
Adding a console application with a service reference with the following code:
using System;
using Client.ServiceReference1;
namespace Client
{
class Program
{
static void Main(string[] args)
{
var proxy = new ServiceClient();
var result = proxy.PostData();
Console.WriteLine(result);
Console.ReadLine();
}
}
}
Produces the following output:
No big surprises there. The only thing I am missing is the ability to control the IsolationLevel which seems to be fixed at Serializable.
Flowing transactions from the client
The first step we need to take for client transactions to flow to the service is do the service call inside of a transaction using this client code:
using System;
using System.Transactions;
using Client.ServiceReference1;
namespace Client
{
class Program
{
static void Main(string[] args)
{
using (var tx = new TransactionScope())
{
Console.WriteLine(TransactionReport.GetReport());
var proxy = new ServiceClient();
var result = proxy.PostData();
Console.WriteLine(result);
Console.WriteLine(TransactionReport.GetReport());
}
Console.ReadLine();
}
}
}
If we run the code now we get the following output:
The transaction is there on the client but DistributedIdentifier is still empty so the transaction doesn’t flow to the service yet.
Before transactions will flow we need to do two things. First of all we need a WCF binding that can flow transactions. So far we have been using the BasicHttpBinding, the default for an HTTP endpoint in the .NET 4 default configuration, which doesn’t support transactions. So we need to switch to something like WSHttpBinding that does. Next we need to enable transaction flow because it is disabled by default. All of this can be done by adding a little extra configuration to the web.config file.
<bindings>
<wsHttpBinding>
<binding transactionFlow="true"/>
</wsHttpBinding>
</bindings>
<services>
<service name="Service1">
<endpoint address=""
binding="wsHttpBinding"
contract="IService" />
</service>
</services>
With this in place all we need to do is update the client service reference and transactions will flow from the client to the service.
Keep in mind that flowing transactions across a network boundary is a very powerful but also a very dangerous concept. You might be letting someone else, from a different organization, place locks on your database. So make sure you understand the implications of flowing transactions before you open up a whole can of worms!
Nice [:)]
Enjoy!
TheProblemSolver
DotNetEvents
Note: This blog post is written using the .NET framework 4.0 Beta 2
In my previous post about Windows Workflow Foundation 4 I used the CorrelationScope activity to arrange for the activity correlation between a Send and the related ReceiveReply activities. That is quite easy to do and personally I find things nice and easy to arrange with both messaging activities nested in the CorrelationScope activity . The default templates use a slightly different approach though and use a RequestReplyCorrelationInitializer. The end result is the same though so just use what you think best.
Just for comparison below is the code to create the same workflow with a RequestReplyCorrelationInitializer.
static Activity CreateWorkflow()
{
var getDataResult = new Variable<string>();
var handel = new Variable<CorrelationHandle>();
var send = new Send()
{
CorrelationInitializers =
{
new RequestReplyCorrelationInitializer()
{
CorrelationHandle = handel
}
},
OperationName = "GetData",
ServiceContractName = "IService1",
Endpoint = new Endpoint()
{
Binding = new BasicHttpBinding(),
AddressUri = new Uri("http://localhost:8080/GetDataService/")
},
Content = new SendParametersContent()
{
Parameters =
{
{"value", new InArgument<int>(42)}
}
}
};
var receive = new ReceiveReply()
{
Request = send,
Content = new ReceiveParametersContent
{
Parameters =
{
{"GetDataResult", new OutArgument<string>(getDataResult)}
}
}
};
var workflow = new Sequence()
{
Variables = { getDataResult, handel },
Activities =
{
send,
receive,
new WriteLine() { Text = getDataResult }
}
};
return workflow;
}
The workflow is even a little simpler because there are fewer activities involved. And this is effectively the same as the SendAndReceiveReply and ReceiveAndSendReply templates generate in WF4.
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
Note: This blog post is written using the .NET framework 4.0 Beta 2
There are two types of correlation to think about in Windows Workflow Foundation:
- Message correlation
Basically sending multiple requests to the same workflow. - Activity correlation
Making sure two activities work together.
In this post I am going to show activity correlation
Activity correlation is used when multiple activities form a single logical action. Think about the Send and ReceiveReply activities. Two distinct activities but they work together because the first, the Send activity, send a WCF request somewhere and the second, the ReceiveReply activity, receives the response. The same goes when receiving messages.
The good thing is that when we use the ReceiveAndSendReply or SendAndReceiveReply templates everything will be done for us and the required activity correlation will be configured automatically. However sometimes we might need to do so ourselves because we create a workflow in a different way, like using regular code.
So when we start using the Send and ReceiveReply or the Receive and SendReply activities from code we need to do a bit more work. How much varies and depends on both the workflow and the hosting environment we use. The big exception to the rule is when we use the WorkflowServiceHost and no parallel or overlapping messaging activities. In that case life is simple and the WorkflowServiceHost takes care of all required correlation for us. Nice 
That does leave for quite a few scenarios where we do need to add activity correlation though. The obvious one is using the WorkflowApplication or WorkflowInvoker to run our workflows. Fortunately this produces an System.InvalidOperationException with a message like:
The Send activity is configured with a request/reply Operation 'GetData', however, there is no ReceiveReply activity paired with it. Please pair Send with ReceiveReply and correlate them using a CorrelationHandle.
There are different ways to configure activity correlation but the easiest way is to wrap the activity pair in a CorrelationScope activity. The CorrelationScope will automatically create the required correlation handle and the messaging activities will use this handle.
Show me the code
The code below calls a very standard and basic WCF service passing in the value if 42 and prints the result. The important part is that the send are receive actions are done inside of a CorrelationScope activity so this workflow can run in any host.
static Activity CreateWorkflow()
{
var getDataResult = new Variable<string>();
var send = new Send()
{
OperationName = "GetData",
ServiceContractName = "IService1",
Endpoint = new Endpoint()
{
Binding = new BasicHttpBinding(),
AddressUri = new Uri("http://localhost:8080/GetDataService/")
},
Content = new SendParametersContent()
{
Parameters =
{
{"value", new InArgument<int>(42)}
}
}
};
var receive = new ReceiveReply()
{
Request = send,
Content = new ReceiveParametersContent
{
Parameters =
{
{"GetDataResult", new OutArgument<string>(getDataResult)}
}
}
};
var workflow = new Sequence()
{
Variables = { getDataResult },
Activities =
{
new CorrelationScope()
{
Body = new Sequence()
{
Activities = { send, receive }
}
},
new WriteLine() { Text = getDataResult } }
};
return workflow;
}
Update: See here for the code when using a RequestReplyCorrelationInitializer.
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu
Attached are the PowerPoint sheets and samples from my online Window Workflow Foundation 4 presentation today.
Let me know if you have any questions.
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu