Saturday, 28 April 2007

Microsoft has gone to great lengths to keep you from touching your instance directly, forcing you to always use the workflowinstance class to manipulate your workflow from the outside. They have done this to make sure the integrity of the system is maintained. It is however, a major pain in the ass. ;-)
I have been looking into solving a serious deficiency of windows workflow foundation: updating current workflows in your database. Microsoft does not have a good story on that one, and the WF architecture seems to be designed to make it as hard as possible to actually pull off! One approach to do this, would be to get to your instance, use reflection to get to private and public fields and create your new type, setting those fields as you go. Therefor, I have been looking into getting to my persisted instance.

Please be very careful with the following technique. Do not use it lightly, because it does allow you to do things that will break the integrity. It _will_ get you into trouble, if you do not watch out.


We are going to fetch a persisted workflow, but do not want the workflow runtime interfering. So:

Database db = DatabaseFactory.CreateDatabase("##Your database##");
byte[] act = (byte[])db.ExecuteScalar(CommandType.Text,
"select state from InstanceState where uidInstanceID = '## the guid you are interested in ##'");

This will directly query your database for some guid. (I am using Entlib 3.0 here).
The byte array returned is formatted with the binaryformatter. To be able to serialize activities, the WF-team does not use the Serializable attribute, but uses surrogates. A surrogate is a class that has intimate knowledge on how to serialize a certain type. Looking at the class ActivitySurrogate, you will notice that they have quite a bit going on there. Most important seems to be the private sealed class ActivitySurrogateRef, which defines fields like these:

[NonSerialized]
private Activity cachedActivity;
[NonSerialized]
private Activity cachedDefinitionActivity;
[OptionalField]
private EventHandler disposed;
private string id = string.Empty;
[NonSerialized]
private int lastPosition;
[OptionalField]
private object memberData;
[OptionalField]
private object[] memberDatas;
[OptionalField]
private string[] memberNames;
[OptionalField]
private string rulesMarkup;
[OptionalField]
private Type type;
[OptionalField]
private string workflowChanges;
[OptionalField]
private Guid workflowChangeVersion = Guid.Empty;
[OptionalField]
private string workflowMarkup;

So, it is clear our binaryformatter is going to have to reuse this logic. Thankfully, we can use the ActivitySurrogateSelector. This selector has logic that allows it to select the correct surrogate for each type. We set it like this:

BinaryFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = ActivitySurrogateSelector.Default;

Now it is time to deserialize our byte array. However, it is zipped!! So, let's unzip it:

MemoryStream stream = new MemoryStream(act);
stream.Position = 0;
using (GZipStream stream2 = new GZipStream(stream, CompressionMode.Decompress, true))
{ // here we can finally deserialize.  }

Inside that using-statement, we will deserialize. Here is the line that does the magic:

T activity = (T)Activity.Load(stream2, workflowRuntime.CreateWorkflow(typeof(T)).GetWorkflowDefinition(), formatter);

The 'T' is the type of your workflow.
I am using the static Load functionality of Activity to load. We first pass in our unzipped stream (stream2) and then we have to pass the workflowdefinition of the type we are deserializing. Finally, we are using our own formatter, with the activity surrogate attached.

Obviously, the use of the workflowruntime to get to the workflowdefinition is not pretty. Ugly even. However, I have not been able to circumvent it. Passing a null, will result in serializationexceptions.
I will update this post if I find a better way. Please leave a comment if you know of one!

I can imagine that it is useful to be able to get to your instance directly. For now, the only way to get to it, seems to be when they have to release it to the persistence store.

I hope this helps someone!

Saturday, 28 April 2007 17:43:52 (Romance Standard Time, UTC+01:00)  #    Comments [4]  |  Trackback
 Wednesday, 11 April 2007

My project is migrating a big (very big) winform application to a WPF (using Xbap) application. At this point, only the front-end is touched. The team will eventually evolve into migrating the data-driven procedural backend to a process-centered, domain-driven, WF (Workflow foundation) managed solution.

For this, we are looking for a few experienced C# developers. Obviously, WPF knowledge is a big plus. The project will last for quite a few months. When you walk away, you will have a deep understanding of WPF, CAB and WF.

If this sounds like your cup of tea, please leave a comment or mail me directly. The project is based in The Hague.

Wednesday, 11 April 2007 20:17:14 (Romance Standard Time, UTC+01:00)  #    Comments [8]  |  Trackback
 Tuesday, 10 April 2007

I'm also heading up a team that will be migrating existing businessprocesses to a workflow proces layer. It's exciting, because workflow foundation (WF) allows me to model an entire proces, instead of building small pieces and connecting them in code. This offers superior insight into the real businessproces and thus gives flexibility and power because for the first time, I can really sit down with an analist and explain 'code'. (our UML diagrams are outdated ;-) ). Because we then have a common understanding of the proces, we can feel at ease when modifying it.

Currently I'm working on having the workflow determine the 'actions' that a user (or machine) can perform in some state. WF has the ability to show the possible state transitions and that seems to be the logical piece of information I need to query and present to our client-side code (which well then enable/disable certain commands in the screens). However, it is completely useless because of two things:
1. it does not take into account the role a user is in
2. it will just display the possible transitions, but not the HandleExternalEventActivities (HEEA) that lead to them.

Therefor, I have build my own query. I'm aware that I've probably overlooked some hidden away funtionality, but until then, my code will do perfectly fine!

Given a workflow instance, I will first retrieve the waiting queue's. Then I will iterate the queueInfo objects. In my case, I will only use HEEA activities to handle the queue's, your workflow might differ. I will find that HEEA using the GetActivityByName method. Then I will check if it has roles assigned to it. I will simply check if the given role is in that array.
Next, I will have to lookup the correlationtoken, that might be used. If it is, I'm most interested in the correlationproperty. I will put that combination into my own struct (ProcesCommando). Add it to the list and return it!

ReadOnlyCollection<WorkflowQueueInfo> queues = instance.GetWorkflowQueueData();
foreach(WorkflowQueueInfo info in queues)
{
if(info.QueueName.Equals("SetStateQueue"))
{
continue;
}
else
{
foreach(string subscribedActivity in info.SubscribedActivityNames)
{
HandleExternalEventActivity heea =
instance.GetWorkflowDefinition().GetActivityByName(subscribedActivity) as HandleExternalEventActivity;

Debug.Assert(heea != null,
"Currently only expecting HandleExternalEventActivities");

#region check roles
if(heea.Roles != null)
{
// there are roles defined, so we need to check if the given role is included

bool inRole = false;
// TODO: use predicate
foreach (WorkflowRole workflowRole in heea.Roles)
{
if (workflowRole.Name.Equals(role.Name))
{
inRole = true;
break;
}
}

if (!inRole)
continue; // next subscribed activity.

// apparently the webworkflowrole does not implement equals and gethashcode correctly, so we can't do a 'contains'
// if(!heea.Roles.Contains(role))
// {
// // it does not, so this subscribed activity should never be executed
// continue;
// }
}
#endregion

#region possible correlation
string correlatie = String.Empty;
if (heea.CorrelationToken != null)
{
// there is a correlationtoken, so let's get the correlationproperty

EventQueueName queuename = info.QueueName as EventQueueName;
CorrelationProperty[] corProps = queuename.GetCorrelationValues();

Debug.Assert(corProps.Length == 1,
"Currently expecting exactly one correlation value");
correlatie = corProps[0].Value.ToString();
}

#endregion
ProcesCommands.Add(new ProcesCommando(heea.EventName, correlatie));
}
}

}
return ProcesCommands;

 

Tuesday, 10 April 2007 19:27:21 (Romance Standard Time, UTC+01:00)  #    Comments [8]  |  Trackback