Wednesday, September 24, 2008

Autosizing row heights in a WinForms DataGrid

Autosizing column widths in a WinForms DataGrid is pretty easy, if a little hacky. Although it seems that a similar technique could be used to autosize row heights, the private RowAutoResize method doesn't do what I was expecting it to do. I found some useful code here which I have adapted somewhat so that it can handle DataGrids with IList based classes as their data source. It also looks at all cells to work out the required height.

    public void AutosizeRows()
    {
      int numRows = 0;
      if (DataSource is DataTable)
      {
        numRows = ((DataTable)DataSource).Rows.Count;
      }
      else if (DataSource is IList)
      {
        CurrencyManager manager = (CurrencyManager)BindingContext[DataSource];
        numRows = manager.List.Count;
      }

      using (Graphics g = Graphics.FromHwnd(Handle))
      {
        StringFormat sf = new StringFormat(StringFormat.GenericTypographic);

        // Since DataGridRows[] is not exposed directly by the DataGrid  
        // we use reflection to hack internally to it..
        MethodInfo mi = GetType().GetMethod("get_DataGridRows",
          BindingFlags.Instance | BindingFlags.NonPublic);

        Array dgra = (Array)mi.Invoke(this, null);

        // Convert this to an ArrayList, little bit easier to deal with 
        // that way, plus we can strip out the newrow row. 
        List<object> DataGridRows = new List<object>();
        foreach (object dgrr in dgra)
        {
          if (dgrr.ToString().EndsWith("DataGridRelationshipRow"))
            DataGridRows.Add(dgrr);
        }

        int colCount = TableStyles[0].GridColumnStyles.Count;

        // Now loop through all the rows in the grid 
        for (int i = 0; i < numRows; ++i)
        {
          int maxHeight = 0;

          for (int j = 0; j < colCount; j++)
          {
            SizeF size = g.MeasureString(this[i, j].ToString(), Font, 400, sf);
            int h = Convert.ToInt32(size.Height);

            // Little extra cellpadding space 
            h = h + 8;

            maxHeight = Math.Max(h, maxHeight);
          }

          // Now we pick that row out of the DataGridRows[] Array  
          // that we have and set it's Height property to what we 
          // think it should be. 
          PropertyInfo pi = DataGridRows[i].GetType().GetProperty("Height");
          pi.SetValue(DataGridRows[i], maxHeight, null);
        }
      }
    }

Sunday, September 21, 2008

Capital boozing - the Random Pub Finder book

It used to be called vanity publishing. An aspiring author, having been rejected by all the standard publishers, would decide the problem wasn't with themselves and their work, but with the publishers who were clearly blind to the author's genius. So the only solution was to go out and get their work published themselves and sell hundreds of thousands of copies that way. Of course what generally happened was that, after paying out thousands to the vanity publisher, the author would be left with 2472 copies of their masterpiece sat in the loft, after giving out the other 28 copies to friends and family.

Things have changed, we can now all be published authors via the wonderful lulu.com. The beauty of this system is that a forest doesn't get destroyed to sate the author's vanity. Books are only produced if somebody is actually interested in them. And because they are published on demand, books can be updated and corrected quickly and easily.

So to take advantage of this system, we've taken the reviews from the Random Pub Finder and put them all together in one convenient book shaped package. I don't know about you but even with all the latest technologies for reading on a screen, I still much prefer a book. No power problems, no waiting to boot up, no random crashes.

Anyway, if you want to buy a copy, hop over to http://www.lulu.com/content/2003276. I'm still waiting for a design for the cover, but other than that it's all done.

Saturday, September 20, 2008

Turn it up to 11

11I've just noticed the volume on the BBC media player on their website goes up to 11. I assume the developer of this code is an afficionado of Spinal Tap.

I can't say I've noticed the difference in sound levels between this and other web based apps...

Tuesday, September 16, 2008

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

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

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

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

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

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

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

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

No toolbox items found in assembly!

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

[ToolboxItem(true)]

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

Part 1 - The basics

Part 2 - The database tables

Part 3 - Using Visual Studio

Part 5 - Long running workflows

Part 6 - State machines

Thursday, September 11, 2008

Complete rewrite - two years on

It's almost two years on since I wrote about one of my previous employers starting a complete rewrite of one of the core pieces of the their application suite. So how have things turned out? To tell you the truth I have no idea, nobody from the company will give me any useful information these days. But one thing that I am sure of, the new software hasn't been released yet. So my back of a fag packet estimate of three and half years development time isn't looking quite as daft as it may have done at the time.

Having said that, it is of course possible that the software is done and the company are just waiting for the right time to release it. I remember being blown away when I discovered that Ultimate Play The Game actually held back the release of the ground-breaking Knight Lore game so they could release the less impressive Sabre Wulf without it looking rubbish.

And if you do know anything about the progress of the software of which I'm talking then post a comment, you can do it anonymously...

Tuesday, September 09, 2008

Noel Gallagher pushed off stage

I have a theory why the North West has produced more than its fair share of great popular music over the past few years (The Beatles, Joy Division, New Order, The Smiths, Happy Mondays, British Sea Power (although Brighton try to claim them as their own), Elbow, The Fall, The Stone Roses to name but a few). It's the weather. Rain, rain and more rain. People are forced to stay in quite a lot and do something. So a lot of them decide to try their hand at music and some of them succeed. But I would never include Oasis on that list. Although their initial material was catchy, it never really reached the emotional heights that great pop music does. And their output afterwards has been absolutely awful. To be fair, "Wonderwall" could have been a beautiful song had it not been for the vocals of Liam.

Anyway, I'd pretty much forgotten about Oasis, but apparently they are still chugging along with their Beatles parodies. And now we have this wonderful video of Noel being pushed off stage by somebody. Although that's slightly humorous, the bit I love is self-styled tough guy Liam. First he tries to get away from the stage invader, then when the guy has been held down by two or three security guys, he suddenly remembers how hard he really is and walks across to "have a go" at him. I think it really shows how much of a show the whole tough guy image is...

Wednesday, September 03, 2008

How to kill the hits to your website

I went on holiday a few weeks ago. Unbeknownst to me the Random Pub Finder's database died the day I went away. With no web access I only found out on my return. A few days of emails to our ISP followed (yes we should have had a backup but we didn't; fortunately our ISP did have) and finally the site was back online. A few days after that I went off to Switzerland and what do you know, the database died again. A more paranoid person than myself might think somebody knew I was going away and did it deliberately. I'm not quite that paranoid but am obviously paranoid enough to consider it as a possibility. After the first data loss I'd done a backup, but I didn't have access to it whilst I was out of my office. Anyway, upon my return, the site was restored pretty quickly. So in all the Random Pub finder was offline for about three weeks.

Prior to the database crash, we were averaging about 300-400 visitors a day, now we are barely reaching 100. I guess the broken pages are still in the search engines' caches and who knows if and when we'll get our visitors back...

Friday, August 29, 2008

The wonders of e-ticketing

If you go to Rail Europe and purchase a ticket, chances are you won't actually be sent real tickets. Instead you'll be sent an itinerary with some reference numbers. Then when you get to the station, you go to a machine and pick up the real tickets. So I was trying to get to Basel In Switzerland and chose the train since I had too much baggage to go on the plane and also felt like going on the train was the right thing, environmentally, to do.

So I got to St Pancras and started typing in my reference numbers. My Eurostar tickets popped out but I was unable to get the tickets for the rest of the journey. I thought perhaps this was because I needed to pick them up from the station in Paris so didn't worry too much.

I arrived in Paris, dragged my stuff to Gare d'Est and tried to type in my reference numbers again and got nowhere. The touch screen computer helpfully had an English option with a button so small that it was almost impossible to hit the screen in the correct place to get it working. Nice touch, SNCF. So I started queuing up in the ticket office but time was running out, so I headed for the train. After all, I had an itinerary that included my seat number, so it was obvious that it was my seat. Unfortunately, the ticket inspector didn't see it that way. I didn't have a ticket. Yes, the train was fully booked and nobody was demanding to sit in my seat but I didn't have a ticket. With the man next to me translating, I tried to argue my case, but got nowhere and ended up paying for my ticket again. Ho hum. The ticket inspector did assure me I could get my money back if I went to the ticket office when I got off.

But at least I managed to get to Basel. So the next thing to do was get my ticket refund and get my ticket for the return leg of my journey. Unfortunately the ticket office in Basel informed me I was now in the wrong country to pick up my tickets for the return journey and would have to go to France. Fortunately Basel is only eight minutes from the border so this wasn't too time consuming or expensive. When I got to St Louis, the lady in the ticket office was very nice and her English wasn't too good (although clearly much better than my French) so after getting my return ticket I thought better of trying to explain why she needed to give me some money due to her jobs worth co-worker.

So lesson learned, trains may be more environmentally friendly but train companies don't seem to be in any rush to actually compete with airlines. They cost more, take longer and make life difficult for their customers. To solve one of these problems, here's a suggestion for you, how about sending the tickets along with the itinerary and then I might be tempted to use the train again in Europe?

Monday, August 18, 2008

Babysitting laptops

PCs are so fecking demanding. I've got a few laptops at home ready for some training I'll be doing and every time I switch one of them on I'm assaulted by a bunch of popups telling me I need to update this or that, Windows Update, Java, Macafee, some other crapware installed with Dell. After half an hour of updates, I can finally get round to doing whatever I need to do, which I've forgotten about by now of course.

So how can this be solved? Here's some suggestions

Windows Update - OK, this is probably a necessary evil, although I'm dubious about some of the updates pushed my way. Are they all really necessary?

Macafee - I haven't run a scan for two weeks because the laptop hasn't been switched on at all, I'm not likely to have acquired a virus in that time am I?

Sun Java - Do you really have to keep bugging me to install the Google Toolbar? And what the feck has that got do with Java anyway?

Dell - Stop installing all this crap on your PCs. Offer an option of installing a clean OS, even if it costs a little more. The problem is that all that extra crap you install hits performance and half of it is badly written trash so people start to think Windows is crap and go off and buy a Mac, even though it's actually Dell that's the problem. The irony is that the thing that made PCs successful, open hardware meaning anybody can build them and install whatever they feel like, is the thing that may well kill them off, replaced by the closed Mac...

The Coming Collapse of the Middle Class

This video may be about the US but I'm guessing it also applies to the UK. We may think we are richer than previous generations, but in fact we are spending more on housing, healthcare and have more debt, even though we generally have two parents in work. The problem with having two parents in work is we have no slack in the system. If one parent falls ill or loses their job, we pretty quickly run out of money, since the other parent can't go out to earn extra, they are already in the workforce.

Sunday, August 03, 2008

Connecting two PCs with a network cable

Wireless networks are great for home networks, unless you need to copy large amounts of data around. OK, 802.11g is probably pretty fast, but I've got 802.11b here and when I tried to copy 4Gb of data around, things got a bit slow. I had no blank DVDs and my USB drive is only 1Gb so I had to figure out another way to get the data from one machine to the other. So I figured I'd network them the old way, with wires. And here, mostly so I remember how to do it next time, is what's required. This may not all be required but I'm a software man, not hardware, and you know how many software engineers it takes to change a light-bulb right? But given that most of the information I've found on t'internet seems to be "use a cross-over cable", it might be helpful to someone.

First, make sure the two computers are part of the same workgroup. Right click on 'My Computer' and view the computer's properties to see what workgroup the PCs are a member of. If they are on a domain, I guess they need to part of the same domain.

Next, the cable needs to be a cross-over cable. I think I, er, borrowed my cross-over cable from a former employer but they are available from all good computer equipment shops.

Next, make sure TCP/IP is configured to use fixed IP addresses. More often than not TCP/IP is set up to obtain an IP address automatically since they'll get assigned by your ISP or router or some little gremlin who sits in a box on the internet somewhere - look I dunnow how it works. Here your TCP/IP connection is just between two computers, so there is no little gremlin to assign IP addresses. So look at the properties of your network connection and from there look at the properties of the TCP/IP stack. I think you can pretty much choose whatever IP address you like, so long as the two PCs have different IP addresses. I chose 100.100.100.101 and 100.100.100.102 and that worked fine.

Finally, disable your wireless connection on one PC at least. This may well make no difference at all, but it seemed like a good thing to do. If your PC can see another computer via two network connections, how does it choose which connection to use when copying files? Again I don't know, so figured disabling the slow wireless connection would be a good idea.

Friday, August 01, 2008

SonicWall VPN client and wireless networks

I've had this problem for a while. I have a couple of computers connected on my home wireless network which generally can see each other to share files. But sometimes I'm unable to connect to the other machine. I'd always put this down to some weirdness going on in my wireless network and dealt with it by copying files onto a USB drive. But I finally figured it out today. It would appear if I have the SonicWall VPN client running on one of the machines this causes the wireless network to go funny. Shutting SonicWall down fixes the issue.  No idea if this is a bug or if it is required behaviour but now I know why it happens I don't really care.

Thursday, July 31, 2008

Cuil not so cuil

It's been done to death already but I can't help agreeing with the pundits who say Cuil will not replace Google as the search engine of choice. I did a search for "Doogal Bell" and most of the returned sites were search listings on other sites that linked to my stuff. OK, I could get to my stuff indirectly this way, but it's hardly what I'm after. Doing a search on Google brought up this site, my home page, the Random Pub Finder and my photos on flickr, which is what I'd expect. It did throw up a few useless directory and search listings sites but further down the list. OK, not very scientific, but it's very rare Google gives me unhelpful results so it's still relevant I think.

The Cuil website says "Rather than rely on superficial popularity metrics, Cuil searches for and ranks pages based on their content and relevance". Isn't this the largely discredited method of searching the web that was used by search engines before Google came along? So how's it going to be better now? It was way too easy to game those search engines by just repeating keywords in the text of your page.

Anyway, it'd be nice if somebody did come up with a decent alternative to Google, it's becoming increasingly obvious that they will soon be a monopoly and we all know what monopolies start getting up to...

Tuesday, July 22, 2008

Radiohead - Ceremony

One of my favourite bands* covering one of my favourite songs, what's not to like?

And if you like it, download it with KeepVid (this is mostly a reminder for myself, I'm sure you already know about this website since you're probably much more web savvy than me)

* If you've read one of my previous posts, you may think I don't like them anymore, but their last album has mostly restored my faith. As has this cover.

Saturday, July 19, 2008

Tracking down memory leaks in managed code

I've been spoilt in the past when I've had memory leaks in my applications. I've worked at places where AQTime has been readily available. But I've recently noticed what appeared to be a memory leak in the FreeFlow Administrator and given that it's a free application I can't justify spending cash on a memory profiler. I had a search on the internet for a free memory profiler and downloaded a trial version of .NET Memory Profiler from Scitech. This was pretty sweet but when the trial ran out I was again unable to justify spending money on buying it.

I then thought there might an API available to capture the objects allocated by the .NET runtime but I was unable to find one. I'm guessing there has to be one, since otherwise how would memory profilers work? Perhaps the API is unmanaged or isn't documented, either way I was unable to find it. But what I did find was useful none the less. Shipping with the .NET runtime is a DLL called SOS.DLL that can be used from Visual Studio to debug memory leaks. I won't go into configuring Visual Studio to use the DLL since this post covers this in detail (the post talks about Visual Studio 2005 but it works just as well in 2008). Instead I'll cover the steps needed to track down a memory leak.

First up, set a breakpoint in your app. When you hit the breakpoint and are in the debugger, these are some useful commands that can be typed into the Immediate window.

.load sos - this loads the SOS.DLL so you can now start to use the SOS commands

!DumpHeap -stat - this will show a list of objects created, grouped by their type and ordered by the total amount of memory used by the objects. You need to analyse this list and look for anything that looks suspicious. I generally ignore the .NET types and concentrate on my own types, since it's most likely that these are causing the problems. Also, it's pretty hard to figure out how many .NET type instances would be too many, there may seem to be lots of string objects around but how many should there be? I was suspicious about a class called FolderControl, so decided to dig deeper into the details for that class.

!DumpHeap -type <type name> - this shows the details of each instance of the specified type (you don't need to specify the fully qualified type name, just the class name will do). The most important detail listed here is the address, since this will be used in the next command.

!GCRoot <address> - this looks for references to an object, which can help track down why an object isn't being garbage collected and hence causing a memory leak. I found the output somewhat confusing but I guess it may be useful.

!help - this lists all the commands available. There are many more than the ones mentioned above.

!help <command name> - get more information about a specific command.

In my case, my dynamically created control was hooking into the Application.Idle event and never unhooking the event handler. Since the Application object remains active for the lifetime of the application, my control was never garbage collected. In my experience, a lot of memory leaks in WinForms applications are caused by event handler issues like this.

After all that, I feel like a proper hardcore geek. Time to learn some assembler...

Wednesday, July 09, 2008

IE7 vs FireFox : The never ending debate

I was reading a forum today when I spotted yet another debate about FireFox and IE7. Much like the Windows vs Mac vs Linux debate, this is getting incredibly tedious. Both browsers (along with Safari, Opera et al) do a perfectly good job of letting people browse the web. Both have their plus points (e.g. FireFox has better plug-ins, IE tends to work with more sites) so just let it go, will you?

Much like the OS debates, the thing that really gets my goat is the smug superiority of FireFox users. Look, using a minority web browser does not make you a hip happening person, it just means you use a different browser than the majority of people. It doesn't make you a better person just because you're not using software written by the "evil empire".

For the record, I use IE and FireFox and I'm not hugely excited by either...

Sunday, July 06, 2008

Zoopla - How much is your house currently worth?

Yet another site for the British obsession with house prices. When I first looked, our house was apparently worth £371,039, now it's worth £375,350, who says house prices are falling? (well me for one...). I'm not sure how valuing it to the nearest pound is possible but hey who am I to argue. Perhaps this is the answer for people having trouble selling their homes, put them on the market for a very exact price, thus showing buyers that you're not messing about and know the value of your house precisely. Well, perhaps.

Any talk of the massive inflation in house prices always makes me think of the quote "House prices are a matter of opinion but debt is real". Even though the value of our house has apparently increased way beyond inflation in the 8 years we have been living here, the debt hasn't reduced dramatically. And due to the way mortgages work, we still have to pay the same amount every month. In fact, come November, we'll probably have to pay a whole lot more. So, why is house price inflation good exactly?

Thursday, July 03, 2008

How to escape text in SQL statements

Two things got me thinking about escaping text in SQL statements recently. I had previously thought it was a simple topic that everybody already knew about but given that these two things occurred I can only assume that not everybody knows about why escaping text in SQL is important and how to do it. First people keep ending up on the FreeFlow website because one of the methods in the class library is called SqlEscape, so obviously people want to know how to do it. The implementation is very very simple.

    /// <summary>
    /// Escapes a string so that single quotes are replaced by two quotes for use in SQL expressions.
    /// </summary>
    /// <param name="sql">The string to escape.</param>
    /// <returns>The escaped string</returns>
    public static string SqlEscape(string sql)
    {
      if (sql == null)
        return null;
      return sql.Replace("'", "''");
    }

Second, I got hold of some free PHP code off the web that failed to do any escaping of the text in SQL expressions. I foolishly deployed this to a website without checking the code beforehand. Fortunately I managed to spot the error before any dodgy hacker managed to take advantage of it. In the PHP world, escaping can be done using the mysql_real_escape_string function.

So that covers the how but why is this important? The first, probably least important, reason is that it means queries that include ' in text strings will work. I guess most people come across this problem when they try to enter a name like "O'Connor" into an application and it fails.

The more important issue, particularly with public facing websites, is SQL injection where a hacker can manage to run pretty much any query they like against your database.

There are other ways to solve this problem. Stored procedures and parameterised queries will also do the trick.

The joy of WITH (NOLOCK)

For a long while I thought it odd that reading from a SQL Server database caused the rows that were being read to be locked. Thinking about it for a little longer makes it clear that it's a necessary evil. You don't want some other process to update records as you're reading them, since the results you get back may well be inconsistent. But the thing is this often doesn't matter that much, so long as it's near enough correct. I've only recently come across WITH (NOLOCK) and I think it's wonderful. It's a quick and dirty way to boost the performance of your queries since it means the query can execute without acquiring a lock on the rows you're trying to get hold of.

Opponents of using it will say there are more fundamental problems with your database that could be fixed with some better indices, but sometimes we don't have the time to do proper performance analysis, so a quick fix is welcome. Purists will no doubt look down their noses at using this technique since it's just not the right thing to do. But I don't care, it improves performance for very little cost. Saying that, I do wonder how inconsistent the data may be. For instance, could I read a row that has a half-written text field? I haven't seen anything like this yet but I do wonder if it's possible.

Friday, June 27, 2008

RIP Elmo

Yesterday our pet chinchilla Elmo bit the dust. He had been ill for several weeks. He'd stopped eating and a visit to the vet and some drugs didn't help the little fella.

At least I wasn't to blame for this death unlike our last chinchilla, who I accidentally stood on as he ran down the stairs. A similar fate befell my brother's cat who ended up under the wheels of his car.

I was worried how our daughter would handle his death, but in fact she was very cool about the situation, mostly concerned about when we'd be getting another pet...

Thursday, June 26, 2008

IE8 not ready for prime time

I downloaded and installed IE8 beta 1 primarily to have a look at the new XDomainRequest object which allows for cross-domain requests. Unfortunately that old problem security gets in the way. Although the documentation implies it is possible to make requests to any server, it appears the server has to explicitly support the new request protocol. Ho hum.

Anyway, IE8 screws up lots of pages (most notably Google Maps) and although it claims to have an IE7 emulation mode, using this mode seems to make no difference at all, as far as I can see. So time to uninstall, at least until the next release...

Tuesday, June 24, 2008

IE6 is dead, long live IE7

IE7 usage I'm not one for making predictions. OK, I've made a few in my time but I'm invariably wrong. Predicting that the majority of IE users would be using version 7 within two months was one of my worst. Here we are, over 18 months since it was released and IE7 visitors to the Random Pub Finder have finally started to outnumber IE6 users. So I guess in another 18 months I can start to forget about supporting IE6. Oh, and worry about IE8 instead, sigh... 

Sunday, June 22, 2008

How to use Experts Exchange

I don't know about you but a lot of my web searches bring up Experts Exchange articles. When you first look at the page, it looks like you have to register to see the responses to the query. In fact it says "All comments and solutions are available to Premium Service Members only". But look a little further down the page (OK a lot further) and you'll see all the responses without paying a thing.

It may seem surprising that the comments are all visible to anybody who hasn't registered, kind of destroying their whole business model. But I guess they have a problem. I'm guessing most of their traffic comes from search engines so they need to have some decent content to get visitors in. To do that, they need to include all the responses. They could hide the text from the browser whilst making it visible to search engines, but this would almost certainly get them removed from the search engine listings, since this would be considered as black hat SEO. So they have to include the responses visible to everybody and just hope they get enough suckers signed up with the huge blocks of text before you get to the real content.

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