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

Tuesday, June 10, 2008

Fixing Flash problems in IE7

I've had this for a while, some of the Flash content on the BBC website wouldn't play (although this is better than the problems I had with their previous Real Player content which always seemed to kill my wireless router). I was told my version of Flash wasn't up to date. Re-installing Flash didn't make any difference and following the suggestions here didn't help either.

So I decided to have a closer inspection. I had a hunch it may have something to do with my user agent string, since Flash content worked in some places (including the BBC iPlayer, go figure). So I checked my user agent using the following HTML page

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Untitled Page</title>
</head>
<body onload="javascript:alert(navigator.userAgent)">

</body>
</html>

Weirdly this showed my browser as IE6... Which was odd. Searching a bit further I came across this Microsoft article on user agent strings. BTW, the suggestion to type javascript:alert(navigator.userAgent) into the address bar didn't work for me, my guess is this was a security hole waiting for an exploit so has been disabled. Anyway I had a look in my registry to see what was happening and it turned out I had a registry key under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\User Agent\Post Platform. Part of it pointed to bsalsa.com. I guess I must have installed some of their stuff some time ago though lord knows why they need to fiddle with user agent strings.

Anyway, after deleting this registry key and a restart of IE my user agent string returned to something more sensible and BBC Flash content suddenly sprang into life. Not only that but I can now login into my Fidelity account as well, although that's not necessarily such a good thing given the current state of the stock market.

Update - Hmm, the registry entry has re-appeared, so one of the apps I use quite regularly is writing to that spot in the registry. Next step is to figure out which app it is...

Update 2 - Looks like PHPEdit was responsible, an app written in Delphi which presumably uses one of the components that come from bsalsa.com. I guess any programs using the component may cause the same problem. Getting the latest build from their website fixed the problem.

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

Thursday, May 29, 2008

How record companies can save themselves

I got a lovely book for Christmas, "Factory Records - The Complete Graphic Album", which shows off the artwork for most of the albums and singles that came out on the Factory label along with other artwork used in ads etc. It got me thinking about why people are not buying music from record labels much anymore, instead choosing to download it free over the internet. Of course, one reason for this is the price but I think there's more to it than that. People have always taped music from friends but they still bought it as well.

But look at that Factory artwork again, some of it was superb (and some of it wasn't...). But buying a Factory record wasn't just about music, it generally came in a beautiful package as well. In the days of vinyl, there were little messages in the run-out groove (it wasn't just Factory that did this of course). Being 12" wide meant the artwork could be much more complex. So record companies should go back to vinyl? No, though they could certainly think about it. But the point is that when I bought music in those days I felt a connection with the artist or record company because it felt more than "product", it was sometimes a work of art.

Now if I pop down to a music shop most of the racks seem to be populated with CDs that look like they had absolutely no thought put into their production. Plastic case, crap photo of the artist, no design aesthetic. Why not download it for free? What do I gain by paying a tenner?

But it doesn't have to be this way. I just bought the boxed deluxe edition of U2's "The Joshua Tree". OK, I know, self-important and pretentious, but I do think it was a great album so I'll overlook the earnestness. For just over twice the price of a normal CD, I got 2 CDs (the album and a CD of rarities), a DVD (live concert, documentary and videos), a book and some postcards. All in beautiful packaging. I snapped it up without a second thought. Presumably Island are making money out of this, so why can't other recorded music come like this? Give people a reason for buying your music and they may well be persuaded.

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

Thursday, May 15, 2008

No End in Sight: Iraq's Descent into Chaos

Iraq doesn't get in the news much currently, with natural disasters and global financial problems overtaking it in the headlines. But a quick search of Google News shows plenty of people are still dying there. So 'No End In Sight' is still as relevant now as it was when it came out. And a quick précis of the file would be the US (and the UK of course) did one thing right, getting rid of Saddam, then screwed up everything possible after that. It doesn't bother to delve too deeply into the lies that led up to the war but looks at the aftermath instead. First there was no plan, then the plan that was hatched was insanely stupid and it's no surprise that the carnage carried on for so long. Get rid of the army to leave 500,000 soldiers with no work and with a grudge? Brilliant...

The director, Charles Ferguson, has made an unusual change of career. He started out in software, running the company that created FrontPage (and writing an entertaining book about it). Nice to see him put his millions to good use.

Monday, May 12, 2008

Absolutely

It's odd that I watched pretty much every episode of Absolutely when it was on and yet I'd completely forgotten about its existence until I came across an article about it in an obscure South London listings magazine. Perhaps not surprising since it being on telly coincided with my time at university when I was mostly drunk. Anyway, all 4 series are now available on DVD. Go and buy it now.

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, May 04, 2008

Removing the iTunes crapware

I have to admit to liking iTunes, but one thing I don't like is all the crap it installs alongside it. Some of this stuff is probably essential if you have an iPod, but if not it seems to be pretty much useless. I could get upset about this, but I find the simplest thing is to just remove the unneeded stuff (until the next update). Here's a list of what I've found installed on my PC and how to remove/disable it.

 

Startup programs

These can be removed using Windows Defender (Tools/Software Explorer/Startup Programs)

iTunesHelper.exe

QTTask.exe

 

Services

These can be disabled using Administrative Tools/Services

Apple Mobile Device

Bonjour Service

iPod service

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;
    }
  }
}

Tuesday, April 22, 2008

Become a money lender

OK, perhaps my dream of a world without money lenders is a pipe dream, but if that isn't a possibility then how about we cut out the middle man and do the lending ourselves? OK, we can't completely cut out the middle man (unless we lend out money to random people down the pub), but we can use a middle man to lend or borrow money directly, rather than effectively lending our money to the bank for a pitiful return.

Enter Zopa, who if nothing else, prove that all the good domain names have been taken. Basically you put your money in their system and offer to lend it out at whatever rate you like and it is lent out to people directly for a small fee. They work out the credit-worthiness of borrowers but you take the hit when borrowers default. The claimed interest rates are better than you'll get in a bank account, although it is still taxable. Saying that, tax isn't taken at source, so it's certainly possible to avoid paying tax on the interest earned (although that would clearly be a very bad thing to do...). It's called social lending and I like the idea.

The only downside is that your money is tied up for a potentially long time, you can't get the money back until the loan is paid off. I've put in a few hundred quid to see how well it works out. If it's a success I'll probably start to put money in on a regular basis. My current return? 11p from interest earned on the money sitting in the account waiting to be loaned out.

Monday, April 21, 2008

In defence of geeks

There's an ad for BT on the telly currently in which a mother talks to her partner about her worries for her daughter who's spending lots of time on the internet. She pauses for dramatic effect, as we are left to think of the terrible things that could be happening to the kid. So what is she concerned about? Some sexual predator grooming her child? Oh no, it's much much worse than that... She's worried that her daughter is turning into a geek.

Yeah OK, it's all light-hearted enough, but it misses an important point. If it weren't for geeks, there'd be no internet, no eBay, Amazon, Facebook and all the other crap that we can pass the time with. There'd be no computers for that matter, or iPods or mobile phones or CDs or any other electrical gadgets. I'm sure it was geeks (before they were called that) who developed cars, electrical power generation and the telephone.

So let's all the hail the geek, for producing all this stuff to make our lives so much richer (there is a hint of sarcasm there...)

Sunday, April 20, 2008

More on 0871 numbers

Somebody posted a comment to my little rant about 0871 numbers. Initially I thought it was just someone trying to defend the use of 0871 numbers but then I noticed the link to some site that sells 0871 numbers. A little search of the internet showed the same guy had posted comments to several other blogs with the same link. So a little spam on my blog, no problem, just delete the comment. But then I found a site that is trying to help people to avoid having to use 0871 numbers and other variants, so I thought I'd post a link to that site to help them out and to show spammers that their tactics may well have completely the opposite effect to what they were after...

Friday, April 11, 2008

Live Maps, what the ...?

As of today, if you go to Live Maps and you're based in the UK, you get redirected to Multimap instead. It's still possible to get to the real Live Maps via the URL http://maps.live.com/?mkt=en-us, but why on earth has Microsoft decided to do this? Live Maps has improved drastically over the last few months and is now at the very least on a par with Google Maps. Multimap, whilst certainly better than it was, is still way behind both of them.

All I can think is that Microsoft spent a significant amount of money on Multimap so isn't prepared to let it die a natural death. Multimap is plastered with ads as well so I guess they will earn some cash from it, something that presumably isn't happening with Live Maps. Unfortunately what will happen is people will just use Google Maps instead so Microsoft will lose out. 

Monday, April 07, 2008

Mapping house buying

I really like the user interface provided by Globrix to look at houses for sale, in particular the Google Maps integration. It's certainly much better than the UI of Right Move, although Property Bee adds immense power to Right Move and doesn't currently integrate with Globrix. One thing missing from Globrix is the ability to save properties, but apparently that will be coming along shortly.

So if we ever move house, finding a new one should be much easier than in the past.

Sunday, April 06, 2008

Control DVD

Imagine trying to convince your other half to go and see a film about a late 70s post-punk band from Macclesfield whose lead singer killed himself. Oh, and it's in black and white. I didn't even make the effort, so I waited for the DVD and bought that instead.

I grew up with New Order but Joy Division had always been that other band that transformed into New Order. It's only over the last few years that I've grown to appreciate their music more.

So, onto the film. We all know how it ends (and if you don't, you really should read more, like the start of this blog entry) so there's no surprise there. But given the myth that's grown up around Joy Division and more specifically Ian Curtis, it's perhaps surprising that he's portrayed as a mostly normal bloke, who just had serious problems coping with his increasing fame, epilepsy, fatherhood and his own infidelity. Fact is I think anybody would have trouble coping with all of that. He's also shown as not treating his wife particularly well either so rather than a troubled genius, he comes across as a pretty normal flawed human.

It pays to watch it at least a couple of times. When I watched it the first time I was constantly waiting for him to have a fit or to attempt suicide, rather than actually watch the film. And the film is worth watching since it's beautifully filmed (as you'd expect from Anton Corbijn) and pretty entertaining to boot.

I think the reason we are so drawn to musicians who die young is the thought of what they might have achieved had they lived and for Ian Curtis it's particularly true. For me their three greatest songs are 'Love Will Tear Us Apart', 'Atmosphere' and 'Ceremony', all written pretty close to his death and all sounding pretty damn good even now. So just as they were approaching greatness, he died. But saying that, the fact he died when he did may just add to the beauty in those songs...

Then again, if he had continued on, we almost certainly know how the story would have ended. Joy Division may have produced some more great songs and albums and become as big as U2 (as Peter Hook thinks), but at some point they'd have got old and crap, just like New Order did. So though his death must have been painful for his family and friends, perhaps it was best for us music fans.  

Friday, April 04, 2008

Credit crunch, what credit crunch?

With all this talk of a credit crunch, I'm somewhat confused. Isn't the requirement to have some kind of evidence of an income, a deposit and a clean credit history before being given a mortgage actually a return to the lending practices that were the norm just a few years ago? Now, having a pulse is not the only requirement to be given huge sums of money. Rather than a credit crunch, isn't what we are seeing a return to normal service? And that seems like a good thing to me.

Thursday, April 03, 2008

What I hate about London

I'm not sure at what point I switched from enjoying living in London to wanting to escape as soon as possible. It was probably a slow process but I'm definitely in the latter camp now. Here are a few of the reasons...

Too expensive - For sure you can get paid more if you work in London, but this is completely wiped out by the cost of living here. Ridiculous house prices, expensive travel costs, over-priced pub and restaurant prices all add up to a fortune.

People - I don't have a problem with the people as such, although of course people up North are friendlier, but there are just too many of them. There is nowhere to go to get away from everyone, although deep within the bowels of Wimbledon Common can be pretty quiet on a weekday.

The other problem is that a lot of the people here are only here temporarily, generally to earn a bit of cash before heading off somewhere else. It's hard to have a sense of community when your neighbours are constantly changing.

Driving - I remember when I was younger I used to enjoy driving but the reason I enjoyed It was because I was able to drive without thinking too much. Driving in London is a different matter. First you have to constantly look out for idiots who are going to pull out in front of you. Then you get harassed by some idiot who is determined to get to wherever they are going in as short a time as possible. Then when you get to where you're going to you have find somewhere to park, generally an almost impossible task.

Dysfunctional weather - I have happy memories as a kid of building igloos, sledging and putting chains on the Land Rover when the snow came down. In London I have memories of having a super quick snowball fight and trying to knock together a snowman before all the snow melted by lunchtime. The ground temperature is so high, snow has no chance of surviving. Other weather works kind of as expected I guess.

Commuting - Fortunately I don't have to suffer the pain of this anymore, boy am I glad about that.

Wednesday, April 02, 2008

SCOPE_IDENTITY() and @@IDENTITY

Note to self, always use SELECT SCOPE_IDENTITY() instead of SELECT @@IDENTITY. I knew this already but always forget which one I should be using.

http://codebetter.com/blogs/john.papa/archive/2006/04/07/142503.aspx

Tuesday, April 01, 2008

A world without money lenders

It is becoming increasingly clear that the UK's housing boom wasn't caused by a lack of supply and heightened demand, but rather by slack lending practices by the banks. The proof can be seen now that cheap credit has been taken away and house prices have almost immediately started to drop.

The question this makes me ask is what would happen if there were no mortgages? Presumably houses would be cheaper, essentially costing whatever they cost to build plus some kind of profit for the builder. And wouldn't we all be richer, since we wouldn't need to service such big debts? OK, we'd all have to save up some money to buy a place, but this would be easier since renting would also be cheaper, given that landlords would have a smaller outlay to buy a place and hence would be happy with a lower rent to still get a decent return.

OK, this is all something of a simplification of the economics, but it sure seems like things would be better. I can foresee a few problems. Banks would have to start lending money to other people, since their whole business model requires them to lend money in order to make a profit, but then they could be providing finance to companies who actually generate wealth, rather than to pay for some bricks and mortar.

And even if we wanted to get to this Utopia, we wouldn't want to start from where we are. There'd be a lot of unhappy people with mortgages who would see the value of their houses plummet (me included).

So it might not work, but there seems to be something fundamentally wrong with the current situation. We have got progressively richer but as we have, house prices have increased in line with our increased wealth, thus swallowing up any extra cash. Where's the sense in that? Wasn't the idea that we'd get wealthier so that we didn't have to work so hard?

Thursday, March 27, 2008

0871 numbers

I've been updating the Random Pub Finder to include phone numbers for the pubs on the site and during my Internet searches I've discovered that a few pub websites are showing 0871 numbers, rather than the actual pub's phone number. The phone call costs 10p a minute and the website gets a cut of the money made.

I don't have a problem with advertising on websites as a way to cover costs, so long as it's low key and relevant to the site. Certainly some sites over do it, but mostly it's harmless. The Random Pub Finder has never had advertising because our running costs are pretty low and we prefer not to have ads. Getting several hundred visitors a day is enough to rock my boat. If we ever got so many visitors that we had to pay significant hosting costs, we'd almost certainly have to get some advertising.

But something seems wrong about making money out of phone numbers. For a start, if I use the 0871 number I never actually find out the pub's phone number, so I'm stuck if I need to call it again (unless I use the 0871 number again). Second, why make money out of something that should be freely available? (and yes, directory enquiries should be free as well)

Fortunately, most sites still provide the pub's real number. If you agree with me, I suggest you vote with your feet and start to frequent these sites instead.

Wednesday, March 26, 2008

Property Bee

If you're obsessed with house prices, this is a lovely little add-on for FireFox that keeps track of any changes made to property ads on Right Move, so you can keep an eye on the impending price crash.

Wednesday, March 19, 2008

Free the postcode

It seems quite ridiculous that if you want to find out how UK postcodes relate to their geographic location you have to pay the Post Office for the privilege. Of course it's possible to pull this data out of Streetmap via a bit of screen-scraping (like I've done with my partial list of London postcodes) but this is almost certainly breaking copyright, although the Post Office haven't chased after me yet...

Anyway, it would appear there is a group of people trying to rectify this situation by getting everybody with a GPS device to find the location of any postcode area they happen to be in. I hope it works out for them, although it's insane that it's necessary.

Monday, March 17, 2008

Nothing more to say

For two and a bit years I've not had any trouble posting things here, ideas have sprung into my mind almost constantly. But over the last month or so, I've not had much inspiration. This may be a temporary problem, it may be a longer term issue, at the moment I don't know but I suspect things will be quiet here for a while. As Depeche Mode said 'Enjoy The Silence'

In the mean time, read Joel Spolsky's latest post, which just goes to show that you can regain your mojo, which he had seemed to have lost for some time.

Thursday, March 13, 2008

A picture

I thought I'd add try putting an image onto my header to see how it looks. It's a picture of Darwen Tower, and the view is quite similar to the one from my bedroom window for the first 17 years of my life, although the tower was much further away from our house. The view from my current abode is much less impressive, unless you're a fan of looking in people's windows across the street.

It would appear I can use this image as long as I credit the original photographer, one Andy Pagett, whoever he may be. You can find the original image on Flickr.

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.

Friday, February 29, 2008

Code - Charles Petzold

If he didn't have a blog, for me Charles Petzold would just be that guy who wrote the seminal book on Windows programming. But since he does have a blog, I now know he's written lots of other stuff as well. I guess that shows blogs can be pretty powerful advertising tools, and I guess I'm not the first person to say so.

Anyway, since I'm regularly reminded of his existence, I'd been thinking of purchasing "Code" for some time (or hoping somebody would buy it for me) and I eventually got round to it. To tell you the truth it wasn't really what I was expecting. Code to me is what I use to write software, but Petzold uses a much wider definition. Code in this book refers to any kind of code from Morse, Braille through telegraphs, onto machine code, ASCII and finally onto programming languages. Although it wasn't what I was expecting I certainly wasn't disappointed. Over the course of the book, Petzold explains why computers are built the way they are, starting from the basic building blocks of relays leading onto transistors, binary numbers and logic gates. This then leads onto more complex circuits finally ending up with a microprocessor. Then he adds to this to finally end up with something resembling the computer we know today.

And all the time, he is explaining why there is the need to build what we are trying to build. For instance, I remember learning abut flip-flops at Uni but I don't remember anybody ever explaining why they are useful (they're a basic form of memory if you're interested). Of course it's quite possible I just wasn't listening when that bit was explained but nevertheless "Code" was a great reminder of the things I've learnt in the past. It also helped to join the dots between different bits of technology.

The book is almost ten years old now, but due to its bias towards the history of computer technology it hasn't dated, unlike most tech books. I guess the final chapter may seem a little out of date but that isn't of major importance.

The only problem I can see with the book is that it may not be suited to non-techy people. There is nothing here that couldn't be understood by a lay-person but it would probably be pretty heavy going. But for a technical person looking for a refresher on the building blocks of computers it is perfect.

Monday, February 18, 2008

Zipping files for Windows Explorer

As part of my automated build process, I wanted to be able to ZIP up files. For this I used the excellent (and free!) ZipLib, but there a few gotchas if you want your generated ZIP files to be readable by the built-in Windows compressed folders functionality.

First, Windows XP doesn't like the Zip64 format used by default so that needs to turned off. Second, Windows doesn't like the full path to be included in the file names inside your ZIP file. This means the easiest to use ZipFile class can't be used. Finally, if you're planning to ZIP up the contents of a directory, make sure you delete any ZIP file that was created previously, otherwise you'll try and ZIP up the already created ZIP file.

All of these caught me out, but this is what I came up with that now seems to work.

      string fileName = Path.Combine(zipFolder, zipFileName);

      // delete the ZIP file if it exists already
      if (File.Exists(fileName))
        File.Delete(fileName);

      DirectoryInfo info = new DirectoryInfo(zipFolder);
      FileInfo[] files = info.GetFiles();

      // zip up the required files
      using (ZipOutputStream zipOutputStream = new ZipOutputStream(File.Create(fileName)))
      {
        zipOutputStream.UseZip64 = UseZip64.Off;
        byte[] buffer = new byte[4096];
        for (int i = 0; i < files.Length; i++)
        {
          ZipEntry entry = new ZipEntry(files[i].Name);

          entry.DateTime = DateTime.Now;
          zipOutputStream.PutNextEntry(entry);

          using (FileStream fs = File.OpenRead(files[i].FullName))
          {
            entry.Size = fs.Length;

            int sourceBytes;
            do
            {
              sourceBytes = fs.Read(buffer, 0, buffer.Length);
              zipOutputStream.Write(buffer, 0, sourceBytes);
            }
            while (sourceBytes > 0);
          }
        }

        zipOutputStream.Finish();
        zipOutputStream.Close();
      }

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>

Sunday, February 10, 2008

Metastorm book

If you've ever wanted to read about Metastorm BPM when you haven't got access to a computer, before now there wasn't really anything available. Now my boss and BPM guru Jerome Pearce has written the 'Metastorm BPM Developer's Guide'. So you can now read all about developing processes when you're sat on the loo.

http://www.lulu.com/content/1862471

Lulu looks like a great website for cheap book publishing, something I hope to be taking advantage of soon. I'll keep you posted.

Friday, February 08, 2008

Who is Edward Phillips?

Over the last few weeks I've noticed a strange thing in iTunes. Occasionally a shared music library will pop up called 'Edward Phillips's Library'. I'm even able to connect to it and listen to music. It's great, he has over 11000 tracks, not all to my taste but still something else to listen to. I did wonder where this new source of music was coming from and tonight I figured it out. I had a look at my router's web interface and noticed a few unknown computers listed in the DHCP active IP table, in fact four computers (although I suspect they aren't all connected right now).

ever since I figured out what was causing our wireless issues I've switched back to an insecure wireless network and I've never bothered to filter out MAC addresses this time around. So it looks like people are taking advantage of the fact. Am I bothered? Not really. I've not noticed any particular performance problems and it seems like my generosity has been repaid by having access to some different music.

On another note, I wonder what the music industry makes of all this? I'm guessing they don't want people sharing out their music collections to all and sundry, we should be good consumers and buy all the CDs instead. But then it's Apple doing this and they make plenty of money for the music industry with their DRM downloads. So perhaps they'll turn a blind eye?

Currently listening to Purple Rain by Prince from the album Purple Rain

Wednesday, February 06, 2008

FeedDemon

I've been using RSS Bandit for many years. I did check out FeedDemon when I was initially looking for an RSS reader, but was put off by the price. Yeh I'm a tight arse, what was it, about $25 or so? But RSS Bandit was free and good enough for my needs. Anyway, when I found out FeedDemon was going to be free, I decided it was time to look at again. And boy am I glad I did.

First, it just works. Whereas RSS Bandit seemed to have a few bugs (the toolbar turning into a big red cross or the app not loading forcing me to delete some XML files) FeedDemon hasn't crashed once. Actually I'd like to see FeedDemon crash, just to see how it handles it (I'm hoping it offers to send the details off so it can be fixed)

Second, it's faster. Yes, I love .NET but real proper compiled Delphi code is always quicker, unless you screw it up, which Nick Bradbury hasn't done.

Third, it has more features. The dinosaur report has been most useful for clearing out feeds I never read.

I have only one request. I signed up for Newsgator for no other reason than I thought this feature would appear (Newsgator serves me no purpose since I'm pretty much always at the same machine) but it didn't. Since Newsgator presumably has a huge database of users and their feeds, I'm guessing it wouldn't be too difficult to suggest new feeds I should subscribe to based on my current feed list. I dunnow, perhaps there are privacy issues around this but I'd sure like to see it available.

Friday, February 01, 2008

Why dynamic languages suck

I had a little bit of JScript that wasn't working as expected. I stared and stared at it... For a very long time... Here it is.

var splitDetails = details.split("\t");
for (var i=0; i<splitDetails.Length; i++)
{
  // do something
} 

The problem was that the //do something was never getting hit. Life would have been easier with a debugger but this was server-side JScript in Metastorm which doesn't support debugging so I was forced to just stare at it to work out what was wrong.

No doubt you're now shouting at your computer "you're a fecking idiot! The problem is obvious!" and perhaps you'd be right. Perhaps I was just having a senior moment, which I think I'm entitled to have at my age. For those of you who haven't spotted the error I'd typed length with an upper-case L.

So what happens here? JScript doesn't throw an error because I've tried to access a property that doesn't exist, instead it returns null. Then I presume it converts that to 0 for the loop test so never drops into the code inside the loop.

This kind of thing is all too easy in a dynamic language. If I was using a compiled statically-typed language the compiler would have picked up the problem immediately. Perhaps the advantages of dynamic languages outweigh these kind of problems but personally I'd rather have the crutch of a compiler.

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

Thursday, January 24, 2008

Google wants me

OK, not quite but I did receive an email the other day saying

Google Sydney Engineering division are visiting London to talk about R&D in Australia. You have either been referred by a friend or current Google employee as someone who may be interested in joining us for a special G'Day Google event at Google UK in London.  The evening will include plenty of food and beverages, Aussie style of course...blah...blah... If you are also interested in what is happening in Google London, this will also be a chance to meet local Googlers who can give you an insight into our local engineering divisions.

This intrigued me. First, who had referred me? I don't know anyone at Google but I do know a few people in Australia, so I guess it must have been one of them (perhaps my boss is trying to give me a gentle hint!).

And why are Google Australia coming to the UK to try and recruit people? Do they think all our current bar staff want to return home and start working for Google? Are a lot of the Ozzies over here IT people? Or are they after getting UK residents to move over there?

Anyway, I was interested in going along, not because I think I'd have any chance of working for Google since my predominantly Windows background wouldn't fit in with their open source Linux and Python development. I also doubt I'd manage to refrain from laughing when asked one of their typical interview questions, presuming I got that far. But I was interested in knowing what they do and the kind of people who work there and they did mention beer... OK, they didn't mention beer but they did mention Australian beverages which I can only assume means beer.

They provided a link to their site to get more details and to RSVP. Except it doesn't work and hasn't done for two days. I'm wondering if this is some kind of test in itself, to weed out those people not clever enough to figure out how to find their web page.

Update - After a little research it would appear the URL provided on their email is a link to one of Google's internal subdomains. Clearly not all of Google's employees are geniuses.

Sunday, January 20, 2008

What are spammers up to?

The Random Pub Finder has a form for suggesting pubs to be added to the database. For a long time, we've had spammers posting lots of dodgy URLs via the form, which is not unexpected, since they think they will get added directly to the database. In fact, the suggestions just get emailed to me, in part to reduce the risk of dodgy URLs ending up on the site and also because I've never got round to automating the process.

Lately the spam has been increasing but not in any sensible way. I'm getting things like "Bill, Man Hatten", "Halo, Moscow" and "Diesel, Washington" coming through. It's left me scratching my head as to what these guys are up to. Why would they be submitting this stuff? It isn't linking through to anything and doesn't seem to contain anything of interest. Are we just the victims of an exceedingly stupid spammer?

Friday, January 18, 2008

Using var in C# 3.0

I hadn't really read much about the new var keyword in C# 3.0 but it had troubled me. I had assumed it was like the var keyword in JScript and meant C# was turning into a late-bound dynamic language which I really wasn't keen on. As I mentioned previously, this can cause problems in JScript.NET when the compiler can't infer the type.

So I was pleased to see that the IL generated by this is still the same as if strongly-typed variables were used and if the compiler can't infer the type from the code, it won't compile. But there's still something of a problem. The addition of this new keyword means that coders can now just declare every local variable using it (fortunately not member variables) which will lead to less clear code. Explicit declarations are good, there's no question what is going on.

And I just know in a few months/years time I'm going to be looking at some code I have to maintain and I will be swearing under my breath and asking why some pillock declared everything using var.

Friday, January 11, 2008

Great titles don't always lead to lots of readers

Some time ago Nick Bradbury (author of the marvelous FeedDemon which is now free!) said that the best way to increase feed readership is to use great titles. Whilst this is undoubtedly true if your blog has a lot of subscribers, the fact is most of us Z-list bloggers don't actually have many people who subscribe to our blogs. According to Google Sitemaps I currently have two people signed up via Google Reader, which I'm pretty astounded by given my tendency to ramble on incoherently about random subjects.

The fact is 90% of my hits come from Google and Google doesn't care about great titles, it cares about matching up search terms to relevant content. One good way to drive traffic to your site is to write about things that interests searchers and that other people aren't writing about, given that if some A-list blogger does write about it you'll get shoved down the search result list. For me, SonicWall, HTML tick marks and Vista sound problems are the most popular subjects...

Anyway, the things to have in your titles are the words people are likely to use when they are searching for something related to your post. If it's in the title, Google will consider the text more important than if it's just in the body of the post somewhere. So forget great titles, have relevant titles.

Tuesday, January 08, 2008

JScript.NET performance

JScript.NET doesn't really seem to have caught on too much. The reasons are clear, there is no IDE support for it in Visual Studio.NET and if the company that developed the language doesn't want to provide an IDE, who else is likely to?

But since Metastorm BPM uses JScript.NET as its primary interface to the world of .NET I have to use it on a fairly regular basis. Many moons ago I had a look at the IL code produced from compiling JScript.NET and was horrified to see it was all late bound calls. Funnily enough the primary reason somebody had decided to use JScript.NET rather than JScript was to try to improve the performance of 8000 lines of server-side JScript code (why he had ever thought that 8000 lines of JScript code would not have performance problems I don't know). Anyway, compiling it did improve the performance somewhat but probably not so much as if the IL code produced had not been late bound.

So now several years later, whilst preparing some course notes, I thought I'd take a look at what the IL code looked like these days in .NET 2. And the results are worth noting I think.

Say we have two methods as follows

        public static function DoSomethingFast() : Object
        {
            var test = new StringBuilder();
            test.Append("a");
            test.Append("a");

            return test.ToString();
        }   

        public static function DoSomethingSlow() : Object
        {
            var test = "Hello world";

            var test = new StringBuilder();
            test.Append("a");
            test.Append("a");

            return test.ToString();
        }

After compiling that with jsc.exe, the IL produced for the first method looks like this

.method public static object DoSomethingFast() cil managed
{
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Text.StringBuilder builder,
        [1] object obj2)
    L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0005: stloc.0
    L_0006: ldloc.0
    L_0007: ldstr "a"
    L_000c: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0011: pop
    L_0012: ldloc.0
    L_0013: ldstr "a"
    L_0018: call instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_001d: pop
    L_001e: ldloc.0
    L_001f: callvirt instance string [mscorlib]System.Text.StringBuilder::ToString()
    L_0024: stloc.1
    L_0025: br L_002a
    L_002a: ldloc.1
    L_002b: ret
}

Looking at that lifted my spirits since it looks like the kind of code you'd get from a compiled C# method, no late-binding at all. Now on to the second method, which has only one difference from the first one.

.method public static object DoSomethingSlow() cil managed
{
    .maxstack 11
    .locals init (
        [0] object obj2,
        [1] object obj3,
        [2] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding,
        [3] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding2,
        [4] class [Microsoft.JScript]Microsoft.JScript.LateBinding binding3,
        [5] object obj4,
        [6] object obj5,
        [7] object obj6)
    L_0000: ldstr "Append"
    L_0005: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_000a: stloc.2
    L_000b: ldstr "Append"
    L_0010: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0015: stloc.3
    L_0016: ldstr "ToString"
    L_001b: newobj instance void [Microsoft.JScript]Microsoft.JScript.LateBinding::.ctor(string)
    L_0020: stloc.s binding3
    L_0022: ldstr "Hello world"
    L_0027: stloc.0
    L_0028: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_002d: stloc.0
    L_002e: ldloc.2
    L_002f: dup
    L_0030: ldloc.0
    L_0031: ldtoken TestSpeed.TestClass
    L_0036: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_003b: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0040: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_0045: ldc.i4.1
    L_0046: newarr object
    L_004b: dup
    L_004c: ldc.i4.0
    L_004d: ldstr "a"
    L_0052: stelem.ref
    L_0053: ldc.i4.0
    L_0054: ldc.i4.0
    L_0055: ldtoken TestSpeed.TestClass
    L_005a: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_005f: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0064: pop
    L_0065: ldloc.3
    L_0066: dup
    L_0067: ldloc.0
    L_0068: ldtoken TestSpeed.TestClass
    L_006d: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0072: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_0077: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_007c: ldc.i4.1
    L_007d: newarr object
    L_0082: dup
    L_0083: ldc.i4.0
    L_0084: ldstr "a"
    L_0089: stelem.ref
    L_008a: ldc.i4.0
    L_008b: ldc.i4.0
    L_008c: ldtoken TestSpeed.TestClass
    L_0091: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0096: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_009b: pop
    L_009c: ldloc.0
    L_009d: stloc.s obj4
    L_009f: ldloc.s obj4
    L_00a1: isinst object
    L_00a6: dup
    L_00a7: stloc.s obj5
    L_00a9: brfalse L_00ba
    L_00ae: ldloc.s obj5
    L_00b0: callvirt instance string [mscorlib]System.Object::ToString()
    L_00b5: br L_00ee
    L_00ba: ldloc.s obj4
    L_00bc: stloc.s obj6
    L_00be: ldloc.s binding3
    L_00c0: dup
    L_00c1: ldloc.s obj6
    L_00c3: ldtoken TestSpeed.TestClass
    L_00c8: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00cd: call object [Microsoft.JScript]Microsoft.JScript.Convert::ToObject(object, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00d2: stfld object [Microsoft.JScript]Microsoft.JScript.LateBinding::obj
    L_00d7: ldc.i4.0
    L_00d8: newarr object
    L_00dd: ldc.i4.0
    L_00de: ldc.i4.0
    L_00df: ldtoken TestSpeed.TestClass
    L_00e4: call class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine::CreateEngineWithType(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_00e9: call instance object [Microsoft.JScript]Microsoft.JScript.LateBinding::Call(object[], bool, bool, class [Microsoft.JScript]Microsoft.JScript.Vsa.VsaEngine)
    L_00ee: stloc.1
    L_00ef: br L_00f4
    L_00f4: ldloc.1
    L_00f5: ret
}

Suddenly things look terribly messy again, with lots of late-binding goo all over the place. The reason is pretty straight forward I think. In the first case, the compiler can infer the type of the test variable, whereas in the second case it can't because the variable is used to hold two different types. It does however mean that minor changes to code can lead to it being compiled completely differently without any warning, which is a bit of a worry.

Thursday, January 03, 2008

Writing software for mutant women

For me, the best part of "Dead Ringers" (the David Cronenberg film, not the mildly amusing impressions show) is the gynaecological instruments developed by one of the freaky medical twins. He's so out of his head on drugs that he thinks the women who are coming to his surgery are all mutants so he has to produce some terrifying new instruments to deal with them.

What brought this to mind was seeing the film again for the first time in about 15 years and also reading one of my Christmas books, "Why Software Sucks". Although seemingly unrelated, I think there is some common ground. As Davis Platt points out many times in his book, the reason why software sucks is because us software developers don't understand our users. We may not be out of our minds on drugs (at least not all the time) but our lack of empathy with our users mean we continually produce software that doesn't meet their needs.

I'm mostly in agreement with him here, although I'm not sure if he's having a go at developers directly or more generally companies that produce software. As a rule, the actual developers don't always have much of a say in what gets pushed out the door. Product management, sales, marketing, CTOs, QA and a host of other people have a pretty big input into what goes into software and how it should look and feel, whereas developers are often just following orders.

There is a reason I'm not too keen on the book, it does tend to re-hash a lot of what Alan Cooper wrote in "About Face". To be fair he is aiming at a different audience, normal people, who are pretty unlikely to have read Cooper's book. Me being a geek and not a normal person have read Cooper's book and Platt's book. If you're a developer, I'd recommend "About Face", if you're just a normal person forced to use software, I'd recommend "Why Software Sucks".

And as a developer I'm trying my best to write software that is not for mutant women.