The Problem Solver

Tell me and I will forget
Show me and I will remember
Involve me and I will understand
- Confucius -

 

Great tip from Dominick to quickly determine the use an ASP.NET, WCF or WF4 service is running under. Add the following ASPX file and hit it with the browser.

<%@ Page %>
 
<%= System.Security.Principal.WindowsIdentity.GetCurrent().Name %>

 

Thanks to Dominick

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , , , ,

In Windows Workflow Foundation 4 it’s easy to create a workflow and expose it as a WCF service. But one thing is that it exposes a XAMLX endpoint to each client can see the service actually implemented as a workflow service instead of a regular service. One way to hide that is to use a regular SVC file as the implementation and point that to a workflow using the WorkflowServiceHostFactory.

 

See the original XAMLX extension

image

 

To change this to an SVC extension we need to do a few things. First we need to use a regular workflow instead of a workflow service. The reason is a regular workflow is compiled into the assembly while a workflow service isn’t compiled. So first step is to add a Workflow1.xaml workflow and copy the content of the Service1.xamlx into it.

image

<Activity mc:Ignorable="sap" x:Class="DeclarativeServiceLibrary1.Workflow1" sap:VirtualizedContainerService.HintSize="317,384" mva:VisualBasic.Settings="Assembly references and imported namespaces for internal implementation" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:p="http://tempuri.org/" xmlns:p1="http://schemas.microsoft.com/netfx/2009/xaml/servicemodel" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Sequence DisplayName="Sequential Service" sad:XamlDebuggerXmlReader.FileName="c:\users\maurice\documents\visual studio 2010\Projects\DeclarativeServiceLibrary1\DeclarativeServiceLibrary1\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="277,344">
    <Sequence.Variables>
      <Variable x:TypeArguments="p1:CorrelationHandle" Name="handle" />
      <Variable x:TypeArguments="x:Int32" Name="data" />
    </Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <p1:Receive x:Name="__ReferenceID0" CanCreateInstance="True" DisplayName="ReceiveRequest" sap:VirtualizedContainerService.HintSize="255,90" OperationName="GetData" ServiceContractName="p:IService">
      <p1:Receive.CorrelationInitializers>
        <p1:RequestReplyCorrelationInitializer CorrelationHandle="[handle]" />
      </p1:Receive.CorrelationInitializers>
      <p1:ReceiveMessageContent>
        <OutArgument x:TypeArguments="x:Int32">[data]</OutArgument>
      </p1:ReceiveMessageContent>
    </p1:Receive>
    <p1:SendReply Request="{x:Reference __ReferenceID0}" DisplayName="SendResponse" sap:VirtualizedContainerService.HintSize="255,90">
      <p1:SendMessageContent>
        <InArgument x:TypeArguments="x:String">[data.ToString()]</InArgument>
      </p1:SendMessageContent>
    </p1:SendReply>
  </Sequence>
</Activity>

Next we add a WCF Service named Workflow1.svc. Delete the code behind file and add a factory pointing to “System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory”

<%@ ServiceHost Service="DeclarativeServiceLibrary1.Workflow1" 
Factory="System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory" %>

 

And that is all there is to it [:)]

 

Select the Workflow1.svc and press F5 to fire up the WCF Test Client to test the SVC worklfow.

image

 

A side benefit is that using the WorkflowServiceHostFactory allows us to derive from it and add our own logic to the WorkflowServiceHost like adding WorkflowExtensions.

 

Enjoy!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 4 comment(s)
Filed under: , , , ,

Whenever I download Visual Studio 2010 projects from the Internet and open the solution I am always prompted by the following message “Security Warning for [project name]. You should only open projects from a trustworthy source”. The warning makes sense because who knows what the code will do and we all run virus scanners for a good reason.

image

Its easy enough to suppress the warning for multiple projects in the same solution but next time you open the solution you get prompted with the same security warning.

 

Fixing this is quite easy, just select the project file in the Windows Explorer, right click and open the files properties and click the Unblock button to tell Windows, and Visual Studio, that you trust the file.

image 

Of course you better be sure that you can trust the source code [:)]

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 3 comment(s)
Filed under: ,

Yesterday I posted a long blog post explaining how to do duplex communications in a Workflow service. Its a long story but the most important points where that workflow services don’t support the same style duplex communication as WCF with the callback channel defined in the ServiceContract but rather something that is called durable duplex where the callback contract is independent and the client has to create a ServiceHost and act as a full-blown WCF service. Also the callback address had to be passed by the client using CallbackContextMessageProperty and the workflow service had to use a callback correlation handle to connect the Receive activity with the Send activity used for the callback.

 

So what is the problem with using the CallbackContextMessageProperty and CorrelationHandle?

There is no real problem per se except that it takes quite a bit of doing to set this up, both on the client and in the workflow service. Because standard WCF callbacks are not supported by workflow services using a ServiceHost to handle the callback on the client is unavoidable. This is unfortunate because this prevents several scenarios, most notably, using Silverlight for the client application. However all the complexity with the CallbackContextMessageProperty  and CorrelationHandle is only there to pass a single string, the callback address. And the Send activity has the capability to set the callback address dynamically using the EndpointAddress property.

 

Would it not be much simpler in this case to pass the callback address as a parameter to our initial service call? 

 

As it turns out it is! And in the rest of this post I will show how to do just that.

The starting point is the DuplexDemo solution from the previous post. You can download this code from here.

First thing is to add a string variable “callbackAddress” and update the Receive activity to accept this as a second parameter.

image

And we need to set the Send activity EndpointAddress to the passed address using “New Uri(callbackAddress)”

image

Next we can remove the callbackHandle we no longer need. Remove this from the variables, the Receive CorrelationInitializers and the Send activity CorrelatesWith. That is the service done. Next we need to remove some code from the client removing the OperationContextScope, CallbackContextMessageProperty and adding the callback address as the second parameter to the GetData() call. The code in the Main() function now looks like this:

static void Main(string[] args)
{
    var address = "http://localhost:8080/ServiceCallback";
    var serviceCallback = new ServiceCallback();
    var host = new ServiceHost(serviceCallback, new Uri(address));
 
    host.Open();
 
    var proxy = new ServiceClient();
    Console.WriteLine(proxy.GetData(42, address));
    Console.ReadLine();
 
    proxy.Close();
    host.Close();
}

 

So much simpler [:)]

 

But besides being simpler there is another benefit. Because the callback address is being passed as a normal piece of data instead of some hidden context header we can check if it is passed. So we can have our workflow check if a callback address is passed and if not just skip the callback altogether. This way the workflow service is still usable from clients, like Silverlight, where creating a ServiceHost is not an option. Of course they don’t receive the data being passed in the callback so that is something to keep in mind when deigning your workflow.

 

Conclusion.

I like the simpler approach this offers. It doesn’t mean that durable duplex should never be used, it certainly has its place, but when this simpler approach is all that is needed so much the better.

Sample project SimpleDuplexDemo.zip

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)

There are quite a a few cases where it is useful to have a duplex communications. An obvious candidate is allowing a service to notify the user interface application of the progress so the user knows what is happening. Normally, when using plain WCF, you would use a ServiceContract attribute with a CallbackContract that specified the service uses duplex communications. For example something like the following code on the server:

[ServiceContract(CallbackContract = typeof(IService1Callback))]
public interface IService1
{
    [OperationContract]
    string GetData(int value);
}
 
[ServiceContract]
public interface IService1Callback
{
    [OperationContract]
    void Reply(string message);
}

along with a client like this:

class Program
{
    static void Main(string[] args)
    {
        var callback = new Service1Callback();
        var proxy = new Service1Client(new InstanceContext(callback));
 
        Console.WriteLine(proxy.GetData(42));
 
        Console.ReadLine();
    }
}
 
class Service1Callback : IService1Callback
{
    public void Reply(string message)
    {
        Console.WriteLine(message);
    }
}

 

But with a workflow service this doesn’t work because, without the ServiceContract attribute, there is no way to specify the CallbackContract. Yet workflows support duplex communications. So if we can’t use a CallbackContract how can we do so?

 

Workflow Services and Durable Duplex

Instead of the normal WCF duplex services workflow services use a mechanism that is called durable duplex. This is a more disconnected way of doing duplex communications that has a big advantage in that both communications work independently of each other. And that means the usual drawbacks of duplex communications don’t apply here. We can even use a completely different binding on the callback than on the original request. There is however a downside as the client has to create its own ServiceHost and act as a complete WCF service, a little more involved than a normal duplex request. Also settings a workflow, and the client, up for durable duplex takes a bit more configuring. So lets take a look how to do so.

 

image

Creating the Workflow Service

First step is to create the workflow service. This starts just like any other WCF Workflow Service Application. The durable duplex mechanism requires a context binding so the first change to the project is to use the wsHttpContextBinding. An easy change using the WCF protocolMapping:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <protocolMapping>
      <add scheme="http" binding="wsHttpContextBinding" />
    </protocolMapping>
    <!-- Remainder deleted -->
  </system.serviceModel>
</configuration>

Next step is adding a client console application and making sure we can call the workflow service. Add a console application, add a service reference to the workflow service and add the following code to the Main() function:

static void Main(string[] args)
{
    var proxy = new ServiceClient();
    Console.WriteLine(proxy.GetData(42));
 
    Console.ReadLine();
}

No big deal so far. Now lets add the duplex part.

 

First of all we need to add a ServiceHost to the client exposing a callback contract and a callback service definition for the workflow service to call back to. Unlike a normal duplex service, where you define the contract on the service, we need to define this contract on the client using the following definition:

[ServiceContract]
interface IServiceCallback
{
    [OperationContract(IsOneWay = true)]
    void Update(string message);
}
 
[ServiceBehavior(InstanceContextMode= InstanceContextMode.Single)]
class ServiceCallback : IServiceCallback
{
    public void Update(string message)
    {
        Console.WriteLine(message);
    }
}

 

Next step is to update the Main() function to host this service:

static void Main(string[] args)
{
    var address = "http://localhost:8080/ServiceCallback";
    var serviceCallback = new ServiceCallback();
    var host = new ServiceHost(serviceCallback, new Uri(address));
 
    host.Open();
 
 
    var proxy = new ServiceClient();
    Console.WriteLine(proxy.GetData(42));
 
    Console.ReadLine();
 
    proxy.Close();
    host.Close();
}

Note that this looks just like a service would when self hosting.

 

Next step is to get the workflow service to call back to the client. For this we need to do a could of things. First of all we need to let the workflow service know where to call back to. This is done by adding a CallbackContextMessageProperty with the callback address to the request header.

This can be done with an OperationContextScope by replacing the proxy.GetDate() call with the following code in the Main() function:

var proxy = new ServiceClient();
using (new OperationContextScope((IContextChannel)proxy.InnerChannel))
{
    var context = new CallbackContextMessageProperty(address, new Dictionary<string, string>());
    OperationContext.Current.OutgoingMessageProperties.Add(CallbackContextMessageProperty.Name, context);
    Console.WriteLine(proxy.GetData(42));
}

 

Next we need to update the workflow itself.

The first thing we need to add is a CorrelationHandle variable to hold the callback correlation.

image

Next we need to initialize the callbackHandle using the CorrelationInitializer on the GetData Receive activity.

image

Next we need a Send activity to send the response to the client setting its CorrelatesWith to the same callbackHandle just initialized.

image

Finally we set the Send activity properties to match the client callback service as follows:

image

Note that we leave both the AddressUri and EndpointAddress empty, there is no config either, because this is provide through the request context and the callback correlation handle. We are also setting and set the binding to basicHttpBinding because that was what the client was using.

And finally we need to add a message to pass back like this:

image

So with all this complete we can run our client and service and have the service call back into the client.

image

 

A few things that might trip you up.

Forgetting to set the callback CorrelationHandle results in no messages being sent to the client. In fact an InvalidOperationException occurs with message "Endpoint with Name='<not specified>' and ServiceContract '<not specified>' has a null or empty Uri property. A Uri for this Endpoint must be provided.". This is caused by the Send activity not being able to find its callback address.

Not passing an empty context from the client. The CallbackContextMessageProperty has a few constructor overloads, one just taking a single EndpointAddress and no context parameter. Seems like a good choice as we don’t need to specify any context. Unfortunately this results in an ArgumentNullException with message "Value cannot be null. Parameter name: context". Again when the Send activity tries to find its callback address

 

How about Silverlight?

And the is of course the biggest problem of them all: what if you can’t create a ServiceHost on the client? In that case you are done for and there is no way to use this durable duplex mechanism. And I guess that rules Silverlight out as a possible client [:(]

 

Conclusion

All in all this works but it is kind of hard to setup the first time because of all the intricacies with the CallbackContextMessageProperty and the callback correlation handle.

Sample project DuplexDemo.zip

Enjoy

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Reblog this post [with Zemanta]
Posted by Maurice | 3 comment(s)

With WF4 it is quite easy to use data that is part of the request message, for example an order identifier, to route multiple WCF messages to the same workflow. It is also possible to have multiple messages that can start a new workflow. In this blog post I am going to show both these concepts in a single workflow. This workflow is going to be able to receive three different messages, AddItem, AddExpensiveItem and Submit.  Either AddItem or AddExpensiveItem can start a new workflow while Submit can only be done with an existing workflow.

The final workflow looks like this±

image

 

To create the workflow you need to start by creating a new WCF Workflow Service Application in Visual Studio 2010. Next delete the default activities that have been created by the template and replace it with a Sequence activity. Add a variable named TotalAmount of type Int32 and a Boolean variable named OrderSubmitted. Add a DoWhile activity and set its condition to OrderSubmitted = false so it will keep on executing until we set the OrderSubmitted variable to true.

So much for the boring part, now we get to the real message part.

First we need to add a Pick activity. This is needed because we want to receive multiple different messages without specifying an order. Add a third PickBrach so we get a total of three branches and a a Receive activity to each Trigger. Set all three ServiceContractName’s to OrderService and set the OperationName to AddItem, AddExpensiveItem and Submit respectively. Now the AddItem and AddExpensiveItem requests are allowed to start a new workflow so check the CanCreateInstance for both of them.

Next the important part for the correlation bit. All three requests need to share some common piece of data.Below is the Content definition of the AddItem request.

image

Both the AddExpensiveItem and Submit requests only contain the orderId parameter

image

The important is that they all share the same orderId that the client needs to provide.

 

That takes care of sending some common data but we still need to configure the workflow to use that to correlate requests to the same workflow. This last part is done using the CorrelatesOn property of the Receive activity. In all three point this to the orderId being passed in and make sure all three have a matching key.

image

 

After we have added the processing part to the Action part of each PickBranch we are all set to go.

Note: to create the SendReply, just right click the Receive activity and select Create SendReply. This will be placed on the clipboard so now you can paste it into the Action Sequence.

 

Running the workflow

Press F5 to activate the WCF Test Client. You should see three possible actions, either AddItem or AddExpensiveItem can start a workflow or send messages to an existing workflow. It just depends on whether the orderId passed in can be correlated to an existing workflow. If so that workflow receives the message, otherwise a new workflow is started.

image

The Submit message on the other hand can only be executed with an existing workflow.

 

 

Just in case anyone is interested below is the complete XAML for the workflow:

<WorkflowService mc:Ignorable="sap" ConfigurationName="Service1" sap:VirtualizedContainerService.HintSize="1193,850" Name="Service1" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/servicemodel" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:ssa="clr-namespace:System.ServiceModel.Activities;assembly=System.ServiceModel.Activities" xmlns:ssx="clr-namespace:System.ServiceModel.XamlIntegration;assembly=System.ServiceModel" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <p:Sequence sad:XamlDebuggerXmlReader.FileName="c:\users\maurice\documents\visual studio 2010\Projects\DeclarativeServiceLibrary1\DeclarativeServiceLibrary1\Service1.xamlx" sap:VirtualizedContainerService.HintSize="1163,820" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces">
    <p:Sequence.Variables>
      <p:Variable x:TypeArguments="x:Int32" Name="TotalAmount" />
    </p:Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <p:DoWhile sap:VirtualizedContainerService.HintSize="1141,696">
      <p:DoWhile.Variables>
        <p:Variable x:TypeArguments="x:Boolean" Name="OrderSubmitted" />
      </p:DoWhile.Variables>
      <p:DoWhile.Condition>[OrderSubmitted = False]</p:DoWhile.Condition>
      <p:Pick sap:VirtualizedContainerService.HintSize="1115,576">
        <p:PickBranch DisplayName="Branch1" sap:VirtualizedContainerService.HintSize="307,530">
          <p:PickBranch.Variables>
            <p:Variable x:TypeArguments="CorrelationHandle" Name="__handle1" />
            <p:Variable x:TypeArguments="x:Int32" Name="Price" />
          </p:PickBranch.Variables>
          <p:PickBranch.Trigger>
            <x:Reference>__ReferenceID0</x:Reference>
          </p:PickBranch.Trigger>
          <p:Sequence sap:VirtualizedContainerService.HintSize="277,312">
            <sap:WorkflowViewStateService.ViewState>
              <scg3:Dictionary x:TypeArguments="x:String, x:Object">
                <x:Boolean x:Key="IsExpanded">True</x:Boolean>
              </scg3:Dictionary>
            </sap:WorkflowViewStateService.ViewState>
            <p:Assign sap:VirtualizedContainerService.HintSize="255,58">
              <p:Assign.To>
                <p:OutArgument x:TypeArguments="x:Int32">[TotalAmount]</p:OutArgument>
              </p:Assign.To>
              <p:Assign.Value>
                <p:InArgument x:TypeArguments="x:Int32">[TotalAmount + Price]</p:InArgument>
              </p:Assign.Value>
            </p:Assign>
            <SendReply DisplayName="SendReplyToReceive" sap:VirtualizedContainerService.HintSize="255,90">
              <SendReply.Request>
                <Receive x:Name="__ReferenceID0" CanCreateInstance="True" sap:VirtualizedContainerService.HintSize="277,100" OperationName="AddItem" ServiceContractName="OrderService">
                  <Receive.CorrelatesOn>
                    <XPathMessageQuery x:Key="key1">
                      <XPathMessageQuery.Namespaces>
                        <ssx:XPathMessageContextMarkup>
                          <x:String x:Key="xgSc">http://tempuri.org/</x:String>
                        </ssx:XPathMessageContextMarkup>
                      </XPathMessageQuery.Namespaces>sm:body()/xgSc:AddItem/xgSc:orderId</XPathMessageQuery>
                  </Receive.CorrelatesOn>
                  <Receive.CorrelationInitializers>
                    <RequestReplyCorrelationInitializer CorrelationHandle="[__handle1]" />
                  </Receive.CorrelationInitializers>
                  <ReceiveParametersContent>
                    <p:OutArgument x:TypeArguments="x:Int32" x:Key="orderId" />
                    <p:OutArgument x:TypeArguments="x:String" x:Key="item" />
                    <p:OutArgument x:TypeArguments="x:Int32" x:Key="price">[Price]</p:OutArgument>
                  </ReceiveParametersContent>
                </Receive>
              </SendReply.Request>
              <SendParametersContent>
                <p:InArgument x:TypeArguments="x:Int32" x:Key="Total">[TotalAmount]</p:InArgument>
              </SendParametersContent>
            </SendReply>
          </p:Sequence>
        </p:PickBranch>
        <p:PickBranch sap:VirtualizedContainerService.HintSize="307,530">
          <p:PickBranch.Variables>
            <p:Variable x:TypeArguments="CorrelationHandle" Name="__handle1" />
          </p:PickBranch.Variables>
          <p:PickBranch.Trigger>
            <x:Reference>__ReferenceID1</x:Reference>
          </p:PickBranch.Trigger>
          <p:Sequence sap:VirtualizedContainerService.HintSize="277,312">
            <sap:WorkflowViewStateService.ViewState>
              <scg3:Dictionary x:TypeArguments="x:String, x:Object">
                <x:Boolean x:Key="IsExpanded">True</x:Boolean>
              </scg3:Dictionary>
            </sap:WorkflowViewStateService.ViewState>
            <p:Assign sap:VirtualizedContainerService.HintSize="255,58">
              <p:Assign.To>
                <p:OutArgument x:TypeArguments="x:Int32">[TotalAmount]</p:OutArgument>
              </p:Assign.To>
              <p:Assign.Value>
                <p:InArgument x:TypeArguments="x:Int32">[TotalAmount + 100]</p:InArgument>
              </p:Assign.Value>
            </p:Assign>
            <SendReply DisplayName="SendReplyToReceive" sap:VirtualizedContainerService.HintSize="255,90">
              <SendReply.Request>
                <Receive x:Name="__ReferenceID1" CanCreateInstance="True" sap:VirtualizedContainerService.HintSize="277,100" OperationName="AddExpensiveItem" ServiceContractName="OrderService">
                  <Receive.CorrelatesOn>
                    <XPathMessageQuery x:Key="key1">
                      <XPathMessageQuery.Namespaces>
                        <ssx:XPathMessageContextMarkup>
                          <x:String x:Key="xgSc">http://tempuri.org/</x:String>
                        </ssx:XPathMessageContextMarkup>
                      </XPathMessageQuery.Namespaces>sm:body()/xgSc:AddExpensiveItem/xgSc:orderId</XPathMessageQuery>
                  </Receive.CorrelatesOn>
                  <Receive.CorrelationInitializers>
                    <RequestReplyCorrelationInitializer CorrelationHandle="[__handle1]" />
                  </Receive.CorrelationInitializers>
                  <ReceiveParametersContent>
                    <p:OutArgument x:TypeArguments="x:Int32" x:Key="orderId" />
                  </ReceiveParametersContent>
                </Receive>
              </SendReply.Request>
              <SendParametersContent>
                <p:InArgument x:TypeArguments="x:Int32" x:Key="Total">[TotalAmount]</p:InArgument>
              </SendParametersContent>
            </SendReply>
          </p:Sequence>
        </p:PickBranch>
        <p:PickBranch DisplayName="Branch2" sap:VirtualizedContainerService.HintSize="307,530">
          <p:PickBranch.Variables>
            <p:Variable x:TypeArguments="CorrelationHandle" Name="__handle2" />
          </p:PickBranch.Variables>
          <p:PickBranch.Trigger>
            <x:Reference>__ReferenceID2</x:Reference>
          </p:PickBranch.Trigger>
          <p:Sequence sap:VirtualizedContainerService.HintSize="277,312">
            <sap:WorkflowViewStateService.ViewState>
              <scg3:Dictionary x:TypeArguments="x:String, x:Object">
                <x:Boolean x:Key="IsExpanded">True</x:Boolean>
              </scg3:Dictionary>
            </sap:WorkflowViewStateService.ViewState>
            <SendReply DisplayName="SendReplyToReceive" sap:VirtualizedContainerService.HintSize="255,90">
              <SendReply.Request>
                <Receive x:Name="__ReferenceID2" sap:VirtualizedContainerService.HintSize="277,100" OperationName="Submit" ServiceContractName="OrderService">
                  <Receive.CorrelatesOn>
                    <XPathMessageQuery x:Key="key1">
                      <XPathMessageQuery.Namespaces>
                        <ssx:XPathMessageContextMarkup>
                          <x:String x:Key="xgSc">http://tempuri.org/</x:String>
                        </ssx:XPathMessageContextMarkup>
                      </XPathMessageQuery.Namespaces>sm:body()/xgSc:Submit/xgSc:orderId</XPathMessageQuery>
                  </Receive.CorrelatesOn>
                  <Receive.CorrelationInitializers>
                    <RequestReplyCorrelationInitializer CorrelationHandle="[__handle2]" />
                  </Receive.CorrelationInitializers>
                  <ReceiveParametersContent>
                    <p:OutArgument x:TypeArguments="x:Int32" x:Key="orderId" />
                  </ReceiveParametersContent>
                </Receive>
              </SendReply.Request>
            </SendReply>
            <p:Assign sap:VirtualizedContainerService.HintSize="255,58">
              <p:Assign.To>
                <p:OutArgument x:TypeArguments="x:Boolean">[OrderSubmitted]</p:OutArgument>
              </p:Assign.To>
              <p:Assign.Value>
                <p:InArgument x:TypeArguments="x:Boolean">True</p:InArgument>
              </p:Assign.Value>
            </p:Assign>
          </p:Sequence>
        </p:PickBranch>
      </p:Pick>
    </p:DoWhile>
  </p:Sequence>
</WorkflowService>

 

Enjoy!

 

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 6 comment(s)
Filed under: , , , ,

Recently the workflow team at Microsoft released a first CTP to the ADO.NET Activity Pack. In this blog post I am going to take a quick look at how to get started with these activities.

 

First step is installing the activity pack. Download the setup MSI from http://wf.codeplex.com/releases/view/43585 and run the installer. This will install the activities in the folder C:\Program Files (x86)\Microsoft WF ADO.NET Activity Pack\CTP 1 and register them with VS2010 so they automatically appear in the toolbox. Note that the main assembly containing the activities is called Microsoft.Data.Activities.dll.

If we start VS2010 and create a new workflow project the 3 ADO.NET activities are right there in the toolbox.

image

 

All three activities operate in a similar manner so I am going to use the ExecuteSqlQuery<T> to demonstrate how to use these activities. The sample is simple, all I am going to do is load a set of customers from the Northwind database and display these on the screen.

First step is to define a customer type the workflow can work with. This is as simple as they come:

public class Customer
{
    public string CustomerId { get; set; }
    public string CompanyName { get; set; }
    public string ContactName { get; set; }
 
}

Next you can drag a ExecuteSqlQuery<T> onto the design surface. I put them inside of a Sequence so I can print the result after the ExecuteSqlQuery<T> is finished. When prompted for the type select Brose for Types … and select the Customer from the current project.

image

 

Now the activity is on the design surface but as can be seen from the Error List both the connection and the command need to be specified.

 

First the connection because that is a bit misleading, at least for me the first time I tried.

Clicking the connection button in the property sheet allows you to use an existing database connection or create a new one. Nice but that does mean your connection string will be part of the workflow, not so good, and is a literal instead of an expression so not easy to change. The proper way to do this is to add a connection string setting to the project first. And once that is done this connection will also appear in the Configure Database Connection dialog for the ExecuteSqlQuery<T>,

image

Next step is to specify the command to execute.

image

Here we are doing a simple select from the customers table and filtering the results to only include the UK based companies. All we have is a simple textbox to enter a SQL command, table or stored procedure name. Quite primitive [:(]. Keep in mind that the select * is quite a bad idea as well and you are far better of specifying the field names as we will see in a bit.

image

Next we need to map the data loaded to our customer type. For this purpose we get a record variable that points to a SqlDataReader and we can use the record.GetString(index) and similar functions to load the data. This requires passing an index, the reason doing a SELECT * is not a smart mover

 

image

When this is done all we need to do is capture the Result property, which contains a List<Customer> and loop over this printing the content.

image

 

The complete workflow in XAML:

<Activity mc:Ignorable="sap" x:Class="WorkflowConsoleApplication7.Workflow1" sap:VirtualizedContainerService.HintSize="356,1058" mva:VisualBasic.Settings="Assembly references and imported namespaces for internal implementation" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:local="clr-namespace:WorkflowConsoleApplication7" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mda="clr-namespace:Microsoft.Data.Activities;assembly=Microsoft.Data.Activities" xmlns:mv="clr-namespace:Microsoft.VisualBasic;assembly=System" xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.Activities" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:s1="clr-namespace:System;assembly=System" xmlns:s2="clr-namespace:System;assembly=System.Xml" xmlns:s3="clr-namespace:System;assembly=System.Core" xmlns:s4="clr-namespace:System;assembly=System.ServiceModel" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic;assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Sequence sad:XamlDebuggerXmlReader.FileName="c:\users\maurice\documents\visual studio 2010\Projects\WorkflowConsoleApplication7\WorkflowConsoleApplication7\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="316,1018">
    <Sequence.Variables>
      <Variable x:TypeArguments="scg3:List(local:Customer)" Name="Customers" />
    </Sequence.Variables>
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <mda:ExecuteSqlQuery x:TypeArguments="local:Customer" ProviderName="{x:Null}" CommandText="Select * from Customers where Country = @Country" ConnectionConfigurationName="WorkflowConsoleApplication7.Properties.Settings.NorthwindConnection" sap:VirtualizedContainerService.HintSize="294,647" Result="[Customers]">
      <mda:ExecuteSqlQuery.RecordProcessor>
        <ActivityFunc x:TypeArguments="sd:IDataRecord, local:Customer">
          <ActivityFunc.Argument>
            <DelegateInArgument x:TypeArguments="sd:IDataRecord" Name="record" />
          </ActivityFunc.Argument>
          <ActivityFunc.Result>
            <DelegateOutArgument x:TypeArguments="local:Customer" Name="target" />
          </ActivityFunc.Result>
          <Sequence DisplayName="Map Customer Fields" sap:VirtualizedContainerService.HintSize="264,476">
            <sap:WorkflowViewStateService.ViewState>
              <scg3:Dictionary x:TypeArguments="x:String, x:Object">
                <x:Boolean x:Key="IsExpanded">True</x:Boolean>
                <x:Boolean x:Key="IsPinned">False</x:Boolean>
              </scg3:Dictionary>
            </sap:WorkflowViewStateService.ViewState>
            <Assign sap:VirtualizedContainerService.HintSize="242,58">
              <Assign.To>
                <OutArgument x:TypeArguments="local:Customer">[target]</OutArgument>
              </Assign.To>
              <Assign.Value>
                <InArgument x:TypeArguments="local:Customer">[New Customer]</InArgument>
              </Assign.Value>
            </Assign>
            <Assign sap:VirtualizedContainerService.HintSize="242,58">
              <Assign.To>
                <OutArgument x:TypeArguments="x:String">[target.CustomerId]</OutArgument>
              </Assign.To>
              <Assign.Value>
                <InArgument x:TypeArguments="x:String">[record.GetString(0)]</InArgument>
              </Assign.Value>
            </Assign>
            <Assign sap:VirtualizedContainerService.HintSize="242,58">
              <Assign.To>
                <OutArgument x:TypeArguments="x:String">[target.CompanyName]</OutArgument>
              </Assign.To>
              <Assign.Value>
                <InArgument x:TypeArguments="x:String">[record.GetString(1)]</InArgument>
              </Assign.Value>
            </Assign>
            <Assign sap:VirtualizedContainerService.HintSize="242,58">
              <Assign.To>
                <OutArgument x:TypeArguments="x:String">[target.ContactName]</OutArgument>
              </Assign.To>
              <Assign.Value>
                <InArgument x:TypeArguments="x:String">[record.GetString(2)]</InArgument>
              </Assign.Value>
            </Assign>
          </Sequence>
        </ActivityFunc>
      </mda:ExecuteSqlQuery.RecordProcessor>
      <InArgument x:TypeArguments="x:String" x:Key="Country">UK</InArgument>
    </mda:ExecuteSqlQuery>
    <ForEach x:TypeArguments="local:Customer" DisplayName="ForEach&lt;Customer&gt;" sap:VirtualizedContainerService.HintSize="294,207" Values="[Customers]">
      <ActivityAction x:TypeArguments="local:Customer">
        <ActivityAction.Argument>
          <DelegateInArgument x:TypeArguments="local:Customer" Name="item" />
        </ActivityAction.Argument>
        <WriteLine sap:VirtualizedContainerService.HintSize="257,100" Text="[item.CustomerId &amp; &quot; &quot; &amp; item.CompanyName &amp; &quot; &quot; &amp; item.ContactName]" />
      </ActivityAction>
    </ForEach>
  </Sequence>
</Activity>

and in design mode:

image

 

Conclusion

These activities work well enough but result in using plain old ADO.NET. Now there is nothing wrong with plain ADO.NET but with the current emphasis on Object Relational Mappers like the Entity Framework this seems a bit out of place. And the option to use connection strings from the application settings instead of embedding them in the workflow isn’t all that obvious either. Still a nice set of activities to add to the toolbox.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)

Note: This blog post is written using Silverlight 4.0 RC 1

One of the cool new features in Silverlight 4 is the ability to data bind to indexed properties. This means that even if you don’t know at design time what properties you data object has you can still data bind to them.

The syntax is very similar to a normal data binding, only in this case you need to use the [key] syntax instead. For example in example below the FirstName is a regular property while the LastName below is an indexed property.

<StackPanel Name="LayoutRoot">
    <StackPanel Orientation="Horizontal" Margin="1">
        <TextBlock Text="FirstName" Width="150"></TextBlock>
        <TextBox Text="{Binding FirstName, Mode=TwoWay}" Width="500"></TextBox>
    </StackPanel>
    <StackPanel Orientation="Horizontal" Margin="1">
        <TextBlock Text="LastName" Width="150"></TextBlock>
        <TextBox Text="{Binding [LastName], Mode=TwoWay}" Width="500"></TextBox>
    </StackPanel>
</StackPanel>

 

Creating the class to data bind to is simple. All you need to do is add a property with the following syntax:

public object this[string key]
    { get; set; }

 

In this example I am using a Peron class with a regular FirstName property and all others are dong using indexed properties. The complete class, including INotifyPropertyChanged looks like this:

public class Person : INotifyPropertyChanged
{
    public Person()
    {
        PropertyChanged = (s, e) => { };
 
        FirstName = "Maurice";
        this["LastName"] = "de Beijer";
        this["BabyName"] = "Kai";
    }
 
    private Dictionary<string, object> _data = new Dictionary<string, object>();
    public object this[string key]
    {
        get
        {
            if (!_data.ContainsKey(key))
                _data[key] = null;
 
            return _data[key];
        }
        set
        {
            _data[key] = value;
            PropertyChanged(this, new PropertyChangedEventArgs(""));
        }
    }
 
    public IEnumerable<string> Keys
    {
        get
        {
            return _data.Keys;
        }
    }
 
    private string _firstName;
    public string FirstName
    {
        get
        {
            return _firstName;
        }
        set
        {
            _firstName = value;
            PropertyChanged(this, new PropertyChangedEventArgs("FirstName"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}

 

While not having to know the objects structure at design time is nice but in the XAML above I still hard coded the data bindings so that doesn’t buy us much yet. So fully utilize this we need to dynamically generate the UI as well. Fortunately that is quite easy to do with the following code:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    var person = new Person();
    DataContext = person;
 
    foreach (var item in person.Keys)
    {
        var lbl = new TextBlock();
        lbl.Text = item;
        lbl.Width = 150;
 
        var txt = new TextBox();
        var binding = new Binding("[" + item + "]");
        binding.Mode = BindingMode.TwoWay;
        txt.SetBinding(TextBox.TextProperty, binding);
        txt.Width = 500;
 
        var line = new StackPanel();
        line.Orientation = Orientation.Horizontal;
        line.Children.Add(lbl);
        line.Children.Add(txt);
        line.Margin = new Thickness(1);
        LayoutRoot.Children.Add(line);
    }
}

And the result looks like this. The LastName is printed twice because it is both hard coded into the XAML and added dynamically.

image

 

A few gotchas I ran into.

While doing this I experimented with using a Dictionary<string, object> as the DataContext itself. That worked file except for the part that this doesn’t implement INotifyPropertyChanged. So i tried to create a Peron class deriving from Dictionary<string, object> and adding a indexer to that but this doesn’t work, data binding completely fails to load the data. The same was try for my own implementation of IDictionary<string, object>.

Another thing was the property name that is used with the INotifyPropertyChanged.PropertyChanged event. It seems using an empty string as the property name is the only thing that works to get other controls to update their binding. Not a big issue but I caught me at first.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 4 comment(s)
Filed under: , , ,
WWF Logo

Image by ponChiang via Flickr

Note: This blog post is written using the .NET framework 4.0 RC 1

 

Using the ReceiveAndSendReply activity template and the WorkflowServiceHost it is easy to create a workflow service. Other applications can communicate with the workflow just as if it is a regular WCF service, they never need to know the difference. Most of the configuration is quite straightforward. Select the Receive activity and configure it and do the same with the SendReply activity. Most the settings normally done using a ServiceContract or an OperationContract are there.

But what about faults?

Turns out there is no property anywhere to specify anything about a possible fault being returned.

 

Throwing a FaultException

When a FaultException is thrown in a workflow service pretty much the expected thing is done. The exception is send to the client as a soap fault and the client, assuming .NET here, can catch it as a FaultException. No big deal so far.

How about a FaultException<T>?

Normally when you want to add extra information with a fault you create an extra class and decorate the service contract with a FaultContract attribute specifying the fault returned. This gets added to the service metadata and doing an Add Service Reference creates the required type on the client. But with a workflow service there is no service contract class we can add the attribute to. And there is no property we can use for this purpose either. So how do we specify the fault contract with a workflow service?

The trick is to add a second SendReply to the workflow associated with the same Receive activity. The second SendReply activity returns the fault contract and this is how it gets into the workflow service metadata. Adding the second SendReply is easy, just right click the Receive activity and select Create SendReply and the activity is there. Next create a variable to hold the fault information.

image

The FaultDetail I am using is real simple, all it does is send the data received back to the client.

using System.Runtime.Serialization;
 
namespace ServicesAndFaults
{
    [DataContract]
    public class FaultDetail
    {
        [DataMember]
        public int Data { get; set; }
    }
}

 

With this second SendReply activity, creating and throwing a FaultException the workflow looks like this:

image

With the following expression used to initialize the fault

image

The SendReply activity that is returning the error is configured like this:

image

With this setup adding a Service Reference from a client application and using the following code has the expected result

static void Main(string[] args)
{
    var proxy = new ServiceClient();
 
    try
    {
        Console.WriteLine(proxy.GetData(42));
    }
    catch (FaultException<FaultDetail> ex)
    {
        Console.WriteLine("FaultException<FaultDetail> with {0}", ex.Detail.Data);
    }
    catch (FaultException ex)
    {
        Console.WriteLine(ex);
    }
 
    Console.ReadLine();
}

 

image

The output from the workflow in the service console application look like this. Note that the last message, of the workflow finishing, never appears. Not really surprising as we throw an error that is never caught and terminates the workflow.

image

 

But I want to keep my workflow alive!

Sometimes the behavior above is just fine but at other times it is not. Suppose you have been working on an order for months do you really want to terminate all that work just because of a single exception? Probably not.

The second way to return a fault to the calling client is just to return the FaultException or FaultException<T>  from the SendReply activity used to define it. The client will never know the difference but the workflow will keep on running perfectly happy. So all I needed to do is remove the Throw activity and let the second SendReply do its work.

This time the service console application output look like this:

image

 

Nice to know but this feature of Windows Workflow Foundation 4 does leave something to be desired in the discoverability department. Here is another document describing how to use this feature.

 

Download the source code ServicesAndFaults.zip

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Reblog this post [with Zemanta]
Posted by Maurice | 1 comment(s)
Filed under: , , , , ,
The Logitech iFeel optical mouse uses a red LE...

Image via Wikipedia

One of the things developers often have to do is replace a single activity with multiple ones. This requires replacing the original activity with a Sequence and then adding the original activity to the sequence. When developing UI in Blend the same kind of operation is often required to wrap an element inside of a StackPanel, Border or something similar. In Blend this is very easy to do as there is a context menu for just this action. So all that is needed is right click and wrap something in the container of choice.

I would like to see the same functionality in the workflow designer where the user can select an activity and wrap it in a Sequence, TryCatch, TransactionScope and other relevant activities. I would also like to see the opposite where a developer can remove a wrapping Sequence is there are no other child activities.

 

reflections_blend_02[1] 


Do you think this would be a useful feature to have? Then please go here and vote for it!

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Reblog this post [with Zemanta]
Posted by Maurice | 1 comment(s)
Filed under: , ,

Note: This blog post is written using the .NET framework 4.0 RC 1

 

Most of the time I used compiled workflows in Windows Workflow Foundation 4. Its nice and easy, you design the workflow, compile it and at runtime there is a .NET type you use to create and run workflows. The main drawback is that this approach isn’t very flexible, sometimes you want to be able to change your workflow definition at runtime or store it in a database so recompiling isn’t an option.

Fortunately we can also load a workflow from the XAML file itself and execute the resulting workflow activity. This is done using the ActivityXamlServices class that will let us load the XAML file and return an activity, to be exact it returns a DynamicActivity as a wrapper around your definition.

The simplest option is just to call Load() passing in the file name like this:

Activity workflow = ActivityXamlServices.Load("YourWorkflow.xaml");

 

If you are using activities, or other types, from the local assembly this is going to fail though and you need a slightly more verbose way of doing thing like this:

var settings = new XamlXmlReaderSettings()
{
    LocalAssembly = typeof(SendForManualApproval).Assembly
};
var reader = new XamlXmlReader("YourWorkflow.xaml", settings);
Activity workflow = ActivityXamlServices.Load(reader);

 

We need to use the XamlXmlReaderSettings to indicate what the local assembly reference in the XAML is.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 1 comment(s)
Filed under: , , , ,

Note: This blog post is written using the .NET framework 4.0 RC 1

Using Workflow Foundation 4 the NativeActivity is the powerhouse when it comes to building native activities. One of its many capabilities is around error handling. Every so often I run into one of these things where things don’t quite work the way I expect them to and this is one of these cases.

 

The basics of error handling when scheduling child activities.

Whenever a NativeActivity is executed it is passed an instance of the NativeActivityContext which it can use to schedule other activities using the ScheduleActivity() function. This ScheduleActivity() function has a few overloads, one of them using an FaultCallback. This FaultCallback is called when some kind of exception occurs while executing the child activity being scheduled. The fault handling function is called with a couple of parameters including a NativeActivityFaultContext and the exception that is unhandled. The NativeActivityFaultContext contains a HandleFault() function used to indicate that the fault was handled. Not quite as straightforward as a try/catch block but given the asynchronous nature of workflow that would not work.

So I expected the following activity to catch any exceptions and continue.

 

public sealed class MyActivity : NativeActivity
{
    public Activity Body { get; set; }
 
    protected override void Execute(NativeActivityContext context)
    {
        context.ScheduleActivity(Body, FaultHandler);
    }
 
    private void FaultHandler(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
    {
        Console.WriteLine(propagatedException.Message);
        faultContext.HandleFault();
    }
}
 

Do not use, this code has a serious error!

 

Lets test this code by executing the following workflow:

private static Activity CreateWorkflow()
{
    return new Sequence
    {
        Activities =
        {
            new WriteLine { Text = "Start outer sequence." },
            new MyActivity 
            {
                Body = new Sequence
                {
                    Activities = 
                    {
                        new WriteLine { Text = "Start inner sequence." },
                        new Throw { Exception = new InArgument<Exception>(ctx => new DivideByZeroException()) },
                        new WriteLine { Text = "End inner sequence." }
                    }
                }
            },
            new WriteLine { Text = "End outer sequence." }
        }
    };
}

 

Given this workflow I would expect the following output:

image

 

However what really happens is something else as I receive the following output:

image

As we can see the second inner WriteLine still executes even though the exception is caught at a higher level!

 

This behavior reminds me of the infamous VB6 On Error Resume Next where an error would just be ignored and the next statement executed. Not really what I was expecting or want.

So the fix is easy. All that is needed is to explicitly cancel the child activity being executed using the CancelChild() function. Below the correct version of my NativeActivity.

public sealed class MyActivity : NativeActivity
{
    public Activity Body { get; set; }
 
    protected override void Execute(NativeActivityContext context)
    {
        context.ScheduleActivity(Body, FaultHandler);
    }
 
    private void FaultHandler(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
    {
        Console.WriteLine(propagatedException.Message);
        faultContext.HandleFault();
        faultContext.CancelChild(propagatedFrom);
    }
}

The correct fault handler

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , , ,

During last years CodeCamp in Rotterdam I recorded my presentation on doing iPhone development using jQTouch and ASP.NET MVC. After looking at several options I have posted the recording on Vimeo. You can view the Dutch language recording here.

If you are interested in doing iPhone development but don’t want to get started using Objective-C that jQTouch might just be what you need. Its a cool way to build HTML 5 and JavaScript application for the iPhone. And, thanks to HTML 5, it will even let you take the application and data offline if you want that. Best of all you can deploy them without going through the Apple AppStore if you prefer not to. And if you still want to go the AppStore route a product like PhoneGap will even let you do that.

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 3 comment(s)

Note: This blog post is written using the .NET framework 4.0 Beta 2

In this previous blog post I showed how to create an asynchronous activity using the NativeActivity and CreateBookmark to pause a workflow execution. Using that in a WorkflowApplication was easy but what about WorkflowInvoker or WorkflowServiceHost?

 

So what about using a WorkflowInvoker or a WorkflowServiceHost?

Neither the WorkflowInvoker not the WorkflowServiceHost contain a ResumeBookmark function so how do we resume a bookmark using either of those execution hosts? The trick is to use a IWorkflowInstanceExtension.

 

Using a IWorkflowInstanceExtension

Workflow extensions can be of any type you want, there is no base class or interface requirement. But that also means they are not aware of the workflow runtime environment and can’t do much more that respond to calls from a workflow. The way do be able to do a little more is by implementing the IWorkflowInstanceExtension interface. This interface has just two methods. One of those, the SetInstance, is passed a wrapper object around the running workflow allowing us do do some more work. The most important is that this also lets us resume bookmarks.

My workflow extension basically waits for the activity to call it and once done wait one second and resume the bookmark. A

class MyExtension : IWorkflowInstanceExtension
{
    private WorkflowInstanceProxy _instance;
    public IEnumerable<object> GetAdditionalExtensions()
    {
        return null;
    }
 
    public void SetInstance(WorkflowInstanceProxy instance)
    {
        _instance = instance;
    }
 
 
    public void WaitSome(Bookmark bookmark)
    {
        ThreadPool.QueueUserWorkItem(state =>
            {
                Thread.Sleep(1000);
                var ias = _instance.BeginResumeBookmark(bookmark, 42, null, null);
 
                var result = _instance.EndResumeBookmark(ias);
                Console.WriteLine("BookmarkResumptionResult: '{0}'", result);
            });
    }
 
}

Note that the GetAdditionalExtensions() function just returns null as we are not adding extra extensions.

 

The new version of the activity

With the required extension done we still need to make sure it gets added to the workflow runtime of choice. The easiest option is to let the activity itself do so in the CacheMetadata() function by using the AddDefaultExtensionProvider() function. The new activity looks like this:

public class MyBookmarkedActivity : NativeActivity
{
 
    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);
        metadata.AddDefaultExtensionProvider<MyExtension>(() => new MyExtension());
    }
 
    protected override bool CanInduceIdle
    {
        get { return true; }
    }
 
    protected override void Execute(NativeActivityContext context)
    {
        var bookmark = context.CreateBookmark("MyBookmark", BookmarkResumed);
        var extension = context.GetExtension<MyExtension>();
        extension.WaitSome(bookmark);
 
    }
 
 
    private void BookmarkResumed(NativeActivityContext context, Bookmark bookmark, object value)
    {
        Console.WriteLine("Bookmark resumed with '{0}'.", value);
    }
}

 

Now this is all we need to run the activity as it will automatically add the required extension:

WorkflowInvoker.Invoke(new MyBookmarkedActivity());

or when using a WorkflowServiceHost

var wsh = new WorkflowServiceHost(new MyBookmarkedActivity());
wsh.AddDefaultEndpoints();
wsh.Open();
Console.WriteLine("Listening");
Console.ReadLine();
wsh.Close();

And just in case you where wondering, neither of these last two use any configuration file.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)
Filed under: , , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

Windows Workflow Foundation 4 introduces the concept of bookmarks to temporarily pause activities. A bookmark is basically a named pause point in an activity. The result is that the workflow runtime doesn’t consider an activity to be finished until all its bookmarks are either resumed or removed.

 

That last statement isn’t completely true though as it is only the case when the optional BookmarkOptions aren’t specified or specified as None. With BookmarkOptions.MultipleResume a bookmark can be resumed multiple times, de default is just once, so just resuming a bookmark isn’t enough it actually needs to be removed. The other option is BookmarkOptions.NonBlocking which means that the activity is finished even if the bookmark was never resumed. This last option is useful when you want to be able to receive messages while child activities are executing but you don’t need to receive the message per se. The BookmarkOptions enum is a flag so you can combine both NonBlocking  and MultipleResume if you so desire.

One annoying thing with using bookmarks is that you need to use a NativeActivity and you have to override the CanInduceIdle property and return True as the default of False doesn’t permit bookmarks to be created, even when the NonBlocking option is used.

public class MyBookmarkedActivity : NativeActivity
{
    protected override bool CanInduceIdle
    {
        get { return true; }
    }
 
    protected override void Execute(NativeActivityContext context)
    {
        var bookmark = context.CreateBookmark("MyBookmark", BookmarkResumed);
 
    }
 
    private void BookmarkResumed(NativeActivityContext context, Bookmark bookmark, object value)
    {
        Console.WriteLine("Bookmark resumed with '{0}'.", value);
    }
}

 

Running the workflow and resuming a bookmark is easy when using a WorkflowApplication as seen below.

var wa = new WorkflowApplication(new MyBookmarkedActivity());
 
wa.Run();
 
var data = Console.ReadLine();
wa.ResumeBookmark("MyBookmark", data);
 
Console.ReadLine();

 

Nice and easy right?

Well yes except we also have WorkflowInvoker and WorkflowServiceHost to host our activity and neither contains a ResumeBookmark function. More about that later.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | with no comments
Filed under: , , , ,

Note: This blog post is written using the .NET framework 4.0 Beta 2

When I create custom activity designers the icon that appears is usually one of the first things I want to change. Doing so in WF4 isn’t hard once you know where to look but if you don’t can be a bit of a challenge.

Suppose I have a simple write line activity like this:

[Designer(typeof(MyWriteLineDesigner))]
public sealed class MyWriteLine : CodeActivity
{
    // Define an activity input argument of type string
    public InArgument<string> Text { get; set; }
 
    // If your activity returns a value, derive from CodeActivity<TResult>
    // and return the value from the Execute method.
    protected override void Execute(CodeActivityContext context)
    {
        // Obtain the runtime value of the Text input argument
        string text = context.GetValue(this.Text);
        Console.WriteLine(text);
    }
}

 

I have used the Designer attribute to attach my own designer to this activity.

The standard designer looks like this:

<sap:ActivityDesigner x:Class="WorkflowConsoleApplication3.MyWriteLineDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
    <Grid>
 
    </Grid>
</sap:ActivityDesigner>

 

And dropping that on a workflow designer surface results in the following:

image

 

So far so good except now I want to change the icon that appears at the top left of the designer.

The designer has an Icon property. Unfortunately the property sheet doesn’t do us a lot of good here.

image

 

In order to change the icon we need to add some XAML to the designer telling it what to draw. The new designer looks like this

<sap:ActivityDesigner x:Class="WorkflowConsoleApplication3.MyWriteLineDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
    <sap:ActivityDesigner.Icon>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <ImageDrawing>
                    <ImageDrawing.Rect>
                        <Rect Location="0,0" Size="16,16" ></Rect>
                    </ImageDrawing.Rect>
                    <ImageDrawing.ImageSource>
                        <BitmapImage UriSource="Clipboard_edit.png" ></BitmapImage>
                    </ImageDrawing.ImageSource>
                </ImageDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </sap:ActivityDesigner.Icon>
 
    <Grid>
 
    </Grid>
</sap:ActivityDesigner>

Inside of the sap:ActivityDesigner.Icon element we need to add a DrawingBrush using an BitmapImage pointing to a file. The file, Clipboard_edit.png in this case, has been added to the project and its Build Action has been changed to “Resource”.

image image

Once this has been done and the project rebuild the new icon will show up on the design surface.

image

 

 

Sweet Smile

Posted by Maurice | 2 comment(s)
Filed under: , , , , ,

The topic of Visual Basic expression in Windows Workflow Foundation 4 seems to be a bit of an item for some people as you can see in the comments to my blog posts about it here. Now personally I don’t think it is such a big deal as it only concerns the expressions entered in the workflow, the main project can still be C# if you so desire.

 

Anyway the team responsible at Microsoft have created a survey asking for feedback on the subject. So if you have an opinion, and many seem to, please take 5 minutes go here and complete survey.

 

Enjoy!

www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu

Posted by Maurice | 2 comment(s)
Filed under: , , , , , ,

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 Smile 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!

image

 

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.