Thursday, July 09, 2009

More on string.Concat vs the + operator in C#

A post of mine from 3 years ago about the performance differences between using string.Concat and the string class’s + operator got its first comment yesterday so I thought I’d flesh out what I said there to clarify what happens. First, here’s a little test program to show different ways to concatenate strings.

  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("a" + "b" + "c" + "d");
      Console.WriteLine(string.Concat("a", "b", "c", "d"));

      string a = "a";
      string b = "b";
      string c = "c";
      string d = "d";
      Console.WriteLine(string.Concat(a, b, c, d));
      Console.WriteLine(a + b + c + d);
    }
  }

So now lets look at the IL generated from that, using our old friend Reflector.

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 4
    .locals init (
        [0] string a,
        [1] string b,
        [2] string c,
        [3] string d)
    L_0000: nop 
    L_0001: ldstr "abcd"
    L_0006: call void [mscorlib]System.Console::WriteLine(string)
    L_000b: nop 
    L_000c: ldstr "a"
    L_0011: ldstr "b"
    L_0016: ldstr "c"
    L_001b: ldstr "d"
    L_0020: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_0025: call void [mscorlib]System.Console::WriteLine(string)
    L_002a: nop 
    L_002b: ldstr "a"
    L_0030: stloc.0 
    L_0031: ldstr "b"
    L_0036: stloc.1 
    L_0037: ldstr "c"
    L_003c: stloc.2 
    L_003d: ldstr "d"
    L_0042: stloc.3 
    L_0043: ldloc.0 
    L_0044: ldloc.1 
    L_0045: ldloc.2 
    L_0046: ldloc.3 
    L_0047: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_004c: call void [mscorlib]System.Console::WriteLine(string)
    L_0051: nop 
    L_0052: ldloc.0 
    L_0053: ldloc.1 
    L_0054: ldloc.2 
    L_0055: ldloc.3 
    L_0056: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_005b: call void [mscorlib]System.Console::WriteLine(string)
    L_0060: nop 
    L_0061: ret 
}

OK, so looking at the first method where we concatenate string literals using the + operator and we can see the compiler helps us out by concatenating the strings at compile time, which is going to be as optimal as possible. The second example shows that the compiler doesn’t do this magic when we use string.Concat so string.Concat is actually slower in this scenario.

Now if we look at the next examples where we concatenate string variables, the generated IL is exactly the same! So the performance characteristics are likely to be somewhat similar to say the least. Things get more interesting when you get beyond 4 strings since there is no version of string.Concat that takes more than 4 parameters, so they have to be pushed into an array but the result is the same, the + operator generates the exact same code as string.Concat.

So I can’t see a scenario where you’d want to use string.Concat (unless you’re particularly fond of it) and if string concatenation performance is an issue, you probably should be using the StringBuilder class.

Wednesday, July 08, 2009

Encryption in Metastorm BPM

A question came up on the Metastorm forums about encrypting sensitive data contained in custom variables so I thought I’d see what I could come up with. I took this C# code and translated it to JScript.NET, which looks something like this

import System;
import System.IO;
import System.Security.Cryptography;
import System.Text;
import eWork.Engine.ScriptObject;

package Encrypt.Encrypt
{
    public class Encryption
    {
        private static const password : String = "password";
        public static function Encrypt( ework: SyncProcessData, args: Object[] ) : Object
        {
            // args[0] - string to encrypt
            // returns the encrypted string

            var encrypt : Encryption = new Encryption(password);
            return encrypt.Encrypt(args[0]);
        }

        public static function Decrypt( ework: SyncProcessData, args: Object[] ) : Object
        {
            // args[0] - string to decrypt
            // returns the decrypted string
            if (args[0] == "")
                return "";

            var encrypt : Encryption = new Encryption(password);
            return encrypt.Decrypt(args[0]);
        }

        function Encryption(password : String)
        {
            GenerateKey(password);
        }

        private var Key : byte[];
        private var Vector : byte[];

        private function GenerateKey(password : String)
        {
            var sha : SHA384Managed  = new SHA384Managed();
            var b : byte[] = sha.ComputeHash(new ASCIIEncoding().GetBytes(password));

            Key = new byte[32];
            Vector = new byte[16];

            System.Array.Copy(b, 0, Key, 0, 32);
            System.Array.Copy(b, 32, Vector, 0, 16);
        }

        public function Encrypt(plainText : String) : String
        {
            var data : byte[] = new ASCIIEncoding().GetBytes(plainText);

            var crypto : RijndaelManaged = new RijndaelManaged();
            var encryptor : ICryptoTransform = crypto.CreateEncryptor(Key, Vector);

            var memoryStream : MemoryStream = new MemoryStream();
            var crptoStream : CryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);

            crptoStream.Write(data, 0, data.Length);
            crptoStream.FlushFinalBlock();

            crptoStream.Close();
            memoryStream.Close();

            return Convert.ToBase64String(memoryStream.ToArray());
        }

        public function Decrypt(encryptedText : String) : String
        {
            var cipher : byte[] = Convert.FromBase64String(encryptedText);

            var crypto : RijndaelManaged = new RijndaelManaged();
            var encryptor : ICryptoTransform = crypto.CreateDecryptor(Key, Vector);

            var memoryStream : MemoryStream = new MemoryStream(cipher);
            var crptoStream : CryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read);

            var data : byte[] = new byte[cipher.Length];
            var dataLength : int = crptoStream.Read(data, 0, data.Length);

            memoryStream.Close();
            crptoStream.Close();

            return (new ASCIIEncoding()).GetString(data, 0, dataLength);
        }
    }
}

Then all that is required is to decrypt the string when the form is loaded and encrypt it when the form is saved, like so

%sensitive:=%ScriptEval(JScript.NET,,%Procedure.Name,%MapName,"Encrypt.Encrypt.Encryption.Decrypt",%sensitive )

%sensitive:=%ScriptEval(JScript.NET,,%Procedure.Name,%MapName,"Encrypt.Encrypt.Encryption.Encrypt",%sensitive)

Now the user should see the unencrypted text and the encrypted version will be stored in the database. You will also need to decrypt the data anywhere else you need to use it.

One thing to realise at this point is that the system is still not secure. Although it will stop casual viewers who just run a query against the custom variable table, it won’t stop a more professional hacker. The script text is also stored in the database, so a hacker can have a look at that and find the password used to encrypt/decrypt the data. A more secure implementation would store the password in a location that only the engine account has access to, assuming the engine account is also locked down.

Download the demo procedure

Saturday, July 04, 2009

MooZoom with image maps

MooZoom is a nice piece of JavaScript that adds zoom and pan functionality to images, built on top of MooTools. It wasn’t exactly what I needed. I didn’t want the zoom/pan to be constrained to the original size, I wanted the ability to zoom out beyond the original size and I wanted image maps to be handled correctly. I’m quite pleased with the results and you can download the source here.

To use it, in your image simply set

class="moozoom"

Friday, July 03, 2009

Day 21 – where I get offered a job

3 weeks in and I get a job offer, pretty good going I think. I have the weekend to think about it. And given that a bird in the hand is worth two in the bush (even if the birds in the bush have really nice plumage) I will probably accept it.

For any other IT job hunters in the current climate, here’s my advice.

  • Throw your CV onto every job website out there.
  • Make sure everyone you know is aware you are looking for work, have no shame!
  • If you have a website or blog, make it obvious you are looking for work. Play the percentages game, every person who sees you are looking for work may be a potential employer.
  • Don’t demand to get paid as much as you were paid before. Assuming you’re out of work like me, your current income is zero or thereabouts, so your previous salary is pretty much irrelevant.
  • Accept all interviews. Even if the job isn’t a perfect fit, it’s good to get back into the interviewing groove, which will help when a better role turns up. And who knows, a job that doesn’t appear perfect on paper may turn out to be better than expected.
  • When you have an interview and you get asked about something you have no knowledge of, go off and investigate it. OK, it’s too late for that particular interview but it might come up again.
  • Learn about other technologies which may have passed you by in the past. Today the guy I spoke to said he was impressed with my use of the JavaScriptSerializer class, which I’d only started playing with the day before my technical test.

Wednesday, July 01, 2009

Resizing an image map when zooming an image

There are some cool libraries out there for zooming images in a HTML document, but none that I’ve seen handle resizing an image map attached to the image. My research may be incomplete so I might be recreating the wheel here, but this simple solution seems to fix the problem. Now I need to integrate with one of those libraries.

First up my HTML looks like this

      <map id="map" name="map">
        <area coords="15,92,568,247" alt="Blah" href="javascript:alert('hello');" />
        <area coords="18,259,546,432" alt="Blah" href="javascript:alert('hello 2');" />
      </map>
      <input type="button" value="+" onclick="javascript:ZoomIn();"/>
      <input type="button" value="-" onclick="javascript:ZoomOut();"/><br />
      <img src="Highlight cells.png" usemap="#map" id="image" />

And then there is some JavaScript to do the resizing, that looks like this

  <script type="text/javascript">
    function ZoomIn() {
      Zoom(1.1);
    }

    function ZoomOut() {
      Zoom(0.9);
    }

    function Zoom(amount) {
      // resize image
      var image = document.getElementById('image');
      image.height = image.height * amount;

      // resize image map
      var map = document.getElementById('map');
      for (var i = 0; i < map.areas.length; i++) {
        var area = map.areas[i];
        var coords = area.coords.split(',');
        for (var j = 0; j < coords.length; j++) {
          coords[j] = coords[j] * amount;
        }
        area.coords = coords[0] + ',' + coords[1] + ',' + coords[2] + ',' + coords[3];
      }
    }
  </script>

Day 19 – More calls

Quite a few calls from agents today, including some jobs that sound very interesting. What I’ve noticed is that I never hear anything back about the roles that sounds particularly interesting, just the run of the mill stuff. I guess the interesting jobs get absolutely deluged with CVs so can pick and choose people who look to be perfect. My first call about Metastorm work as well, so there is some work out there on that side of my skills. And I will return to Croydon for yet another interview on Friday.

Tuesday, June 30, 2009

Day 18 – Bell, Doogal Bell

So little happened today that I actually answered the door to the Jehovah’s Witnesses and tried to convert them to Atheism. I did have a call from an agent wondering if I’d be interested in a contract position in Sweden and an email asking if I spoke French and would be interested in a job in Paris. Being an international man of hacking does appeal I guess, flying from nation to nation with only a laptop loaded up with Visual Studio.

Improving the Local Search .NET call

Following on from my post about using Google Local Search from C#, I thought I’d try to improve it. Deserializing the JSON data ended up with some ugly typecasts and manipulation of Dictionarys. The first thing to notice is that the JavaScriptSerializer class has a Deserialize<T> method so all that is needed is a class to hold the returned data. Here’s a simple implementation of this.

  public class Results
  {
    public double lat;
    public double lng;
  }

  public class ResponseData
  {
    public Results[] results;
  }

  public class LocalSearchData
  {
    public ResponseData responseData;
  }

OK, I know, there’s a bit of a lack of OO encapsulation going on there but it seems like public fields with names matching the data returned from the JSON are required. They can be replaced with properties, but these must have getters and setters so this doesn’t really buy you much except to stop FxCop moaning at you.

Then the deserializing code looks much nicer

        LocalSearchData searchData = serializer.Deserialize<LocalSearchData>(response);
        latitude = searchData.responseData.results[0].lat;
        longitude = searchData.responseData.results[0].lng;

This still isn’t perfect. We have to use the same names as used in the JSON, which doesn’t really match up with .NET naming conventions and we have a class hierarchy that doesn’t really serve a purpose. It looks like the JavaScriptConverter class might help out here but that’s something to look at another day. Another alternative might be to just use these classes for moving the data into yet another class that has a better interface.

Monday, June 29, 2009

Day 17 – A sweaty interview

Croydon is “Manhattan as imagined by Le Corbusier” apparently, or so my mate Jethro says. I’d agree to an extent, except I’d have to say it’s Manhattan without the glamour. And today it was a particularly sweaty unglamorous place.

I was faced with a technical test, which I quite liked since I could do it. But as is always the case with these kind of tests, there are a hundred ways to implement a solution, so it all depends whether what I’ve done resonates with the person looking at the code.

And then onto an interview, where the interviewers basically said they are looking for someone who can be a team leader, top quality developer, project manager, software architect and product manager. Oh and they don’t want to pay very much. Of course I said I could do all of these things, although I wanted to shout “you will never find anybody who meets all those requirements with the money you’ve got to offer!”

Local Search web service

All these cool pieces of AJAX code are great but what if you want to use them from some server-side code? Local Search doesn’t provide any kind of web service API as far as I’m aware, but all AJAX calls eventually have to resolve down to simple HTTP calls. So it should be possible to use them from a server-side piece of code. To test out this theory, I thought I’d see if I could write some C# code to use Google’s Local Search AJAX API to get the latitude and longitude for a postcode as if it was a web service call.

So to see what is happening under the hood, we need to fire up Fiddler and use a page that uses the Local Search API, like this one. If we issue a query using that page, we can see the URL used is something like this.

http://www.google.com/uds/GlocalSearch?callback=google.search.LocalSearch.RawCompletion&context=0&lstkp=0&rsz=small&hl=en-GB&gss=.uk&sig=b211652959f1f93330a3286c1a81eab6&q=KT1%203EG%2C%20UK&sll=37.77916,-122.42009&gll=37747397,-122451853,37810922,-122388328&llsep=500,500&key=ABQIAAAAjtZCgAx5i04BiZDO6HlxhRQUdBDpWCOMRMbgTcqadX0jQ8HOERSxXxhk24TIBUpivovAKLrnpSio9w&v=1.0&nocache=1246220526894

And the returned data is in JSON format. Click on that link in a browser and you should see the returned JSON data. And if we move to the .NET world and make the call from C#, like this

      HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
        "http://www.google.com/uds/GlocalSearch?callback=google.search.LocalSearch.RawCompletion&context=0&lstkp=0&rsz=small&hl=en-GB&gss=.uk&sig=b211652959f1f93330a3286c1a81eab6&q=KT1%203EG%2C%20UK&sll=37.77916,-122.42009&gll=37747397,-122451853,37810922,-122388328&llsep=500,500&key=ABQIAAAAjtZCgAx5i04BiZDO6HlxhRQUdBDpWCOMRMbgTcqadX0jQ8HOERSxXxhk24TIBUpivovAKLrnpSio9w&v=1.0&nocache=1246220526894");
      WebResponse resp = req.GetResponse();
      Stream respStream = resp.GetResponseStream();
      StreamReader reader = new StreamReader(respStream);
      string response = reader.ReadToEnd();
      MessageBox.Show(response);

This works as well. Which is good since it suggests Google aren’t doing anything to stop people using the API from ‘browsers’ that aren’t really browsers. So the next thing to figure out is which bits of the URL are required. So after removing all the parameters that aren’t needed, we are left with

http://www.google.com/uds/GlocalSearch?q=KT1%203EG%2C%20UK&v=1.0

I was somewhat surprised at how few of the parameters are actually required for the call to still work. Even the user’s API key isn’t needed. Of course, since this is completely undocumented, this may change in the future. In fact last time I tried to do this, I’m fairly certain it was a lot harder to get the HTTP call to work from .NET.

So now we know what URL is required, we just need to be able to parse the returned JSON data into something more .NET friendly. Fortunately .NET 3.5 provides the JavaScriptSerializer class to serialize and deserialize JSON strings. So putting it all together, we get a fairly simple implementation

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Web.Script.Serialization;

namespace LocalSearch
{
  public static class Postcode
  {
    public static void Geocode(string postcode, out double latitude, out double longitude)
    {
      HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
        string.Format("http://www.google.com/uds/GlocalSearch?q={0}%2C%20UK&v=1.0", postcode));
      using (WebResponse resp = req.GetResponse())
      using (Stream respStream = resp.GetResponseStream())
      using (StreamReader reader = new StreamReader(respStream))
      {
        string response = reader.ReadToEnd();
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        Dictionary<string, object> deserialized = 
          (Dictionary<string, object>)serializer.DeserializeObject(response);
        Dictionary<string, object> responseData =
          (Dictionary<string, object>)deserialized["responseData"];
        object[] results = (object[])responseData["results"];
        Dictionary<string, object> resultsData =
          (Dictionary<string, object>)results[0];

        latitude = Convert.ToDouble(resultsData["lat"]);
        longitude = Convert.ToDouble(resultsData["lng"]);
      }
    }
  }
}

This could be improved by using Deserialize<T> instead of DeserializeObject but that would involve writing some classes to hold the returned JSON data I think. I might look at that some other time.

Of course this provides exactly the same functionality as my StreetMap screen scraping code of many moons ago, so what’s the point? Really just to show a fairly generic method of using AJAX calls from .NET server-side code.

Friday, June 26, 2009

Day 14 – my first real interview

To be frank I thought this would be a complete train crash of an interview. I’d been asked to do a presentation on some of the work I’d done during my last job and I generally don’t feel overly confident doing presentations. So I guess it was a success because my presentation wasn’t awful. It was helped by the fact that the interviewer seemed to be checking his email most of the time I was talking.

The rest of the interview seemed to go OK, although my interview assessing antennae seem to be a little off at the moment. I met the agent afterwards and he said because there are so many candidates going after jobs, employers are getting much choosier about who they take on. Whereas in the past being a 75% match may have been enough, now they only accept a 100% match. Which may explain my incorrect assumptions about how well interviews have gone so far.

Thursday, June 25, 2009

Day 13 – where my hopes are dashed

So today I had a telephone interview with a potential employer and things seemed to be going pretty well. I think you can generally tell how well an interview is going. Some times it’s clear the two of you are not really on the same wavelength, but other times it’s like you’re talking to someone who you could imagine as a friend, or at least a work mate. Then he mentioned a company I’ve done some work for recently and I thought things were going even better. In the past, quite a few of my jobs have come not necessarily from my knowledge or experience but from having some kind of connection with the company I’m interviewing with. At Metastorm, one of my former colleagues worked there. At APT, I knew the person who would become my boss. At Process Mapping, the boss was a former work mate. Of course, I’m sure that’s not the only the reason I got those jobs, but it certainly didn’t reduce my chances.

So the thing was having that connection made me think I’d at least get a face to face interview. But then the agency tells me they’ve decided not to go any further and I was somewhat disappointed. If it had been one of those awkward interviews where there is no common ground I’d have been cool with it, but now I’m sitting here wondering what did I miss, what did I say that came across badly and how do I rectify it? But then of course the next interview will be with someone completely different who has completely different requirements and a completely different perspective on who they are looking for. So it’s a different game, one where you find out the rules after the game has finished.

Wednesday, June 24, 2009

Day 12 – where I go to the Job Centre

I dunnow, I was kind of expecting the Job Centre to be full of terrifying men shouting ‘gisajob!’, but it was actually quite a pleasant experience, mostly normal people looking for jobs and the staff were perfectly friendly, not the intimidating bunch I’d been led to believe. There were some burly security guards there who are presumably there in case it all kicks off. In fact I’d imagine they are probably hoping it does kick off, so at least they’ve got something to do.

I had a telephone interview which seemed to go OK, but I haven’t heard anything back from them yet.

Postcode geocoding in ASP.NET with live update

I put up an example of postcode geocoding using the Google Local Search AJAX API and somebody asked if it was possible to populate an ASP.NET GridView with the data in real-time. So since I currently have some spare time, I thought I’d give it a go. First a disclaimer, I have no idea if this breaks the terms and conditions for Local Search usage so check before doing it yourself.

First, lets create a table to store the postcode information

CREATE TABLE Postcodes(
    Postcode nchar(10) NOT NULL,
    Latitude float NOT NULL,
    Longitude float NOT NULL,
 CONSTRAINT PK_Postcodes PRIMARY KEY CLUSTERED 
 (
    Postcode ASC
 )
)

I won’t post all the code here, but the basic process goes like this

  • When the user presses the ‘Get lat/long’ button, execute the Local Search query
  • When/if that returns the latitude and longitude for the postcode, send the results off to a generic handler using a XMLHttpRequest object that puts the data into the Postcodes table
  • When that call returns, update the GridView, which sits in an UpdatePanel so only the grid gets updated.

Anyway, this is what it looks like (yeh I know, not too pretty) and you can download the source here.

Postcode geocoding in ASP.NET

Tuesday, June 23, 2009

Day 11 – where I wonder how List<T> is implemented

Just over 5 years ago I had an interview at APT and after a mammoth interview (the longest I’ve ever experienced) the interviewer told me he thought I’d done well but he was a little disappointed that I didn’t know how the TList class in Delphi was implemented (an internal array as it happens, as opposed to a linked list). Fast forward to now and I was having a telephone interview today where the discussion turned to data structures and the difference between an array and a linked list. We discussed how a list like this could be improved to provide faster random access. I probably didn’t do too well on this, since the internals of collection classes are generally not something I’ve needed to worry about too much but I probably should think about them some more since they do seem to turn up in interviews a lot.

But after all this talk of linked lists, how is the List<T> class (or the ArrayList class for that matter) actually implemented? Well, after firing up Reflector I discovered that, just like in Delphi, these classes actually use an array internally. I can only presume the overhead of having to resize the array when more items are added is outweighed by the memory fragmentation and slow random access of a linked list. Of course if you really want a linked list, there is a LinkedList<T> class available.

Monday, June 22, 2009

Looking for work - Day 10

When I started this series of blog posts I was thinking it might be interesting because we are meant to be in the middle of a big recession. But as far as I can tell there seem to be plenty of jobs out there for people like myself. It could just be down to my impressive CV (ahem) or my tactic of throwing enough crap at the wall that some of it is bound to stick or it could just be there are still a good number of jobs available. I’ve had another two telephone interviews confirmed today so I’ve now got three telephone interviews and a face-to-face interview lined up. I’m guessing that employers will probably interview the same number of people as in the past, even if there are more CVs available to look at, they will still not want to spending all day interviewing people, so I’m guessing my chances of actually being offered a job from one these interviews are the same as they were in the past. This is just conjecture on my part of course, but it certainly makes me feel better.

Talking of my blanket bombing technique, here are the sites I have submitted my CV to. It seems that different recruiters use different websites to find candidates, so it is probably worthwhile submitting a CV to as many as possible

jobserve.co.uk

totaljobs.com

jobsite.co.uk

monster.co.uk

planetrecruit.com, jobsearch.co.uk, gisajob.com (I think these three share the same back end)

cwjobs.co.uk

technojobs.co.uk

jobs.ac.uk

Friday, June 19, 2009

Day 7 – where nothing happens

A few phone calls from agents, but nothing of note.

Running a client-side script when a form segment loads

Form segments don’t provide a way to run a client script when the form segment is loaded. This can be somewhat limiting but it can be solved quite easily. Add a label to the form segment and set the label’s caption to

<script type="text/javascript">window.attachEvent("onload", Setup);</script> 

Then add a script to your form segment to do what ever you want

function Setup()
{
    alert("hello");
}

The problem with flags in Metastorm BPM

Flags are a great way to pass data between processes or to pass data from an external application to Metastorm BPM. There are several ways to raise flags, via the eRaiseFlag executable, using the Raise Flag ActiveX control, via the engine’s XML interface or through the engine’s COM interface. FreeFlow provides a wrapper around the last two approaches. Usage is pretty simple

      Connection conn = new Connection();
      // use to switch between TP and COM
      conn.RaiseFlagBy = RaiseFlagBy.TransactionProtocol;
      conn.RaiseFlag("New Data", new string[] {"some data", "1"});

As an aside, eagle eyed C# coders may be wondering why the last parameter of RaiseFlag doesn’t use the params keyword to simplify usage even further. The problem is there are several overloads of this method (taking user name, password, folder ID etc) so adding params would confuse the compiler since there would be multiple matches for a call to RaiseFlag. One solution would be to give the method a different name but this would make the API less discoverable since different versions of the same method would have different names. Another solution would be to just have one version of the method, with all the required parameters, but that wouldn’t really make life any simpler since the simple usage above would require passing in all parameters. API design isn’t an exact science and sometimes compromises are required.

But back to the main point of this post. Say we are going to create a new folder in Metastorm BPM using the flag data passed to populate the custom variables. Typically this might happen when somebody fills in an online ASP.NET form and we want to kick off some kind of process in Metastorm based on that data. So we’ll have a flagged creation action in the process and add some code to read the data, like so.

%tText:=%Session.FlagData[1]
%iNumber:=%Session.FlagData[2]

Which works fine. OK, say we change the data passed in to

conn.RaiseFlag("New Data", new string[] {"some\tdata", "1"});

Now when we run the code we don’t get a folder created. Instead we get an error in the Designer Log saying “'%inumber' failed while evaluating expression '%iNumber:=data' Error setting value for custom folder field 'inumber'”. This is because flag data passed to the engine is tab-delimited, so if your actual data contains tabs, everything gets screwed up.

We have two problems to solve here. First, how do we handle data with tabs in it, since we probably can’t stop tabs being entered by the user of the ASP.NET form. Secondly, how do we deal with any kind of failure to parse the data passed. This is more of a problem, since currently if we fail to parse the data, we lose it all since the folder never gets created.

robust flagsSo tackling the second problem first, we want to do as little work as possible in the flagged creation action. We will just assign the flag data to a temporary memo variable, since the flag data will not be available outside the flagged action.

%xmData:=%Session.FlagData 

You may find this won’t work for you in earlier versions, at some point only each individual item of flag data could be accessed but it looks to have been fixed in version 7.6. If it doesn’t work, you’ll need to manually combine each piece of flag data.

Next in the Parse conditional action (with no condition), we attempt to assign the flag data to the variables, like so

%tText:=%xmData[1]
%iNumber:=%xmData[2]

This will still fail and will stay at the ‘Got Data’ stage, but at least we haven’t lost the data. The Edit action can then be used to manually fix up the data and get the folder on its way.

So back to the first problem, handling tabs in flag data. Really the only solution to this is to use a different delimiter when raising the flag. None of them are perfect, since potentially any of them could be in the data, but %CHR(160) has worked well for us in the past. Another solution might be to pass your data in some other format such as XML. That will be more complicated but more robust. 

Thursday, June 18, 2009

Day 6 – where I take a test

I spent the morning taking a programming test, sent it off to the agent (the second in a chain of agents I’ve had to pass through to get my CV to the actual employer) and he told me they’d probably get back to me in a week. It seems like no-one is in too much of a hurry to actually employ people. My one interview is over a week away, although I’ve now got a telephone interview lined up for next week which will be followed by a real interview the next day if the phone interview goes well.

I did like the agent’s story of how one person who took this test (for a C# position) actually implemented his solution in Perl… It was quicker apparently. I’m sure he probably solved the problem in 3 incomprehensible lines of code, but he didn’t get the job. 

Wednesday, June 17, 2009

Looking for work – day 5

Perhaps the job market isn’t as bad as people are saying, I’ve managed to get myself an interview. Admittedly not the best paying job in the world, but it’s enough to cover the bills. And I’ve still got a few other potentials looking positive.

And going off at a tangent, I’ve got an idea for an application somebody could write. I’d do it myself, but I don’t have time right now. Logging into all these job websites to update my CV is a right pain. Keeping track of all the jobs I’ve applied for and all the conversations I’ve had with agents is also hard work. So an application that helps manage all these things would be most useful. It should be able to store all your login details for job websites, then I should be able to do a search across all the sites and also update my CV on all the sites in one action. It should also be able to keep track of job applications, link them to emails in Outlook and so on.

Tuesday, June 16, 2009

Looking for work – day 4

It occurs to me that having Metastorm BPM plastered all over my CV isn’t really helping in my job search. If a Metastorm job does come up then I’d hope I’d be in a good position to get it, but the fact is there aren’t any Metastorm jobs around at the moment. And most agencies (and potential employers for that matter) don’t have a clue what it is so they probably drop my CV in the bin, since they’ve almost certainly got a big pile of CVs that match their requirements more closely. To be fair, I’d do the same thing myself. So more CV tweaking is required I think. It seems like uploading a new CV to the job websites might trigger an email to agents anyway, so it may make sense to keep updating it, to keep myself at the front of the queue.

Agency quote of the day - “Winsocks are also important as well”

Monday, June 15, 2009

Looking for work – day 3

Things I learned today

  • There are still some jobs available for hackers like myself
  • Some of them are potentially quite interesting. In fact one actually seemed pretty exciting, not necessarily for the work itself but for the company who do something I think could change the face of one of our industries, in a good way.
  • Apparently having an A in Maths A-Level is considered a good thing, even though I took the exam 19 years ago and have forgotten everything about it. Take heed young uns!

Some stats

  • Number of agents I’ve talked to – lots
  • Number of jobs I’ve put myself forward for – several
  • Number of interviews lined up – none…

Sunday, June 14, 2009

Looking for work – day 2

I dunnow, but this may be interesting to people, so I’ve decided to blog about my attempts to find a new job. Given the current economic situation, I suspect it may take a while and as my life may start to mirror ‘Fun with Dick and Jane’, it could get entertaining (a film I ironically saw just a couple of months ago at the ‘Baltimore or bust’ conference…)

It will be interesting for me, for at least one reason. I’ve never had to look for a job during a recession. I was having a wild time at university during the last recession, it all passed me by in a drunken haze.

So what have my first moves been? I’ve uploaded my latest CV to Jobserve, Jobsite and TotalJobs and applied for a few jobs. I’ve also asked a few people on LinkedIn to recommend me. Being a Sunday, I obviously haven’t heard back from any of the agencies, but hopefully tomorrow things will really start to kick off for real.

Debugging authentication scripts in Metastorm BPM

I talked previously about how to simplify editing authentication scripts in Metastorm BPM by using the FreeFlow Administrator. What I didn’t talk about was how to debug what is actually going on in your authentication scripts. There isn’t a way to debug these scripts from within Visual Studio, or at least not that I’m aware of, so the only way to debug them is to write some kind of trace statements. This can help clarify which scripts are being executed and what path is being taken through the scripts.

I guess you could just write to a text file but the way I’ve done this in the past is to use the createLogEntry function. This function is provided in most of the authentication scripts supplied by Metastorm and usage of it is as follows.

    var err = new Object; 
    err.eDetectedByMethod = "eLogin()";
    err.eDetails = "logging in via web";
    createLogEntry( err );

This will write an entry to the eLog table. Typically I’ll add something similar to the above code as the first lines in each eLogin method in each authentication script, to see which script is actually being called, then add it where necessary to see why scripts are failing.

Saturday, June 13, 2009

Looking for work

I am in the market for a job. Ideally I’m after a .NET development role in London, but I’ve also got a lot of Metastorm BPM experience so anything in that area would suit me. You can find my CV here.

Thursday, June 11, 2009

Paste from Visual Studio naughtiness

There’s a nice add-in for Windows Live Writer called Paste from Visual Studio that lets you copy and paste syntax highlighted code from Visual Studio into Live Writer. I’ve used it for a while and had no problems with it. But the other day I looked at the source of the generated HTML and was a little disappointed to find a link to the author’s website with no link text, meaning it never shows up on the page.

I guess it’s OK wanting to get some links to his site, but to do it in such a way as to be invisible to the end user is a little underhand. I’m not sure what a search engine would make of a link like that. Very possibly it will be considered as some kind of black hat SEO and my pages will be downgraded as a result.

So I guess I’ll have to re-implement the plug-in myself. The guy does kindly provide the source code on his website so it shouldn’t be too difficult to get my own version built. Or I guess I can go back to using the excellent C# code format site, which does the right thing by just adding a comment to the generated HTML that includes the URL for the site.

Wednesday, June 10, 2009

Using a different SAP to login to Metastorm BPM via FreeFlow

Something that comes up quite frequently is how to use FreeFlow to login to Metastorm BPM using something other than the default authentication mechanism. This typically happens when SSO has been installed and although it’s possible to use FreeFlow with SSO it can be a hassle to set up, so the user wants to use the standard eUser authentication. This is pretty easy, by using the SAP property, as below. Note, the SAP property is zero based, so if the eUser script is second in your list of authentication scripts, its value needs to be 1.

  class Program
  {
    static void Main(string[] args)
    {
      Connection conn = new Connection();
      conn.HttpServer = "NEWDOOGAL";
      conn.Engine = "NEWDOOGAL";
      conn.ConnectionType = ConnectionType.HTTP;
      conn.SAP = 1;
      conn.LogOn("Doogal", "");
      Console.WriteLine("Session : " + conn.SessionId);
      Console.ReadLine();
    }
  }

There have been some reports of this not working in e-Work version 6. I suspect this is a bug on the Metastorm end, but since version 6 isn’t supported any more, I haven’t investigated too much. I suggest upgrading, it’s not too painful! And if you need help upgrading, you know who to call.

Thursday, June 04, 2009

IIS Search Engine Optimization Toolkit

I love Google Webmaster Tools since it tells me things I’m doing wrong on the websites I maintain (in fact I love any tools that tell me what I’m doing wrong, FxCop, the HTML editor in Visual Studio, compilers… I’m sure a psychiatrist would draw some scary conclusions from this admission). But the problem with the Google webmaster Tools is that they aren’t very responsive. If I fix an issue, I have to wait for the Google bot to crawl that page again before I know it’s been fixed.

So I was pleased to be directed to the Search Engine Optimization Toolkit by ScottGu’s post. It’s only in beta but it’s already a powerful tool. It will tell you about lots of potential problems on your site, such as missing alt tags on images, missing description meta tags, broken links, no h1 tag etc… I’ve spent the day trying it out with the Process Mapping site and have cleaned up a lot of potential problems, that no other tools had told me about. Only time will tell if this improves our ranking a lot, but I’m guessing even in the worst case, it won’t cause our ranking to drop, since all the suggestions seem perfectly sensible.

It’s simple to install, requires nothing special on the website to get it working and provides instant feedback, so I’m sold on it. The only possible downside is that it will only install on IIS 7 I guess (although that doesn’t mean your website needs to be running IIS 7, just the machine running the toolkit). 

Monday, June 01, 2009

The search for some page rank

I played around with bing today to see if it’s any good. I was pleased to see that a search for Metastorm brought up our forum and the FreeFlow web page on the first page, so clearly bing is a complete success. Or perhaps not, the image search brought up a photo of their former CEO, who hasn’t been there for many years.

But it did make me go off and check the same search against some other search engines (if you try this yourself, you may well get different results, I’d be interested to know if they are wildly different). Yahoo brings up the Process Mapping website on the first page, Cuil has Jerome’s book on the first page and puts the Process Mapping website on the second page and Ask has the forum on the second page (although for some reason they think I’m Dutch).

Finally I checked the other search engine that you may have heard of. OK, it’s the only search engine that matters. And we are way down the search rankings (page 5 for the forums, page 4 for FreeFlow). But the weird thing is if I search on Google’s UK site, FreeFlow and the forums are on the first two pages. That may make some sense for the FreeFlow site since it’s hosted in the UK, but the forums are hosted in Australia. Next I changed my search term to ‘Meta storm’ and was asked ‘Did you mean: Metastorm’, and although all the results were related to Metastorm, both the FreeFlow website and the forums now appeared on the first two pages. We also rank highly on searches for ‘Metastorm development’ and ‘Metastorm consultancy’ so it seems odd that we are so low down on that one search.

So is it a cock-up, conspiracy, some weirdness in the ranking algorithm or have we been marked down for doing something bad? I don’t think we’ve employed any bad practices in our attempts to improve our ranking and if we had, I would imagine that would impact all our rankings, not just on one search, so I think we can eliminate the last option. But I have no idea about the other three.

Saturday, May 30, 2009

Intercepting a HTTP call to modify its behaviour

Say you have some application that uses a HTTP call to get hold of some XML data. It could be a web service but might also be an ASHX generic handler, or any kind of server-side HTTP handling code. Say you want to change the behaviour of that code in some way but you don’t have access to the source code but can change the URL that the calling application uses. What to do?

This was my problem to solve. Actually I didn’t realise it was a problem until I thought of the solution, then thought it would be an exceedingly useful thing to do, and may well be useful in other scenarios. The basic idea is to create a generic handler that will replace the original handler and in the simplest case just pass the request onto the original handler and return the response from the original handler. The configuration of the calling app needs to be updated to use the new handler.

There may be other solutions, such as adding a HTTP module to the original handler, if it happens to be an ASP.NET application. This solution may impact performance since two HTTP calls will be made rather than just one, but I still quite like it.

Here’s the code for the ProcessRequest method, that obviously needs updating to make any changes to the response returned by the original handler. It also shows how to deal with POST data, if the call you’re intercepting is a POST call.

    public void ProcessRequest (HttpContext context) 
    {
      context.Response.ContentType = "text/xml";
      
      // create outgoing HTTP request
      HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
        ConfigurationManager.AppSettings["messageHandlerUrl"]);
      req.Method = "POST";
      req.KeepAlive = false;
      req.ContentType = context.Request.ContentType;

      // get POST data from incoming request
      string parameters;
      using (StreamReader postReader = new StreamReader(context.Request.InputStream))
      {
        parameters = postReader.ReadToEnd();
      }

      // add POST data to outgoing request
      using (Stream stream = req.GetRequestStream())
      using (StreamWriter streamWriter = new StreamWriter(stream))
      {
        streamWriter.Write(parameters);
        streamWriter.Close();
      }
      
      // get response
      using (WebResponse resp = req.GetResponse())
      using (Stream respStream = resp.GetResponseStream())
      using (StreamReader reader = new StreamReader(respStream))
      {
        string response = reader.ReadToEnd();
        context.Response.Write(response);
      }
    }

Thursday, May 28, 2009

Saving a control’s image to a file

Saving a WinForms control’s image to a file, should be pretty straightforward. After all, there is a DrawToBitmap method that should do the trick, right? Well, not quite. Unfortunately DrawToBitmap draws the controls in reverse order, i.e. the top controls are drawn first. So first you need to reverse the z-order of the controls on the form and then reverse them again then after generating the image, something like the following

        InvertZOrderOfControls(formControl);
        using (Bitmap bitmap = new Bitmap(formControl.Width, formControl.Height))
        {
          formControl.DrawToBitmap(bitmap, new Rectangle(0, 0, formControl.Width, formControl.Height));
          bitmap.Save(outputFile, ImageFormat.Jpeg);
        }
        InvertZOrderOfControls(formControl);

Ah, but what about the implementation of InvertZOrderOfControls? You can find that here.

Even after that frigging around, the saved image may not be perfect. The output produced is dependant on the underlying operating system. Vista and XP produce reasonably accurate output, but Server 2003 doesn’t look too good.

Tuesday, May 26, 2009

writing-mode: tb-rl broken in IE8

Vertical text in a web page can be pretty damn handy. I’ve used it in our documenter for Metastorm BPM procedures where we have some grids with columns which would become too wide if the column headers used horizontal text. OK, it only ever worked in Internet Explorer, but given the add-on is for an application that only runs in IE on Windows, I wasn’t too concerned by that.

Now, along comes IE8 and vertical text no longer works properly. Grids are incorrectly sized when they contain vertical text. Try this in a HTML page.

  what the...
  <table border="2px">
    <tr>
      <td style="writing-mode:tb-rl">Some text</td>
      <td style="writing-mode:tb-rl">Some more text</td>
    </tr>
  </table>
  .. is going on in that grid?

And this is what that will look like (obviously view in IE8 to see the problem)

what the...
Some text Some more text

.. is going on in that grid?

Which is disappointing… Ah but you’re not here to see the problem are you? You want to know how to solve it I guess. The only solution I’ve found is to add the following to your head element, which forces IE8 to render the page in IE7 emulation, which means you miss out on all the new rendering goodness in IE8.

<meta http-equiv="X-UA-Compatible" content="IE=7" />

Update – You’ll probably need to manually force IE to switch to IE8 standards mode to see the problem, seems that MS has decided BlogSpot sites are not ready for IE8, so this is running in compatibility mode.

Sunday, May 24, 2009

To ID or not to ID

Somebody commented on my post about my implementation of a US states table, saying there was a problem with it because there was no ID field. I can understand the comment but don’t necessarily agree with it. The table has a primary key, the state code. In this instance, this seems a reasonable approach, the state code isn’t likely to ever change and non-numeric primary keys can still be used as foreign keys in other tables. In fact using the state code has its advantages since in some queries, joining to the state table won’t be necessary if only the state code is required in the result set, so SQL is simplified and is probably quicker.

But ID fields are almost expected by many people, but is this purely down to habit, or are there good reasons for having an ID field in tables? In many cases, there is no other obvious column (or number of columns) to use as a primary key in the table. Then it clearly makes sense to add an ID column. But are there other reasons to use an ID column? Does it offer better performance? I don’t actually know the answer to this question, but I’m interested to know.

I do have an example where a seemingly sensible unique column was used as a primary key, but this caused problems down the track and an ID column would have been a better solution. It was a user table that used the user name as the primary key. This seems like a reasonable idea, since it has to be unique, but the problem came when users wanted to change their user name in the system. This can be due to any number of reasons, but the main one is getting married. Since the user name was used as a foreign key on lots of other tables, things got somewhat complicated when trying to update them all. Of course with hindsight a user ID column would have been a better solution in this case.

So perhaps that’s the reason people add ID columns by default, since it’s perhaps impossible to guess beforehand whether the natural primary key will need to be updated. That said, it seems pretty unlikely that state codes will change, so I’m happy to keep the state code as my primary key.

Wednesday, May 20, 2009

Google Friend Connect and the back button

If you wait long enough, generally whatever feature you’ve wanted to add to your website gets implemented by somebody in such a way that you can reuse it yourself, so I was pleased to see Google Friend Connect had the ability to let users add comments to web pages, as this is something I’d wanted to add to the Random Pub Finder for a long time, and hadn’t got round to doing, mainly due to laziness. Admittedly Friend Connect may not be the perfect solution, particularly if you want to keep in control of your data, but it looks good as a quick and dirty hack.

So I added it and almost immediately I noticed a bit of a major problem. Hitting the back button didn’t take me back to the previous page, I needed to press it again. In fact, it turns out that for every Friend Connect gadget that you add to a page, another entry gets added to the history list, making backwards navigation even more difficult. It looks like it’s an IE only issue, but given that most web users are still using IE, I think it’s pretty big problem (unless normal users don’t use the back button?). After doing some searching on the internet, I discovered this problem has been around for quite a while but still hasn’t been fixed. Which is a shame, because Friend Connect looks cool. But it looks like I’ll have to remove it until this issue is addressed.

Tuesday, May 19, 2009

Editing authentication scripts in Metastorm BPM

 Editing Metastorm authentication scripts If you’ve ever had problems with SSO in Metastorm BPM, or been required to implement an unusual authentication mechanism, you’ve probably had to go through the jig of removing, editing and re-adding the script through the System Administrator application. Having found this approach less than optimal, I added support for authentication scripts to the FreeFlow Administrator. This works somewhat differently since it allows you to edit scripts directly. This makes the edit, test and debug process much quicker. Edit the script, apply the changes, test the login (the engine picks up the new script without needing a restart) and then debug and start again if necessary. It’s probably not something you’ll use every day but when you do need it, it makes life much easier.

Thursday, May 14, 2009

Is BPM counter cyclical?

There’s a theory going around that has probably been mentioned enough times now to become a mainstream view. The theory is this – BPM is counter cyclical. And the reasons put forth for it are this. During a recession, companies are looking to cut costs and one way to achieve this is by purchasing and implementing BPM software. BPM promises to improve process efficiency thus reducing costs.

It’s a persuasive argument, if a little simplistic, but is it true? To tell you the truth I have no idea. Rashid Khan argues it’s not true and the only arguments I’ve seen that BPM is counter cyclical seem to have come directly or indirectly from BPM vendors. Certainly something that promises to offer a significant reduction in costs is going to be attractive but BPM software is generally an expensive purchase (although there are free options starting to appear) and then there are costs involved in actually getting it up and running. No single company’s processes are exactly the same as another so some analysis and development will be required. And processes change, so there are on-going costs incurred. Finally, it’s difficult to quantify how much money will be saved by implementing BPM. I’ve seen some impressive figures for savings made, but will those figures be replicated in your organisation? There are so many variables involved, it’s probably impossible to know. Again, that may make it difficult to justify the up front cost.    

Next, some anecdotal evidence. The Lombardi conference was cancelled and became a virtual event, but the Metastorm conference was well attended, so it’s difficult to draw any conclusion from that. Metastorm claim their revenues have increased by 16% over the past year which isn’t explosive growth, but any kind of increase should be considered a good thing at the moment. The company I work for has certainly not been unaffected by the downturn but we are still managing to keep our head above water.

That all said, BPM isn’t going away. And if it isn’t counter cyclical, that won’t necessarily be a bad thing. Perhaps, finally, we might see some consolidation in the area. Some of the weaker companies may disappear or merge and the stronger companies can gain more market share and thus have more money when the recovery comes (whenever that maybe) to produce even better software.

Of course, if you are thinking about or are actively implementing BPM in your organisation, Process Mapping is here to help.

Tuesday, May 12, 2009

Lastminute trademarks the colour magenta

When browsing the Lastminute website, I spotted this notice at the bottom.

"lastminute.com", "lastminute" and the colour magenta are all trade marks owned by Last Minute Network Limited and/or its group companies

Bizarrely it turns out that trademarking colours is possible. But we’re OK, we can all continue to use the colour so long as we aren’t in the same business as Lastminute, which is, er, selling pretty much anything. Oh.

Monday, May 11, 2009

A generic error occurred in GDI+ when saving an image

This has to be one of the most useless error message I’ve ever encountered in the .NET framework. It seems that pretty much any problem that occurs when using Image.Save will produce this error message. Looking at the code in Reflector, the error is returned from the GDI+ function GdipSaveImageToFile. For me the solution was pretty simple, the folder I was trying to save my image to didn’t actually exist but it took some head scratching before I realised.

Saturday, May 09, 2009

New look JobStats site

Not sure when it happened, but JobStats has got a new look. This is the best site to find out about the health of the IT job market in the UK. From looking at the site, it’s clear they now have some issues with their data (or calculations) since some of the average rates are clearly ridiculous. And if they’re not, I really should be getting into doing SAP consultancy. That said, the graph showing the number of advertised jobs is truly terrifying, since there are now fewer jobs advertised than at any time in the last ten years. If you consider that probably many more of the total jobs available are now advertised online than ten years ago makes that graph even more depressing viewing. Lets hope I don’t need to to look for a new job any time soon… (and that is really tempting fate)

Sunday, May 03, 2009

Adding trace to ASHX files

This is almost certainly caused by me being an idiot, but I know there are other people out there who are idiots on occasion so I thought I’d post this. I’ve never used Trace.Write in ASP.NET before and the first place I needed to use it was in a generic handler ASHX page. I didn’t look too closely at what was required and assumed that I needed to use Trace.Write in System.Diagnostics. That was my first mistake. This doesn’t add any trace output to the trace.axd page (though it would be cool if it did since I could then add trace to my assemblies that would show up, perhaps adding a custom trace listener could fix this?).

So then I looked at the code for System.Web.UI.Page and realised it has a Trace property but a generic handler doesn’t have this property so I thought that I might be out of luck. Eventually I realised that the HttpContext passed into the ProcessRequest method has a Trace property so I could just use that for all my tracing needs. Problem solved.

Update: I’ve now realised it’s pretty easy to include trace output from System.Diagnostics.Trace calls in ASP.NET apps. Just add the following to web.config

<configuration>
  <system.diagnostics>
    <trace autoflush="false" indentsize="4">
      <listeners>
        <add name="WebPageTraceListener"
            type="System.Web.WebPageTraceListener, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
      </listeners>
    </trace>
  </system.diagnostics>

Baltimore skyline

Baltimore skyline panorama

There’s an advert for Microsoft on currently that shows a kid taking some photos and with a few clicks stitching them together into a panorama. All thanks to the ease of use of Vista. So when I was in Baltimore this week I took some photographs to attempt to do the same thing. After all, if some kid can do it, I’m sure I should be able to. But when I fired up Windows Photo Gallery I was unable to find any options to make a panorama. So I fired up Windows Live Photo Gallery instead and that did have the option. In fact it was pretty damn simple to create a panorama, as you can see above.

But two things bother me. Why are Microsoft advertising Windows by showing the capabilities of an application that doesn’t ship with the operating system? And why are there two different photo gallery applications which seemingly do the same thing but are subtly different?

Saturday, May 02, 2009

A simple WinForms numeric edit control

There are plenty of controls out there that will allow only numeric entry but they may not meet your needs. If you don’t want the up/down buttons, the built-in NumericUpDown control won’t be of use and using one of those huge libraries just for their numeric control may be overkill. If that describes your position, this uber-simple control may fit the bill.

  public class NumberControl : TextBox
  {
    /// <summary>
    /// Creates a new <see cref="NumberControl"/> instance.
    /// </summary>
    public NumberControl()
    {
      TextAlign = HorizontalAlignment.Right;
    }

    /// <summary>
    /// Triggered when a key is pressed. Swallows all keys except for digits.
    /// </summary>
    protected override void OnKeyPress(KeyPressEventArgs e)
    {
      base.OnKeyPress(e);
      string decimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
      if (e.KeyChar.ToString() == decimalSeparator)
      {
        if (Text.IndexOf(decimalSeparator) > -1)
          e.Handled = true;
      }
      else if (!char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar))
      {
        e.Handled = true;
      }
    }
  }

Wednesday, April 22, 2009

Adding Street View to web pages

Street View has been available for a while but it didn’t interest me too much until recently when it launched in the UK. Then I noticed it was available as a part of the Google Maps API so I decided I’d add it to the Random Pub Finder. I initially thought it would work like the user interface available on the Google Maps website, but it doesn’t seem such an integrated solution is possible. To be fair, dragging the little man around your map probably isn’t what you’ll want to do generally so this isn’t a big issue, and if you’re clever you could probably implement this kind of solution anyway. I was pleased to find it was very easy to add to the site. First add a div to your page that will contain the Flash control

  <div name="pano" id="pano" style="width: 750px; height: 300px">
    <div id="panoError" style="text-align: center; margin-top: 100px;color: #aaa;">Loading...</div>
  </div>

Then add  a reference to the Google Maps API script.

<script src="http://maps.google.com/maps?file=api&amp;v=2.x&amp;key=ABQIAAAAjtZCgAx5i04BiZDO6HlxhRSDF8NMhBf90dVWYNzYEfop4QQs3RSkYVE7vnmvtIBRRQjoFXq4kz15Mg" type="text/javascript"></script>

Finally add some JavaScript to initialise the control.

    function load()
    {
      var latLong = new GLatLng(51.412640159832, -0.30039334012124);
      var panoramaOptions = { latlng:latLong };
      var myPano = new GStreetviewPanorama(document.getElementById("pano"), panoramaOptions);
      GEvent.addListener(myPano, "error", handlePanoError);
    }

    function handlePanoError(errorCode)
    {
      var error = "An error occurred";
      if (errorCode == 600)
      {
        error = "No street view available";
      }
      else if (errorCode == 603)
      {
        error = "The Flash plugin is not available";
      }

      document.getElementById("panoError").innerText = error;
    }

Actually a lot of this JavaScript isn’t even needed, you can get away with just the first three lines of the load function, but the rest is useful for error handling. Error 600 can happen quite frequently so is best handled in some manner. I’ve not seen error 603 myself but I’ve shown it here because some of the code examples I’ve seen use the constant FLASH_UNAVAILABLE, which doesn’t seem to be defined anywhere, meaning when any error occurs you’ll actually get a script error if you use this constant. I guess there are more errors that can occur but I’ve not come across them yet.

Friday, April 10, 2009

Renaming a user in Metastorm BPM

Rename Metastorm user

People change their names for all sorts of reasons, marriage being the most common, but there are plenty of other reasons, such as a change of religious belief or wanting to get rid of an unfortunate surname. Whatever the reason, the Users and Roles utility that ships with Metastorm BPM doesn’t have any support for changing user names. The FreeFlow Administrator can help out here. Just fire it up, change the user’s name and apply the changes. This will change the user name in all relevant Metastorm tables. However it can’t change every reference to that user. For instance if you’ve stored a user name in a custom variable, that won’t get updated, since there isn’t really any way of knowing that the custom variable value refers to a user.

This can also be achieved programmatically. This may be useful if your corporate standards have changed and you need to change every user’s name in the system. Or you may want to prefix the users’ names with the domain name because you’re moving to SSO. Whatever the reason, here’s some sample code to achieve this.

using System;

using FreeFlow.Administration;

namespace ChangeAllUsers
{
  class Program
  {
    static void Main(string[] args)
    {
      Server server = new Server();
      server.Connect("sa", "a", "Metastorm");
      foreach (User user in server.Users)
      {
        Console.WriteLine("Processing " + user.Name);
        user.Name = "domain\\" + user.Name;
        user.ApplyChanges();
      }
    }
  }
}

Monday, April 06, 2009

Documenting Metastorm procedures

You may be aware of the procedure documenter produced by Process Mapping, the company I work for. If not, have a look. It’s a Designer add-in, which means a menu item is added to the Designer’s Tools menu that kicks off the documenter and generates the HTML documentation. I spent a little time today extracting the HTML generation code from the add-in assembly into a separate assembly. This means that the code can be called from anywhere you like, which leads to the possibility of automating your documentation generation. Combine this with FreeFlow and you can automate the documentation of all your published procedures. The following console application demonstrates how this could be achieved.

using System;
using FreeFlow.Administration;
using ProcessMapping.ProcedureDocumentationGenerator;

namespace DocumentProcedures
{
  class Program
  {
    static void Main(string[] args)
    {
      Server server = new Server();
      server.Connect("sa", "a", "Metastorm");
      foreach (Procedure proc in server.Procedures)
      {
        Console.WriteLine("Processing " + proc.Name);

        string filename = "c:\\temp\\" + proc.Name + ".xep";
        proc.Versions.LatestVersion.SaveToFile(filename);

        string htmlFilename = "c:\\temp\\html\\" + proc.Name + ".html";
        DocumentationGenerator generator = new DocumentationGenerator();
        generator.IncludeMapImages = true;
        generator.Generate(filename, htmlFilename);
      }
    }
  }
}

I believe later versions of SQL Server allow the execution of .NET code from within the database, so I would imagine it is possible to add a trigger to the eProcedure table that kicks off this code whenever a new record is added, so documentation will always be up to date.

Of course the procedure documenter isn’t a silver bullet. To generate useful documentation, some work will be required to ensure the notes in you procedures contain useful information.

Friday, April 03, 2009

Debugging server-side scripts in Metastorm BPM

Script Debugging in Metastorm BPM

Debugging server-side scripts in the Metastorm BPM can be difficult. One thing that can help with debugging JScript/VBscript scripts is the FreeFlow Administrator. When an error occurs in your script, generally you’ll be given a line number where the error occurred. This may not relate to a line number in the Designer because scripts are merged together when they get published to the database. The FreeFlow Administrator provides a simple way to view your scripts and see the line numbers and hence track down bugs more easily.

Unfortunately in the world of JScript.NET, when an error occurs you won’t get a line number telling you where it happened. If you want to learn more about debugging .NET code in Metastorm BPM, I would recommend Process Mapping’s .NET course (generally presented by myself).