Showing posts with label Windows Workflow. Show all posts
Showing posts with label Windows Workflow. Show all posts

Saturday, August 15, 2009

Using the StateDropDownEditor in state machine workflows

Dropdown Many moons ago I wrote about creating custom state machine activities. I’ve finally got back to doing some more work on this and thought it would be nice to add a dropdown to choose the target state for my custom activity. This looks pretty easy to implement, just decorate the property with the following attribute

[Editor(typeof(StateDropDownEditor), typeof(UITypeEditor)), DefaultValue((string) null)]

Unfortunately, StateDropDownEditor is an internal class so this won’t compile. The way round this is to fire up Reflector and copy the code for the StateDropDownEditor class. Not sure what the copyright issues are for this, but it starts to solve the problem. Unfortunately the StateDropDownEditor class references a StateMachineHelpers class which is also internal. We can again use Reflector to get hold of the implementation of this class, but things can start to get messy at this point. We end up having to pull in pretty much the whole state machine infrastructure code but the solution is pretty simple, just remove the GetCurrentState method and then no more code needs to be ‘borrowed’. I now have a nice dropdown for selecting the target state.

Sunday, February 22, 2009

BPM for business users

Prototype activity From my time on the Metastorm forums one thing is obvious, most people implementing Metastorm solutions are not business people. Mostly they are technical people, with a few not so technical people. And it’s generally the not so technical people who are having the biggest problems getting to grips with the product. Is that a failing of the software? I’d say not, although it could arguably be a failing of the marketing of the software, which like most BPM products suggests a non technical person can use it from start to finish to produce a working system.

This situation reminds me of other areas of software development, like the development of websites, or application development. A while ago, people thought anyone with a copy of FrontPage could develop their own website or anyone with a copy of VB could knock together a working application. But these other areas have matured enough to recognise that actually those pesky developers are still required (or to put it another way, designers can’t code and coders can’t design) and the development environments are now set up in such a way that the designers can work with the coders to produce a working system. For a website, the designer can produce a HTML layout that looks good and then the coder can add the required code so it does something. In the world of Windows development we are moving to WPF and other technologies where the layout is separated from the code, so the designer can produce the pretty screens and the coder can then add code to do stuff without both sides stepping on each others toes too much.

And for me, that’s the approach that BPM will have to take at some point, realising that the business people can’t code and the coders don’t understand the business, so trying to put only one of these people in charge of the BPM system will never fly. I think Windows Workflow is a step in the right direction with its ability to create custom activities. The coder can create all the required custom activities and then the business person can plug these together like Lego as required. Or this could work in the opposite direction, where the business user produces their process and adds dummy activities where they need functionality to be implemented and the coder goes off and creates the required activities.

With that in mind, I created a simple PrototypeActivity that can be added to a workflow and lets the user specify the inputs and outputs that will be need to be implemented. The screenshot shows how it looks in operation (although a proper implementation would probably provide a better user interface). The code is below.

  public enum PropertyDirection
  {
    In,
    Out,
    InOut
  }

  public class ActivityProperty
  {
    private string propertyName = "";
    public string PropertyName
    {
      get { return propertyName; }
      set { propertyName = value; }
    }

    private string description;
    public string Description
    {
      get { return description; }
      set { description = value; }
    }

    private PropertyDirection direction;
    public PropertyDirection Direction
    {
      get { return direction; }
      set { direction = value; }
    }

    public override string ToString()
    {
      return direction.ToString() + " - " + propertyName.ToString();
    }
  }

  public class PrototypeActivity: Activity
  {
    public static DependencyProperty PropertiesProperty =
      DependencyProperty.Register("Properties", typeof(ActivityProperty[]), typeof(PrototypeActivity));

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Browsable(true)]
    [Description("The properties the activity requires")]
    public ActivityProperty[] Properties
    {
      get
      {
        return ((ActivityProperty[])(base.GetValue(PrototypeActivity.PropertiesProperty)));
      }
      set
      {
        base.SetValue(PrototypeActivity.PropertiesProperty, value);
      }
    }
  }

Sunday, November 02, 2008

Custom activities in Windows Workflow 4.0

Another presentation from PDC, http://channel9.msdn.com/pdc2008/TL21/

Looks like authoring custom activities has got a whole lot easier and more powerful. Designer rehosting is much simpler. Looks like I might have to finally learn some WPF though.

Wednesday, October 29, 2008

Windows Workflow 4 at PDC

See the intro to WF 4.0 at http://channel9.msdn.com/pdc2008/TL17/. Watching at the moment and looks pretty cool. Fully declarative workflows, better designer, store workflow data in a proper queryable manner in the database.

Monday, October 27, 2008

Metastorm BPM 7.6 and Windows Workflow part 6 - state machines

stateThis will almost certainly be my last post about executing Windows Workflows from Metastorm BPM as I think I've covered pretty much everything there is to cover (although leave a comment if there is something I've missed).

One of the comments from one of the previous posts was regarding state machine workflows and whether these could be executed from the Metastorm environment. So I knocked together a simple state machine workflow in Visual Studio that starts, waits in its initial state for 5 minutes then moves to the final state.

As discussed before, there is no support for publishing your own VS authored workflows to the Metastorm database so I modified the little command-line tool I wrote previously to cope with state machine workflows. For brevity I won't post all the source, just the change required, which is as follows.

        for (int i=0; i<types.Length; i++)
        {
          if (types[i].IsSubclassOf(typeof(SequentialWorkflowActivity)))
          {
            workflowType = types[i];
            break;
          }
          else if (types[i].IsSubclassOf(typeof(StateMachineWorkflowActivity)))
          {
            workflowType = types[i];
            break;
          }
        }

So after publishing the workflow, I tried to execute it from Metastorm and it worked as expected. Cool.

The next test was to see what happened when I restarted the engine whilst a workflow was executing, since this caused problems with sequential workflows. I was expecting the same problem, since state machine workflows execute in the same way as sequential workflows. And I was correct in my supposition, the workflow never completed.

So in conclusion, state machines can be used in Metastorm but you'll need to hack the database. But WF in Metastorm won't be of use to you if you need to execute long running workflows. Which is a shame since this is probably the main purpose of WF.

Part 1 - The basics

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 5 - Long running workflows

Sunday, September 28, 2008

Metastorm BPM 7.6 and Windows Workflow part 5 - Long running workflows

OK, I want to go back to basics for this post. What is the point of Windows Workflow? Here's the first paragraph of the preface to "Essential Windows Workflow Foundation"

"Windows Workflow Foundation is a general-purpose programming framework for creating reactive programs that act in response to stimulus from external entities. The basic characteristics of reactive programs is that they pause during their execution, for unknown amounts of time, awaiting input."

I've highlighted what I consider to be the important point here. A WF workflow may lay dormant for minutes, days or weeks. In the normal world of .NET code, implementing this kind of application can be troublesome. For a start, the application may shutdown and restart before the workflow resumes, so the application needs to cope with this possibility. This means the objects must be persisted and restarted with the correct state. A simple solution like making the executing thread sleep for the required amount of time isn't going to work, in terms of robustness or scalability (imagine 1000s of these workflows being executed at the same time).

So WF helps solves this problem. Without this feature, WF is really just a different approach to producing standard .NET code, either through the designer or generating declarative XML documents to explain how the code should execute. It might well be useful in some scenarios but it isn't really a killer reason to use the technology.

BPM WF

So with that in mind I created a very simple workflow to execute from Metastorm BPM, which you can see on the left. All it does is write to a text file, wait for ten minutes, then write to the text file again.

The first thing to ensure is to set the "Microsoft Workflow timeout (secs)" registry setting to something sensible. Unfortunately setting this to zero doesn't mean no timeout, it just uses the default 120 seconds timeout. So this needs setting to something very large. This is a DWORD value with a maximum value of 4294967295, which I calculate is approximately 49710 days, that hopefully is large enough for most workflows.

Executing the workflow worked fine in this scenario. The workflow idled for 10 minutes then resumed and completed.

So the next experiment was to start the workflow, shut down the engine and then restart the engine and see what happened. I waited... and waited... Nothing. I started another workflow just in case the workflow runtime hadn't been kicked off but still nothing. So there's a bit of a problem there, particularly if you want to run long running workflows.

As an aside, if you're running your workflow asynchronously then you won't be able to use most of the activities provided for interaction with Metastorm BPM. This is a shame. In particular the RaiseFlag activity would be exceedingly useful since it could be used to signal to the parent process that the workflow had completed. As it is, you'll need to do this with the ECL activities provided, which are more complicated to configure or write your own code.

Part 1 - The basics

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 6 - State machines

Tuesday, September 16, 2008

Metastorm BPM 7.6 and Windows Workflow part 4 - Using your own activities

My previous posts on Metastorm/WF integration have had a few comments, so I thought I'd follow up on them.

First up, it is possible to reference your own assemblies containing custom activities in the WF Composer, via the Options dialog that can be found on the main menu. For me this means the Composer could potentially be used by non-technical people, although the workflow designer may still be too difficult to hand over to non-techies (this isn't Metastorm's fault BTW, the design surface is all Microsoft's work).

But before I could check out this feature, I had a problem using the Composer. I haven't used it for a few weeks or months and when I tried to do almost anything I got the following error

Could not load file or assembly 'mscorcfg, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. (mscorlib) 

I suspect this is down to me uninstalling Visual Studio 2005 since I installed the WF Composer which I guess uninstalled the .NET 2.0 SDK which is required by the WF Composer. It's a shame the SDK is required, as it's a 354MB download... If it's just for the one assembly then it's flipping ridiculous.

Anyway, after installing the SDK and reinstalling the Metastorm WF bits, things started working again (the re-install may not be necessary but I uninstalled before I realised what my problem was).

OK, so I thought I'd try to use some of my own activities. My first problem was that any assembly I tried to reference produced the following error

No toolbox items found in assembly!

This was easily solved by adding the following attribute to my activity class definition

[ToolboxItem(true)]

My activities now appear in the toolbox but I'm unable to drag them onto the design surface. I guess I'm missing something in my activity class code but I don't know what. They work in Visual Studio and my own designer so I'm not sure why they fail here. Anyway, although the WF Composer is of passing interest, my main interest is using workflows authored in Visual Studio, so I'm not going to look at it too closely.

Part 1 - The basics

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 5 - Long running workflows

Part 6 - State machines

Sunday, June 15, 2008

Metastorm BPM 7.6 and Windows Workflow part 3 - Using Visual Studio

In the first two parts of this series I've given a brief introduction to the integration of Windows Workflow into Metastorm BPM 7.6. For this final part I'll be discussing what interests me, being able to execute workflows authored in Visual Studio from Metastorm BPM.

The first question was, is it even possible? There is no UI for publishing workflows to the Metastorm database, other than those authored in the Metastorm WF Composer. But looking at the database, it looked like the only tables that needed populating were eMSWorkflow and eMSWorkflowDefinition. After some playing around I came up with the following code, a simple command-line tool. This is by no means bulletproof, it will fail if the workflow has already been published (the next version of the FreeFlow Administrator will let you delete workflows) and will likely fail for a host of other reasons. I was using it purely as a proof of concept.

using System;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Workflow.Activities;

namespace PublishWorkflow
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 4)
{
Console.WriteLine("PublishWorkflow usage:");
Console.WriteLine("PublishWorkflow [ODBC DSN] [ODBC username] [ODBC password] [Workflow DLL] ");
}
else
{
string fileName = args[3];

// get the assembly
Assembly assembly = Assembly.LoadFrom(fileName);

// get the workflow type
Type workflowType = null;
Type[] types = assembly.GetTypes();
for (int i=0; i<types.Length; i++)
{
if (types[i].IsSubclassOf(typeof(SequentialWorkflowActivity)))
{
workflowType = types[i];
}
}

if (workflowType == null)
throw new Exception("Can't find a workflow in the assembly");

string connectionString =
string.Format("UID={0};PWD={1};DSN={2}", args[1], args[2], args[0]);
using (OdbcConnection connection = new OdbcConnection(connectionString))
{
connection.Open();

Guid guid = Guid.NewGuid();

// write to eMSWorkflow
using (OdbcCommand command = connection.CreateCommand())
{
command.CommandText = string.Format(
"INSERT INTO eMSWorkflow (eWorkflowName, eWorkflowGuid) VALUES " +
"('{0}', '{1}')",
workflowType.FullName, guid.ToString());
command.ExecuteNonQuery();
}

// write to eMSWorkflowDefinition
using (OdbcCommand command = connection.CreateCommand())
{
string fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion.ToString();

byte[] buffer;
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read))
{
buffer = new byte[stream.Length];
stream.Read(buffer, 0, Convert.ToInt32(stream.Length));
stream.Close();
}

command.CommandText = string.Format(
"INSERT INTO eMSWorkflowDefinition (eWorkflowGuid, eWorkflowName, eFileVersion, eFullyQualifiedTypeName, " +
"eFullyQualifiedAssemblyName, eWorkflowDefinitionType, eRulesDefinitionName, eLoadedTime, " +
"eWorkflowDefinition, eWorkflowProject) " +
"VALUES ('{0}', '{1}', '{2}', '{3}', '{4}', 'A', '', GETDATE(), ?, NULL)",
guid.ToString(), workflowType.FullName, fileVersion, workflowType.AssemblyQualifiedName,
assembly.FullName);
OdbcParameter parameter = new OdbcParameter();
parameter.Value = buffer;
parameter.DbType = DbType.Binary;
parameter.Direction = ParameterDirection.Input;
command.Parameters.Add(parameter);
command.ExecuteNonQuery();
}
}
}
}
}
}

So I published my simple Visual Studio authored workflow using this code and was able to execute it. It would appear the process context activity provided as part of the Metastorm activities isn't required, although I assume it will be needed if you need to get folder information into your workflow.


The next step was to see if I could execute a workflow that contained custom activities. Obviously the problem here is where will the engine pick up the required assemblies (assuming the custom activities are in their own assembly)? I tried putting the DLL in the engine directory, dotnetbin directory and even the System32 directory (since this is where dllhost lives) but none of them seemed to work. All I got was the following useful error message


Microsoft Workflow evaluation (Instance ID 963fc00a-f2f9-40a8-a0df-7111b0706cd7)
For more information refer to the workflow tracking and event tables.

In fact the tracking and event tables contained no useful information. This error message seems to be the default error message when something goes wrong with a workflow and I've never found any useful information in the database. Debugging the engine through Visual Studio seems to be the only way of getting hold of the real error.


In the end the only way I found to get the engine to pick up my custom activity assembly was to install it in the GAC. Once I did that the workflow executed correctly.


So yes it is possible to execute a VS authored workflow in Metastorm, although some frigging around is required. However I have one concern. One problem I encountered whilst researching this was that workflows would abort after a certain time. Once again, the error above was shown in the Designer Log. Debugging the engine showed the problem was the workflow was timing out. I found a registry setting that controls this timeout, which by default is set to 60 seconds. But how does this timeout work? Is a thread kept alive waiting for the workflow to terminate? If so, this will be a problem for scalability if you wish to execute long running workflows. And that is what Windows Workflow is all about, running some code, sleeping for a week, executing some more code etc. I don't have the answer to this question yet, but will investigate further.


Part 1 - The basics

Part 2 - The database tables

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Sunday, June 08, 2008

Metastorm BPM 7.6 and Windows Workflow part 2 - the database tables

In part 1 I discussed the new Windows Workflow features in Metastorm BPM 7.6. I'll now move onto the database tables used by WF workflows in Metastorm.

First we have two tables that contain process metadata.

eMSWorkflow is pretty simple, it contains one entry for each workflow published to the database. There is a column for the name of the workflow and a GUID column. The name is the name that will be used when executing a workflow from Metastorm. I'm unclear what the GUID column is for. Multiple workflows with the same name aren't possible so it isn't for differentiating between two workflows with the same name.

eMSWorkflowDefinition holds an entry for each workflow version published to the database. Most of this table is pretty self-explanatory. Again there is a GUID column whose purpose isn't clear. There is an interesting column called 'eWorkflowDefinitionType'. I haven't investigated this too far but it suggests that it is possible to publish workflows as assemblies or as uncompiled XOML file.

Next we have tables that hold workflow instance data.

eMSWorkflowTracking contains audit trail information for executing workflows. It looks like Metastorm have implemented their own tracking service which makes sense since it means the workflow audit trail can be connected to the Metastorm folder's audit trail. One problem I have with the current implementation is there appears to be no way to connect a workflow instance to its type, so it will be difficult to figure out how many instances of a particular workflow type are in existence.

eMSWorkflowEvent also includes data about workflow instances. The difference is that this table contains information that is related to the workflow runtime, rather than the workflow itself. So it will contain entries for when an instance has been loaded or idled etc.

Finally there are two other tables. These are the standard tables created for the WF persistence service, CompletedScope and InstanceState. The Microsoft documentation for these tables seems to be pretty much non-existent, so I don't have much idea what is in there. All I can assume is the 'state' column contains the serialized data of the current state of the workflow instance.

So that's the database tables. Part 3 will discuss running workflows generated in Visual Studio (if I get time to actually get it working).

Part 1 - The basics

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Tuesday, May 27, 2008

Adding code to a XOML workflow

I was aware that it was possible to add code to a XOML workflow but I hadn't actually seen any examples of it, until I started looking into the Metastorm WF integration. I'm not sure it's something I'd like to do, it's pretty ugly and I like the idea of producing a workflow definition in a purely declarative manner. That way you're forced to put real code in their own activities which I think helps the overall design of your system. However there is one place where it isn't possible to do everything in XML, when you need to add a property to a XOML workflow. So here's an example of that. If you get stuck using this, you're on your own, I don't know much about it and the documentation seems a bit thin on the ground. Here's hoping that MS add support for XOML properties in the next release...

<x:Code><![CDATA[//<?xml version="1.0" encoding="utf-16"?>
//<XCodeItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="XCodeProperty">
//  <MemberTypeName>System.Boolean</MemberTypeName>
//  <MemberName>Thingy</MemberName>
//  <EmitDependencyProperty>true</EmitDependencyProperty>
//  <IsMetaProperty>false</IsMetaProperty>
//  <IsAttached>false</IsAttached>
//  <IsReadOnly>false</IsReadOnly>
//</XCodeItem>


public static System.Workflow.ComponentModel.DependencyProperty ThingyProperty = DependencyProperty.Register("Thingy", typeof(System.Boolean), typeof(MyWorkflow));

public bool Thingy
{
get
{
return ((bool)(base.GetValue(MyWorkflow.ThingyProperty)));
}
set
{
base.SetValue(MyWorkflow.ThingyProperty, value);
}
}
]]></x:Code>

Monday, May 26, 2008

Metastorm BPM 7.6 and Windows Workflow part 1 - The basics

The new version of Metastorm BPM, 7.6, has several new features (and I may do a full review at some point), but the one that interests me the most is the integration with Windows Workflow. This has been a fairly long time in development, a screenshot appeared on the web about 18 months ago. The first WF integration appeared in 7.5, which allowed BPM functionality to be called from WF workflows. This was not of great interest to me as it always seemed more useful to be able to call WF workflows from Metastorm. This is what has been added in 7.6.

Installation is easy, although a complete re-install is required, rather than an in-place upgrade. An upgrade will update the 7.5 pieces, but will not install the new 7.6 pieces.

The main thing of interest is the new WF designer (called WF Composer) that is used to develop and publish WF workflows to the Metastorm database. It's quite a nice tool, but my main gripe is that it's not Visual Studio. If it's aimed at developers then integration with Visual Studio would be a more preferable solution. Metastorm don't have the resources to produce an alternative to VS and hence any WF designer they produce will always look poor in comparison. Perhaps developers aren't the target audience, but if not then why does it include a C# editor? This is also much less useable than the Visual Studio code editor.

Another problem is that it isn't possible to add your own activities into the Designer. I'd always imagined WF could be used by a non-technical person, but to achieve this would require developers to go off and write activities specific to their organisation, that could then be plugged together by the non-technical people. So to my mind, the Composer isn't suitable for a non-technical person either.

To be fair, the Composer does add some functionality to make life simpler. When creating a workflow, it adds the Process Context activity, which provides access to the data in the BPM folder. It also simplifies adding properties to the workflow, since you can add a property just by selecting a name and a type (much like adding custom variables in Metastorm BPM). This then adds the relevant code to the generated workflow. It also includes some activities that implement familiar Metastorm BPM functions such as Email, Manager, SelectSQL etc. Not all functions are wrapped as activities but the rest can be accessed via the generic EvaluateFunction activity.

Executing workflows from BPM is pretty straightforward. Add the Workflow Support Library to a procedure and then use the Integration Wizard to execute the workflow synchronously or asynchronously. One thing to consider when executing a long-running workflow is how you're going to track its life time. It will have to be executed asynchronously since a synchronous execution will lock up your BPM folder and will also likely time out. You can get hold of the ID of the WF instance when you execute it and the tracking data is stored in the Metastorm database so it should be possible to get hold of the tracking data to display in a Metastorm grid. If your WF workflow must complete before your BPM workflow can continue, you'll probably need to add a timed loopback action to keep checking until your workflow is complete. Alternatively you could send a message back from the WF workflow to inform the Metastorm folder it has finished. There is unfortunately no user interface to view WF instances which could make debugging more difficult.

That all said, I am still interested in getting WF workflows executing in Metastorm BPM. This could be very useful so we can re-use functionality in different workflow environments and even help us to migrate away from Metastorm BPM when we are looking for a cheaper solution. But given that I would like to use Visual Studio to author my workflows how do we get them into Metastorm? I'm going to investigate this and will report back in part 2.

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 4 - Using your own activities

Part 5 - Long running workflows

Part 6 - State machines

Monday, May 05, 2008

Synchronous execution of a child XOML workflow

Previously I came up with a reasonable approach to executing a child XOML workflow asynchronously but what I was really after was executing the workflow synchronously. I eventually realised that the solution to this problem was to fire up another WorkflowRuntime and execute the workflow using that. I now have a WorkflowExecutor class as below.

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

namespace WFBuild.Activities
{
  /// <summary>
  /// Executes a workflow synchronously
  /// </summary>
  public class WorkflowExecutor
  {
    private AutoResetEvent waitHandle;
    private Guid wfGuid;
    private Exception ex;

    public void Execute(string fileName)
    {
      WorkflowRuntime workflowRuntime = new WorkflowRuntime();
      workflowRuntime.StartRuntime();
      workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
      workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

      FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
      XmlTextReader reader = new XmlTextReader(stream);
      WorkflowInstance instance;
      try
      {
        instance = workflowRuntime.CreateWorkflow(reader);
      }
      catch (WorkflowValidationFailedException exp)
      {
        StringBuilder errorMsg = new StringBuilder();
        errorMsg.AppendLine("Validation failed:");
        foreach (ValidationError error in exp.Errors)
        {
          errorMsg.AppendLine(error.ToString());
        }
        throw new Exception(errorMsg.ToString());
      }
      wfGuid = instance.InstanceId;
      waitHandle = new AutoResetEvent(false);
      instance.Start();
      waitHandle.WaitOne();

      if (ex != null)
        throw ex;
    }

    private void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        // pass the problem back to the main call
        ex = new Exception("Workflow terminated - " + e.Exception.Message);
        waitHandle.Set();
      }
    }

    private void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
      if (e.WorkflowInstance.InstanceId == wfGuid)
      {
        waitHandle.Set();
      }
    }
  }
}

This code is called from the activity like so

using System.Workflow.ComponentModel;

namespace WFBuild.Activities
{
  public class InvokeXomlWorkflowActivity: Activity
  {
    private string xomlFile;
    public string XomlFile
    {
      get { return xomlFile; }
      set { xomlFile = value; }
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
      WorkflowExecutor executor = new WorkflowExecutor();
      executor.Execute(xomlFile);

      return ActivityExecutionStatus.Closed;
    }
  }
}

Now this still isn't perfect as a general purpose workflow invoker. Ideally the invoker activity wouldn't wait for the child workflow to complete before returning from Execute. It should start the workflow, return with an Executing status, wait for the workflow to complete, then close itself. But this will do for me so that improvement is left as an exercise for the reader.

Sunday, April 27, 2008

Getting a list of services loaded into the workflow runtime

Getting a list of the services loaded into the workflow runtime is pretty simple. Here's some code to do it.

using System;
using System.Collections.ObjectModel;
using System.Workflow.Runtime;

namespace GetAllServices
{
  class Program
  {
    static void Main(string[] args)
    {
      WorkflowRuntime runtime = new WorkflowRuntime();
      runtime.StartRuntime();
      ReadOnlyCollection<object> services = runtime.GetAllServices(typeof(object));
      for (int i = 0; i < services.Count; i++)
      {
        Console.WriteLine(services[i].ToString());
      }
      Console.ReadLine();
    }
  }
}

If you try to get a service from inside an activity using the ActivityExecutionContext.GetService method, the available services will be different. Looking in Reflector, you'll see there are several services that won't be returned and the IStartWorkflow service will be created on the fly. Also have a look at WorkflowExecutor.GetService called from ActivityExecutionContext.GetService which also special cases some services before finally calling the runtime's GetService.

Saturday, April 26, 2008

Invoking a XOML workflow from a workflow

The built-in InvokeWorkflow activity requires a compiled workflow type which isn't too useful if you're trying to execute a XOML-based workflow. Initially I thought I could compile the XOML workflow and then use the InvokeWorkflow activity. The problem with this approach is that the InvokeWorkflow's TargetWorkflow property can't be set at run-time.

The solution is pretty straightforward, compile the workflow then use the IStartWorkflow service to start the workflow (which is what InvokeWorkflow does internally). But there are still a couple of issues with this.

First, there is no way to pass parameters to a XOML workflow, since there is no way to add top-level properties. You could workaround this by sub-classing the SequentialWorkflowActivity and adding properties for the parameters you want to pass.

The second problem is if you want the child workflow to finish before continuing with the parent workflow. rather than starting the child workflow asynchronously. I've seen examples of how to do this by the child workflow signalling when it's complete but I've not seen this done in a generic way that doesn't require the child workflow to be modified.

Anyway, here's my code which might be useful.

using System;
using System.IO;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Xml;

namespace WFBuild.Activities
{
  public class InvokeXomlWorkflowActivity: Activity
  {
    public InvokeXomlWorkflowActivity()
    {
    }

    private string xomlFile;
    public string XomlFile
    {
      get { return xomlFile; }
      set { xomlFile = value; }
    }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
      string className = Path.GetFileNameWithoutExtension(xomlFile);

      // compile workflow
      WorkflowCompiler compiler = new WorkflowCompiler();
      WorkflowCompilerParameters wfParams = new WorkflowCompilerParameters();
      wfParams.LibraryPaths.Add(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
      wfParams.ReferencedAssemblies.Add("WFBuild.Activities.dll");
      wfParams.OutputAssembly = string.Format("{0}.dll", className);

      // add x:Class attribute
      string tempFileName = Path.GetTempPath() + "temp.xoml";
      XmlDocument doc = new XmlDocument();
      doc.Load(xomlFile);
      const string namespaceUri = "http://schemas.microsoft.com/winfx/2006/xaml";
      if (doc.DocumentElement.GetAttribute("Class", namespaceUri) == "")
      {
        doc.DocumentElement.SetAttribute("Class", namespaceUri, className);
      }
      doc.Save(tempFileName);

      WorkflowCompilerResults results = compiler.Compile(wfParams, tempFileName);

      if (results.Errors.HasErrors)
      {
        string errors = "";
        for (int i = 0; i < results.Errors.Count; i++)
        {
          errors += results.Errors[i].ErrorText + "\n";
        }
        throw new Exception(errors);
      }
      
      Type[] types = results.CompiledAssembly.GetTypes();
      Type workflow = types[0];

      IStartWorkflow service = executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
      if (service == null)
      {
        throw new InvalidOperationException("No IStartWorkflow service available");
      }
      Guid guid = service.StartWorkflow(workflow, null);
      if (guid == Guid.Empty)
      {
        throw new InvalidOperationException("Failed to start the workflow");
      }
      return ActivityExecutionStatus.Closed;
    }
  }
}

Saturday, March 08, 2008

The big BPM lie

I've been looking at a few Business Process Management tools on and off and I've noticed something that most of them share in common, they like to brag that no coding is required to get your BPM solution in place.Whilst a laudable aim, I doubt very much that it can be met, especially when integration with other products is required. And integration is one of the key ingredients to a successful BPM implementation.

The stock answer to integration is the use of web services. And they could be the answer. They are a standard after all, but unfortunately each development platform seem to implement these standards slightly differently. I've had much fun calling web services authored in Perl and Java from .NET. And that was using code, rather than any web service integration abstraction piece provided by a BPM product, which would almost certainly have made other assumptions about how web services work. Almost every abstraction is leaky. And when an abstraction is leaky, it is necessary to drop down to the next lower abstraction, which is code in this case. And if the abstraction isn't leaky, then the BPM vendor must have produced some kind of general integration technology which is actually pretty damn useful on its own, so why aren't they selling that?

And when a BPM vendor says no code is necessary, it means that it's likely its story for writing code in their development environment is probably not going to be as good as it could be. After all, you'll never need it, right?

To a certain extent I can understand this emphasis on a no code solution. After all, if code was required to implement even a basic BPM solution using their product then the BPM software wouldn't be providing much value.

This is one reason why I love Windows Workflow. It is unashamedly a development tool and doesn't shy away from code. OK, it's not a fully-fledged BPM tool, but it is certainly possible for a BPM product to be built on top of it. Workflows can be designed without code, but when it's needed, a developer can come along and produce a custom activity to do whatever integration is required. This seems like the perfect solution and I hope this is how BPM products will be designed in future.

Tuesday, February 12, 2008

Storing variables in a XOML workflow

One problem with XOML only workflows (i.e no code-behind) is the inability to define properties in your top-level workflow to store values that will be used throughout the lifecycle of the workflow, since you haven't got a class in which to define them. Well that's what I thought anyway until I had the idea of creating a custom activity to store these variables. It's all very simple, add the activity to your workflow and set the Value property to whatever you want, then bind it to any other activities that need access to the value.

Here's the code

    public class VariableActivity: Activity
    {
      private string value;
      [Description("The value of the variable")]
      public string Value
      {
        get { return value; }
        set { this.value = value; }
      }
    }

Update - Here's an example of binding the value of the variable to an activity property

<?xml version="1.0" encoding="utf-16"?>
<SequentialWorkflowActivity x:Name="Workflow" xmlns:ns0="clr-namespace:WFBuild.Activities;Assembly=WFBuild.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow">
  <ns0:VariableActivity x:Name="folderToZip" Value="c:\temp" />
  <ns0:ZipFolderActivity x:Name="zipFolder" Folder="{ActivityBind folderToZip,Path=Value}" />
</SequentialWorkflowActivity>

Wednesday, January 30, 2008

Building C# projects with Windows Workflow

wfbuild After reading a post about using Windows Workflow to build an SMTP server, I started thinking of something I could use WF for outside the world of business processes. For a long while I've also been planning on automating my build processes for various projects I've got on the go. I then realised I could combine the two and develop an automated build system with WF. At this point, if you have any experience of automated builds, you're probably thinking "Why the hell don't you use NAnt or MSBuild like any normal person?", which is a fair point.

But using WF has a few advantages. First, as far as I'm aware NAnt and MSBuild don't provide a graphical designer for their build projects (I'm quite happy to be proved wrong on this point) whereas I was able to knock together a designer based on the Microsoft example pretty quickly. Second, I just wanted to use WF for something not related to business processes. It is a very flexible technology and I'm not sure people have realised its potential. I blame the name, which makes everyone assume WF has something to do with workflow, which for many people has a very particular meaning. Third, a WF solution shares the main advantage of NAnt and MSBuild, the build scripts can be simple XML files. To be fair it does currently depend on Visual Studio being installed, although I'm sure I could build projects using the C# compiler directly (probably taking advantage of the MSBuild API in fact!).

So after a couple of hours work I had a few activities, a designer and a command-line application to run the workflow XOML (see my previous post) and a working build system. OK, I still have some tidying up to do but I was amazed at what I'd managed to achieve. After some more work I'll dump my code somewhere for people to play with.

Another take on this may be to use the WF designer to generate NAnt and MSBuild XML files directly. My understanding is that WF provides support for using your own custom DSLs. Something else to look at...

Update – You can now download the source code here. The only warranty that comes with it is that “it works on my machine”. In fact it’s quite possible it won’t work on your machine since some of the locations are hardcoded. There are four projects, one is the workflow designer control, then there is a GUI application that uses that control, there’s an assembly full of activities (some of which could be useful in other workflows) and finally there is a console application that can be used to run your build from the command line.

Monday, January 28, 2008

Running a XOML workflow from the command line

I needed to be able to execute a XOML workflow from the command line so I wrote this little app that takes a XOML file as an argument and executes it. This is part of something that should become a nice example of using WF to build applications for non-programmers, i.e. using activities as building blocks like Lego. More on that to follow, perhaps.

using System;
using System.IO;
using System.Threading;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Xml;

namespace WFBuild
{
  class Program
  {
    private static AutoResetEvent waitHandle;
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("WFBuild");
        if (args.Length != 1)
        {
          Console.WriteLine("Usage");
          Console.WriteLine("WFBuild <XOML file to execute>");
        }
        else
        {
          WorkflowRuntime workflowRuntime = new WorkflowRuntime();
          workflowRuntime.StartRuntime();
          workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
          workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

          FileStream stream = new FileStream(args[0], FileMode.Open, FileAccess.Read);
          XmlTextReader reader = new XmlTextReader(stream);
          WorkflowInstance instance = workflowRuntime.CreateWorkflow(reader);
          waitHandle = new AutoResetEvent(false);
          instance.Start();
          Console.WriteLine("Executing...");
          waitHandle.WaitOne();
        }
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
      catch (WorkflowValidationFailedException exp)
      {
        // catch workflow failed validation exception and show validation errors
        Console.WriteLine("Validation failed:");
        foreach (ValidationError error in exp.Errors)
        {
          Console.WriteLine(error.ToString());
        }
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
        Console.WriteLine("Press return");
        Console.ReadLine();
      }
    }

    static void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
    {
      Console.WriteLine("Workflow terminated");
      Console.WriteLine(e.Exception.Message);
      waitHandle.Set();
    }

    static void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
      Console.WriteLine("Workflow completed");
      waitHandle.Set();
    }
  }
}

Update - I've updated the code to show validation errors

Monday, November 19, 2007

Developing custom state activities

I've banged on before about how extensible Windows Workflow is (assuming you can work out how to extend it), but there is at least one place where it isn't quite as extensible as one would like, state activities.

I was trying to create a custom state activity that contained a state initialization activity and an event driven activity and various other activities within those. The first problem I encountered was when I inherited from StateActivity. Trying to add any activities at design-time failed. This seems to be down to the state activity designer. One solution would have been to write my own designer class, but this is complicated by the fact that the standard state designer is internal so can't be inherited from. I guess I could have decompiled the code using Reflector and made the required mods in my own code, but that seemed too much like hard work. So I decided to construct the activity in code instead.

Then I bumped into the second problem. Adding activities in code is pretty straightforward, just do it in the constructor after the call to InitializeComponent, like so.

public UserActivity()
{
  InitializeComponent();
  CanModifyActivities = true;
  try
  {
    // add activities here
  }
  finally
  {
    CanModifyActivities = false;
  }
}

Having said that, if you're doing all your activity construction in code, you can actually get rid of the call to InitializeComponent and get rid of the designer.cs file, since you're not going to need any design-time support.

Anyway that's the theory. In practice, it doesn't quite work out like this, because the StateActivity's validator doesn't like you to add child activities to a state activity. At least this class is public so I could inherit from it, but there aren't really any extensibility points to allow for modification of its behaviour. So I initially wrote my own validator class, that doesn't do anything at all. I later realised that if the validator wasn't going to do anything, I could actually just use ActivityValidator instead. This isn't perfect since it means none of the child activities I've added will be validated, but it works well enough for my scenario.

One further problem (although it isn't an issue for me) is that an end user of your custom state activity won't be able to add their own child activities. I guess the solution to this once again is to write your own designer.

Another related problem is the addition of a set state activity. Since my users can't add one themselves, I have to add one but I don't know the name of the state to transition to until the activity is in use. This was pretty easy to solve, just add a TargetStateName to my activity and pass that value through to my own set state activity.

public string TargetStateName
{
  get
  {
    return setState.TargetStateName;
  }
  set
  {
    setState.TargetStateName = value;
  }
}

My final problem was that the state activity was displayed with all of its child activities visible to the end user, although they are not editable. I still haven't solved this issue, although I suspect the solution will yet again involve writing my own designer class. If only that standard state designer wasn't internal...

Thursday, November 15, 2007

Sharepoint create task workflow error - Not supported exception

If you've tried to create tasks in workflows hosted in Sharepoint using the CreateTask activity you may have come across this error in the Sharepoint logs.

System.NotSupportedException: Specified method is not supported.     at Microsoft.SharePoint.Workflow.SPWorkflowTask.SetWorkflowData(SPListItem task, Hashtable newValues, Boolean ignoreReadOnly)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.UpdateTaskInternal(Guid taskId, SPWorkflowTaskProperties properties, Boolean fSetWorkflowFinalize, Boolean fCreating, HybridDictionary specialPermissions)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.CreateTaskWithContentTypeInternal(Guid taskId, SPWorkflowTaskProperties properties, Boolean useDefaultContentType, SPContentTypeId ctid, HybridDictionary specialPermissions)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.... 

Although saying that, my own searches on the Internet didn't bring up anything, so I may be the only one. Or there may not be many people using workflow in Sharepoint, which is odd given that it's free, which compares very favourably with most workflow products.

But I digress. I thought this would be pretty easy to solve. First up, have a look at the implementation of SPWorkflowTask.SetWorkflowData in Reflector and see what causes it to throw the exception. Which is where things got difficult. Reflector told me the method was obfuscated. I don't know if this is true or if Reflector was getting confused but I left it for a while since I had other things to do. Then I had a thought, perhaps I could still have a look at the code in Reflector if I chose IL as my language of choice. And indeed I could. And this is what the area of interest looked like.

    L_01e8: br.s L_01f0
    L_01ea: newobj instance void [mscorlib]System.NotSupportedException::.ctor()
    L_01ef: throw
    L_01f0: ldloca.s id
    L_01f2: ldsfld valuetype Microsoft.SharePoint.SPContentTypeId       Microsoft.SharePoint.SPBuiltInContentTypeId::WorkflowTask
    L_01f7: call instance bool Microsoft.SharePoint.SPContentTypeId::IsChildOf(valuetype Microsoft.SharePoint.SPContentTypeId)
    L_01fc: brtrue.s L_0200
    L_01fe: br.s L_01ea
    L_0200: ldarg.0

At lines L_01f0 to L_01f7, the method checks to see if the content type is a child of workflow task. If not, it throws the exception. Turns out my workflow.xml file had its TaskListContentTypeId attribute set to 0x0108 (task), rather than 0x010801 (workflow task). I changed that and everything worked, the task was created and I was on my way to workflow nirvana.

It's just a shame that whoever implemented this bit of code didn't use the NotSupportedException that takes an error message as a parameter, then they could have told me what the problem was...