Homepage of programmer Torben Warberg Rohde

SimpleText.NET released

I recently relased the language versioning SimpleText.NET system/utility on CodePlex.
Here the short snip from the project site description:

SimpleText aims at enabling .NET developers to language version sites or apps in a simple manner.
A focal point is servicing external translators with MS Excel export/import.
Texts can be harvested from your source code based on a simple custom format.

Read on at the CodePlex site and take it for a spin. Don't hesitate to drop me a line on mail or the CodePlex discussions if you have comments or suggestions for improvents.


English grammar - Factoid

Classic English grammar question: "Do I use an or a here".
What I learned many years ago was that if the word started with a consonant use "a", otherwise "an". Then I read this post and found out I have been wrong.

Spoiler: It depends on the pronunciation not the spelling.

Shortening file paths - c-sharp function

When listing some file paths for display in a grid, I found that I needed to shorten them, since they can quickly become to large to fit in the relatively small space I had allocated for them. Instead of simply cropping of the beginning or end I wanted to remove some of the middel directories, just like all the standard Windows apps does in e.g. the "Recent files" overview.

Let's try to turn this:
into this:

So I went googling, but found no exact matches. This post on points to a trimming function which operates directly on the Win-forms canvas (so you never get hold of the shortened string object) and to a function in the Windows API (which means you have to include a reference to a DLL that is not native .NET).

This is my take on a function for this purpose. It is not as thouroughly tested or performant as an official MS-API-method would be, but I think it should suffice for most purposes:

/// <summary>
/// Shortens a file path to the specified length
/// </summary>
/// <param name="path">The file path to shorten</param>
/// <param name="maxLength">The max length of the output path (including the ellipsis if inserted)</param>
/// <returns>The path with some of the middle directory paths replaced with an ellipsis (or the entire path if it is already shorter than maxLength)</returns>
/// <remarks>
/// Shortens the path by removing some of the "middle directories" in the path and inserting an ellipsis. If the filename and root path (drive letter or UNC server name) in itself exceeds the maxLength, the filename will be cut to fit.
/// UNC-paths and relative paths are also supported.
/// The inserted ellipsis is not a true ellipsis char, but a string of three dots.
/// </remarks>
/// <example>
/// ShortenPath(@"c:\websites\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "c:\websites\myproject\...\App_Data\themegafile.txt"
/// ShortenPath(@"c:\websites\myproject\www_myproj\App_Data\theextremelylongfilename_morelength.txt", 30)
/// Result: "c:\...gfilename_morelength.txt"
/// ShortenPath(@"\\myserver\theshare\myproject\www_myproj\App_Data\theextremelylongfilename_morelength.txt", 30)
/// Result: "\\myserver\...e_morelength.txt"
/// ShortenPath(@"\\myserver\theshare\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "\\myserver\theshare\...\App_Data\themegafile.txt"
/// ShortenPath(@"\\\theshare\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "\\\theshare\...\themegafile.txt"
/// ShortenPath(@"\theshare\myproject\www_myproj\App_Data\", 30)
/// Result: "\theshare\...\App_Data\"
/// ShortenPath(@"\theshare\myproject\www_myproj\App_Data\themegafile.txt", 35)
/// Result: "\theshare\...\themegafile.txt"
/// </example>
public static string ShortenPath(string path, int maxLength)
	string ellipsisChars = "...";
	char dirSeperatorChar = Path.DirectorySeparatorChar;
	string directorySeperator = dirSeperatorChar.ToString();

	//simple guards
	if (path.Length <= maxLength)
		return path;
	int ellipsisLength = ellipsisChars.Length;
	if (maxLength <= ellipsisLength)
		return ellipsisChars;

	//alternate between taking a section from the start (firstPart) or the path and the end (lastPart)
	bool isFirstPartsTurn = true; //drive letter has first priority, so start with that and see what else there is room for

	//vars for accumulating the first and last parts of the final shortened path
	string firstPart = "";
	string lastPart = "";
	//keeping track of how many first/last parts have already been added to the shortened path
	int firstPartsUsed = 0;
	int lastPartsUsed = 0;

	string[] pathParts = path.Split(dirSeperatorChar);
	for (int i = 0; i < pathParts.Length; i++)
		if (isFirstPartsTurn)
			string partToAdd = pathParts[firstPartsUsed] + directorySeperator;
			if ((firstPart.Length + lastPart.Length + partToAdd.Length + ellipsisLength) > maxLength)
			firstPart = firstPart + partToAdd;
			if (partToAdd == directorySeperator)
				//this is most likely the first part of and UNC or relative path 
				//do not switch to lastpart, as these are not "true" directory seperators
				//otherwise "\\myserver\theshare\outproject\www_project\file.txt" becomes "\\...\www_project\file.txt" instead of the intended "\\myserver\...\file.txt")
				isFirstPartsTurn = false;
			int index = pathParts.Length - lastPartsUsed - 1; //-1 because of length vs. zero-based indexing
			string partToAdd = directorySeperator + pathParts[index];
			if ((firstPart.Length + lastPart.Length + partToAdd.Length + ellipsisLength) > maxLength)
			lastPart = partToAdd + lastPart;
			if (partToAdd == directorySeperator)
				//this is most likely the last part of a relative path (e.g. "\websites\myproject\www_myproj\App_Data\")
				//do not proceed to processing firstPart yet
				isFirstPartsTurn = true;
	if (lastPart == "")
		//the filename (and root path) in itself was longer than maxLength, shorten it
		lastPart = pathParts[pathParts.Length - 1];//"pathParts[pathParts.Length -1]" is the equivalent of "Path.GetFileName(pathToShorten)"
		lastPart = lastPart.Substring(lastPart.Length + ellipsisLength + firstPart.Length - maxLength, maxLength - ellipsisLength - firstPart.Length);

	return firstPart + ellipsisChars + lastPart;


Glat - hvem går først over?

Glat - 2010 01

Reading CSV files on 64 bit Windows Server

In the olden days I used the Jet-provider with a schema.ini to read CSV-files.

Yesterday - after having just finished a small console app on our 32 bit developer box - I deployed it to our 64 bit production server..... crash.... "provider not found".

After a fair amount of digging I downloaded one the many homemade CSV readers... Turns out it was written by an American, so there was no way to specify the charset of the file ;-)

Then I found the "Microsoft Access Database Engine 2010 Redistributable" package (read this small note on how to specify the connection string).
It's a bit bloated just for reading a CSV file, but it installed in a few minutes, did not require restarting the server, and it works perfectly.


Async made simple(r)

If you have ever coded asynchronous stuff in .NET, this upcoming .NET feature should get you fairly psyched.... why handle all the plumbing yourself, when you can just use this new feature, as described in the Channel9 video by Anders Hejlsberg

There's probably some sort of downside to this, but I couldn't spot it from what I heard in the presentation :-)

Captcha with a twist - Are you a human being?

Most sites have a captcha check on forms where users can contribute input such as comments or forum posts. However Stackoverflow have added some twists to this otherwise standard mechanism - one sophisticated and one... not so sophisticated (but very humorous).

The sophisticated: By default there is no captcha - but if they cannot determine whether you are man or machine when you post your question/answer, they redirect you to the captcha page.

The humorous: Instead of just confronting the user with a standard captcha form, they have added a picture which must have been a lot of fun shooting, along with the following refreshingly down-to-earth text:

Are you a human being?
We apologize for the confusion, but we can't quite tell if you're a person or a script.

Please don't take this personally.
Bots and scripts can be remarkably lifelike these days!

Enter the CAPTCHA displayed below, and we'll be out of your way.


... great stuff.

Examining and testing web services

When working with web services you sometimes just want to make a quick "lets hit it, and see what it does" test.

For that purpose WCF Storm is the must-have tool, which a fellow developer recently pointed out to me. It quickly generates a proxy and lets you specify parameters of complex types, add custom headers and  much more (none of which the older WebServiceStudio I used to use could do)... It also lets speaks WCF.

The lite version does everything you are likely to need. The paid versions contain functionality aimed at developers wanting to integrate their web services in test or build routines, or performance test them.

Afsted til Værløse

Ryetvej plantegningDer er nu skrevet under, financieret osv. - og jeg er dermed ejer af Ryetvej 61,
Pr. 1. juli tilbringer jeg derfor en del tid på den nye adresse, hvor der skal males/hamres/skrues/find-selv-på-flere, hvorefter jeg 1. august endegyldigt forlader Vestergade *snøft* ;-)

Ryetvej bagside


Structuring extensive SOAP webservice APIs

Last year I spend some time building a webservice SOAP API for the Metimus system. Although it only covers selected bits of the full system it is still fairly extensive.

In the process I spend quite some time figuring out how to structure SOAP services when you go beyond the simple "RegisterForCompetition" type services... so I thought I'd share, since my attempts at Googling a solution where unsuccessful.

The problem I ran into
When you build a webservice API, the natural instinct is to attempt to make it look like a true OO API as much as possible. So you will want to:

  • Use objects for parameters and return types instead of primitives, which is no problem with the complex types of WSDL
  • Partition your solution into multiple services, as you would have multiple classes in an OO API (e.g. a CompanyHandler service, a UserHandler service etc.)

Now the problem arises when you try to e.g. use a return value "User" object from one service operation as a parameter for an operation on another service. Your solution will not compile!
The reason is that even though you know it's the same class, the client proxy-generator does not (whether it is .NETs WSDL.exe, Java or any other environment). There is no way around it - at least not without making life miserable for the client programmer (and the reason we wanted it OO'ish and shiny was to make it easy :-)

You have two basic choises: Either you partition it into multiple services and use primitives for all parameters - or you keep all operations in one big service and you get to use objects for parameters and return values.

I ended up using the "one big service" option. To make it a bit easier to maintain I then used the partial class feature of .NET, so I didn't end up with one insanely large file.

Further info and examples
A full explanation of the problem including sample code illustrating the problem can be seen here in this Experts-Exchange question I wrote. In case you do not have an account, here are the resource-links that was posted in the answer:

- Microsofts description
- Another explanation