Sergio and the sigil

Unescape my Strings

Posted by Sergio on 2008-05-07

Since I started using Visual Studio 2005 and now 2008 it bugs me that strings in the debug windows (Immediate, Watch, Locals, Autos, etc) are automatically escaped in C# syntax (well, at least in C# projects.)

Let's assume we are writing some code that produces HTML (or XML, or SQL, or JSON, etc) where the text identation is important for understanding the contents. If we find ourselves in the middle of debugging that code and check the value of a variable containing such type of text, here's what we would see.

System.Diagnostics.Debug.WriteLine(html);
Console.WriteLine(html); // BREAKPOINT HERE
Console.ReadLine();

The Immediate window becomes less useful. The line breaks below do not exist in the Immediate window, I added them here for clarity.

?html
"<html>\r\n\t<head>\r\n\t\t<title>Monthly Report</title>\r\n\t\t<style>
\r\n\t\t\t.results td {color:#999;}\r\n\t\t\t/*etc*/\r\n\t\t</style>\r
\n\t</head>\r\n\t<body>\r\n\t\t<h1>Report for April 2008</h1>\r\n\t\t<d
iv id=\"content\">\r\n\t\t\t<p>\r\n\t\t\t\ttext goes here...\r\n\t\t\t<p
>\r\n\t\t</div>\r\n\t</body>\r\n</html>"

Same problem in the Locals or Autos windows, but there at least you can use the visualizers.

Too many clicks for me, though. Besides, the visualizers are modal windows and that makes it harder when you want to compare things.

If you happen to know how to revert to the old behavior of keeping the text format, please let me know. Until I find the right way (or at least a better way) I'm using a little macro to output any selected escaped text, unescaped to a new output pane.

To create the macro:

  • View » Other Windows » Macro Explorer
  • Right-click "My Macros" » New Module
  • Call the new module Unescape
  • Add the following sub
Public Module Unescape
  Public Sub UnescapeText()
    Dim winName As String = "Unescaped"
    Dim win As Window
    Dim output As OutputWindow
    Dim pane As OutputWindowPane = Nothing

    If DTE.ActiveWindow IsNot Nothing AndAlso _
      DTE.ActiveWindow.Selection IsNot Nothing Then

      Dim text As String = DTE.ActiveWindow.Selection.Text

      If Not String.IsNullOrEmpty(text) Then

        text = text.Replace("\t", vbTab) _
          .Replace("\r\n", vbCrLf) _
          .Replace("\n", vbCrLf) _
          .Replace("\r", vbCrLf) _
          .Replace("\""", """")

        win = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
        output = win.Object

        For Each p As OutputWindowPane In output.OutputWindowPanes
          If p.Name = winName Then
            pane = p
            Exit For
          End If
        Next
        If pane Is Nothing Then _
          pane = output.OutputWindowPanes.Add(winName)

        win.Activate()
        pane.Activate()
        pane.OutputString(text)
      End If
    End If
  End Sub
End Module

I made a shortcul for this macro:

  • Tools » Options » Environment » Keyboard
  • Show commands containing: unescape
  • Select the macro when it gets listed
  • Add a Global shortcut, I used Ctrl+Alt+U

Now if I select the escaped text in the Immediate window and press the macro shortcut, I get the following:

If I keep selecting other escaped texts and unescaping them, they'll just be appended to that output, making it possible to do some visual inspection or comparisons.

Designing With Lambdas - Part III

Posted by Sergio on 2008-04-29

In the previous two installments of the series we discussed how we can use lambdas to encapsulate more processing logic and to create contextual APIs. In this post I will show another example of the latter.

Navigating directory structures

This time around we will try to create a safe way to browse and create directories without losing track of the current location. When we write code to read files or directory information from a directory structure, and we need to look into more than one directory, we have to be careful to always be sure of what the current working directory is or use only absolute paths. Neither approach is without its inconveniences.

What I'll try to show here is a way to enforce the working directory as a lambda context. That can be a mouthful so let me try to put it in simpler terms. I'd like to have a way to assert that when a particular code runs, the working directory will be changed to a specified location, and after that code finishes the working directory is restored automatically.

Without lambdas, the context setting could resemble the following:

string previousDir = Environment.CurrentDirectory;
try
{
	Environment.CurrentDirectory = @"c:\myapp";
	DoStuff();

	try
	{
		Environment.CurrentDirectory = 
				@"c:\myapp\subdirs\resources\images\toolbar";
		DoOtherStuff();
	}
	finally
	{
		Environment.CurrentDirectory = @"c:\myapp";
	}

	DoMoreStuff();
}
finally
{
	Environment.CurrentDirectory = previousDir;
}

All those try and finally are there to make sure we restore the appropriate working directory after we are done with each one. I don't know about you, but having a lot of those in my code would add a lot of noise, obscuring the real intent of the code.

What if we could, once again, encapsulate this pattern in a function that accepts a delegate? Here's what a way more revealing code would look like.

Dir.Change(@"c:\myapp", path =>
{
	//we're in c:\myapp
	Dir.Change(subdir =>
	{
		//we're in c:\myapp\subdir
		string dirName = DateTime.Now.ToString("yyyy-MM-dd");
		//we can ask it to create the directory if 
		//  not found (the "true" parameter)
		Dir.Change(dirName, true, dailyLogDir =>
		{
			//we're in c:\myapp\subdir\2008-04-29 (for example)
			using(StreamWriter wr = File.CreateText("logfile.txt"))
			{
				wr.WriteLine("Hello file.");
			}
		});

		//we're back in c:\myapp\subdir

		//we can pass in a deeper path
		Dir.Change(@"resources\images\toolbar", imagesDir =>
		{
			//we're in c:\myapp\subdir\resources\images\toolbar
			//listing all files here
			foreach(string file in imagesDir.GetFiles("*.png"))
				Console.WriteLine("Toolbar icon: " + file);

		});
	});
});

Within each Dir.Change block we can be certain that the current working directory will be the one we specified (unless your own code intentionally changes it.) When the code block finishes, we will be back to whatever directory we were before the block, guaranteed.

Note in line 4 that the name of the directory can be gathered from the parameter's name subdir. This is not always possible because we could be dealing with multi-level directory changes (line 22) or dynamically generated directory names (line 10). Additionally, the rules for naming directories are not the same as for naming C# identifiers. For these reasons, it's also possible to pass a string containing the name of the desired directory.

Here's the code that makes this possible.

public static class Dir
{
	public static void Change(Action<DirectoryInfo> execute)
	{
		Change(false, execute);
	}

	public static void Change(string path, Action<DirectoryInfo> execute)
	{
		Change(path, false, execute);
	}

	public static void Change(bool createIfNeeded, Action<DirectoryInfo> execute)
	{
		string path = execute.Method.GetParameters()[0].Name;
		Change(path, createIfNeeded, execute);
	}

	public static void Change(string path, bool createIfNeeded, 
							Action<DirectoryInfo> execute)
	{
		string previousDir = Environment.CurrentDirectory;

		try
		{
			if(createIfNeeded && !Directory.Exists(path))
				Directory.CreateDirectory(path);

			var di = new DirectoryInfo(path);
			Environment.CurrentDirectory = path;
			execute(di);
		}
		finally
		{
			Environment.CurrentDirectory = previousDir;
		}
	}

	public static bool TryChange(string path, Action<DirectoryInfo> execute)
	{
		if(Directory.Exists(path))
		{
			Change(path, false, execute);
			return true;
		}
		else
		{
			return false;
		}
	}
}

So there you have it. Another example of incorporating lambdas to design APIs that attempt to reduce code noise and eventually read more naturally. Hopefully you're starting to see a pattern here in terms of what a context can be and how lambda-aware APIs can help formalizing that context.

Wrong .Net Version being loaded

Posted by Sergio on 2008-04-23

At work one application has this intermittend problem that started when the developer started to use telerik's r.a.d. web controls. Once in a while, for some (until today) unexplained reasons, the telerik controls would start throwing this error:

This control is complied for ASP.NET 1.x. 
Please use the ASP.NET 2.0 native version: RadTabStrip.Net2.dll 

For reasons that are not relevant for this post, the application runs under ASP.NET 1.1, not 2.0. What the error message reveals is that sometimes .Net v2.0 was being loaded first in that appDomain. Resetting the app and hitting /default.aspx would be enough to make that error go away. Until it happened again a few days after that.

The application developers and the webmaster spent quite a bit of time checking IIS settings, .Net settings, other apps in the same appPool. Everything seemed correct.

Today I joined the effort to get rid of that problem and as soon as I saw some .asp files mixed with the .aspx ones it struck me. "We don't happen to have VB6 code doing interop with .Net code, do you?". That's right, we did. There are valid reasons for this but again it doesn't matter here.

I immediately brought up Scott Hanselman's two posts where he describes a similar situation. Different symptom, but similar enough to help us.

Basically, if the very first page to be hit in the application is an .asp file that does interop with .Net, it will load the .Net runtime and the default behavior is to load the newest version possible, which is version 2.0 in our case. Once loaded, that version stays throughout the lifespan of the appDomain, leaving Telerik's code flipping.

There are a few possible solutions, which are discussed in Scott's posts and comments. In our case it boiled down to the following alternatives:

  1. Update the application to .Net 2.0 or 3.5, migrate the telerik assemblies, and get rid of the ASP/VB6 code: This is the ideal scenario. It is possible, but a little bit too time consuming in our case because of the amount of VB6 stuff there (old app, important app.) But if we could afford the time, that would be the best route to go.

  2. Update the application to .Net 2.0 or 3.5 and migrate the telerik assemblies: The next best thing considering the VB6 problem mentioned above.

  3. Just get rid of the ASP/VB6 code: This could work too. But it seems like too much work to keep supporting .Net 1.x.

  4. Try the ISAPI filter suggested by Scott here: Looks like a quick and effective solution. I would be willing to try it out. Unfortunately we have no C++ expertise in the house and the developer felt a little uncomfortable deploying this solution and creating one more dirty little secret in this already convoluted application.

  5. Bootstrap the classic ASP engine to start ASP.NET the right way: The idea here is to add a global.asa file to the application and use the Application_Start event to call a dummy ASP.NET url. The trick is to invoke some /dummy.aspx file via HTTP, like shown in this article.

They are leaning toward #5 (path of least resistance, I know) but the mid-term solution will be #2 followed by #1 (schedules and budget permitting.)

Anyway, I just wanted to share this in case someone is also tasked with maintaining their own version of the worst application in the world. Thanks Hanselman for making me look good once again.

Update: We chose to go with #5 until we can afford to do #1. The solution was to add the following to the global.asa file (remember those?). The file bootstrap.aspx can be an empty file. It's there only to, erm..., bootstrap ASP.NET.
<script language="vbscript" runat="server">
Sub Application_OnStart
	Dim url, inet, s, xmlhttp 
	'the url must be absolute (i.e. start with http://)
	url = "http://127.0.0.1/myapplication/bootstrap.aspx"

	Set xmlhttp = CreateObject("MSXML2.ServerXMLHTTP") 
	xmlhttp.open "GET", url, false 
	xmlhttp.send "" 
	Set xmlhttp = Nothing 

End Sub
</script>

ALT.NET Conference Recap

Posted by Sergio on 2008-04-21
ALT.NET Conference, take two. When I signed for this conference for the second time I didn't honestly think it was going to be as good as the first one. Boy was I wrong. I got the feeling that the attendees came incredibly prepared. Prepared to stand up and voice their opinions instead of simply watching. Prepared to listen diverging point of views. Prepared to take part in spontaneous conversations, in the conference rooms, in the corridors, in the shared rides, in the hotel lobby, in the restaurants, in the frigging airport waiting to board the plane. And, most of all, prepared to be surprised. I was surprised at so many different levels. Who would have thought that a discussion around JavaScript would spark so much interest? I was certain that the participants that came straight out of the MVP Summit would be all geeked out, but no. I was surprised to see a few MVPs that chose to fly in only for the ALT.NET event and not the summit. I was also surprised to see that some of the greatest talks happened not during the conference hours, but late in the night after a few drinks. Maybe if I start drinking I will become one of the smart guys? From the sessions that I stopped by, several discussions and questions came back with me for further processing.
  • Microsoft v 1.0 vs Stable OSS
    • Is that decision based only on risk assessment?
    • MS is clearly happy with OSS built with .Net. Brad Abrams and ScottGu were there to assure that.
    • Are we lacking a corporation (not-MS) to back OSS, like Canonical, Red Hat, mySQL AB, etc?
    • Too many questions, no clear conclusion


  • Functional Programming: Great talk. As you may or may not know, I am interested in this type of stuff. Just to reiterate, I think FP has its place and we are just seeing the beginning of it in the .Net universe.

  • ASP.NET MVC update: Among all the surprises in the conference, this was the most intriguing one. I went in as a MVC believer and got out happier with webforms. Weird. Some of the upcoming changes in webforms will address important annoyances, like the id munging and routing for urls.

  • Innovation: I arrived late at this discussion and was kind of shocked to hear Scott Hanselman question (or even suggest) that we already have enough implementation of OSS alternatives for many things and maybe there's nothing left to be innovated there, maybe we should just move on instead of creating another mock framework or another blog engine. I think this is kind of absurd. A great part of the innovation is powered by lower level innovations. My example: we didn't seem to need Moq, but now that we have lambdas in .Net, Moq could innovate in that area. As long as the CLR and the language teams keep feeding us new tools, we will keep using the tools in new and creative ways. Anyway, the central point of the discussion was supposed to be if there's innovation in .Net or if we are only porting existing projects from other languages.

  • JavaScript tools and patterns
    • JSUnit - this is slow. too slow.
    • Do we need a JS-specific test runner?
    • We felt that we lack some guidance and some patterns
    • Watch for a new community around these premises


Redmond, the center of the ALT.NET universe

Posted by Sergio on 2008-04-17

...at least for the next three days

Tomorrow I'm hopping on a plane to Seattle/Redmond, WA to take part in the second ALT.NET Open Spaces Conference. I had the chance to be at the first one last October in Austin, TX and what a great experience that was.

My main motivation to attend the event in Austin was to learn from the more experienced folks how they adopted these practices and culture in their organizations. I wanted to participate in discussion about creating interest in self-improvement in my organization, my local .Net community, and eventually in the global .Net ecosystem.

The sessions I took part in included mostly these topics, like how can we help MSDN and MSDN Magazine become a great vehicle for spreading into the .Net community, among other things, practices that go beyond what Visual Studio shows in its Add » New Item, and the lessons that we can learn from the non-.Net space. The March 2008 issue of the magazine was a glimpse at what types of techniques and tools we could be seeing applied by more .Net developers, helping create more maintainable applications and more scalable development and support processes.

This time around I'm hoping to be in discussions that are more specific of Agile processes. I want to understand how to get started and how demonstrable Agile value really is. I'm tired of reading from people that quickly attached themselves to the Agile label just because it looks like the thing to do at the moment. I, for one have never experienced Agile in any organization I worked at. This event will give us a great opportunity to hear and question some of the people that are really employing Agile and experiencing some form of benefit from it. I want to make sure Agile isn't just another bandwagon.

There's always the risk that events like this become the proverbial echo chamber, where folks that think alike tap each other's back, celebrate their common thinking, and waste time discussion irrelevant technicalities. I'm happy to see a lot of blue badges and even people that I'm sure will be there to challenge ALT.NET to put up or shut up. One such individual is JDN — if you read the altdotnet mailing list you're familiar with his point of view. I think that is a good thing.

So, if you happen to be there too and see me, come and talk. That's why we are there for, right. And I'm not one of these guys that can only talk about software. I could spend hours chatting about other things like soccer and .... and ... darn it! Nevermind :)