TRsDomain.dk

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.

SimpleText_admin

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:
c:\websites\acmeinc\myproject\www_myproj\App_Data\somefile.txt
into this:
c:\websites\acmeinc\...\App_Data\somefile.txt

So I went googling, but found no exact matches. This post on codinghorror.com 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(@"\\192.168.1.178\theshare\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "\\192.168.1.178\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)
			{
				break;
			}
			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")
			}
			else
			{
				isFirstPartsTurn = false;
			}
			firstPartsUsed++;
		}
		else
		{
			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)
			{
				break;
			}
			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
			}
			else
			{
				isFirstPartsTurn = true;
			}
			lastPartsUsed++;
		}
	}
	
	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;
}

 

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.

StackOverflow_Captcha_Thumb

... great stuff.

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

Slooow deletion as recycle bin fills up

I was going mad. It was taking forever to delete file in VS.NET, and reverting changes to a Subversion'ed file took just about as long as a total HD-format (at the time I did not connect the two).

Then I found a post where Matt explaines how emptying the recycle bin freed him of the same issue.... two mouse-clicks later VS.NET and Subversion was 3500% faster.

(I still run XP, so this might have been fixed in Vista+)

 
*