Wednesday, 17 December 2008

Mocking the QueryString collection in ASP.NET

One of the hardest parts of building testable web applications using ASP.NET is the HttpContext object, which encapsulates access to the HTTP request and response, server state like the Session and Application objects, and ASP.NET's implementation of various other bits of the HTTP specification.

HttpContext has a God complex. It's all-seeing, all-knowing, ever-present, and most WebForms apps just call HttpContext.Current and work with whatever comes back. This approach really doesn't lend itself to test-driven designs, though, so the ASP.NET MVC team have implemented a collection of virtual base classes - HttpContextBase, HttpRequestBase, etc. - which gives us the ability to isolate elements of the HttpContext for testing purposes, either using a mocking framework or by writing our own test classes that inherit from those base classes. On the whole, this approach works fairly well - especially once you start explicitly passing an HttpContextBase into your controllers instead of letting them run amok with HttpContext.Current - but there's still some legacy implementation details inherited from ASP.NET that can cause a bit of confusion with your isolation tests.

In ASP.NET - both MVC and WebForms - the QueryString property of the HttpContext.Request claims to be a NameValueCollection. It isn't - which becomes immediately apparent if you're trying to test a controller method that handles IIS 404 errors. In classic mode, IIS will invoke a custom error handler as follows. Let's say you've mapped 404 errors to /MyMvcApp/Error/NotFound - where MyMvcApp is a virtual directory containing an ASP.NET MVC application, which contains an ErrorController with a NotFound() method.

image

When your browser requests http://myserver/page/is/not/here.aspx; IIS doesn't find anything, so it invokes your configured handler by effectively requesting the following URL:

http://myserver/MyMvcApp/Error/NotFound?404;http://myserver:80 /page/is/not/here.aspx

Notice that there's no key/value pairs in that query string. The code in my controller that parses it is using HttpContext.Request.QueryString.ToString() to extract the raw query string - but here's where it gets a bit weird. The framework claims that Request.QueryString is a NameValueCollection, but at runtime, it's actually a System.Web.HttpValueCollection. The difference is significant because HttpValueCollection.ToString() returns the URL-encoded raw query string, but NameValueCollection.ToString() returns the default Object.ToString() result - in this case "System.Collections.Specialized.NameValueCollection" - which really isn't much use to our URL parsing code.

So - to test our parsing code, we need our mock to return an HttpValueCollection. Problem is - this class is internal, so we can't see it or create new instances of it. The trick is to use System.Web.HttpUtility.ParseQueryString(), which will take the raw query string and return something that claims to be a NameValueCollection but is actually an HttpValueCollection. Pass in the URL you need to test, and it'll give you back a querystring object you can pass into your tests.

Putting it all together gives us something along these lines - this is using NUnit and Moq, but the query string technique should work with any test framework.

[Test]
public void Verify_Page_Is_Parsed_Correctly_From_IIS_Error_String() {

	// Here, we inject a test query string similar to that created
	// by the IIS custom error handling system.
	var iisQueryString = "404;http://myserver:80/i/like/chutney.html";
	var testQueryString = HttpUtility.ParseQueryString(iisQueryString);

	Mock<HttpRequestBase> request = new Mock<HttpRequestBase>();
	request.ExpectGet(req => req.QueryString).Returns(testQueryString);

	Mock<HttpContextBase> context = new Mock<HttpContextBase>();
	context.Expect(ctx => ctx.Request).Returns(request.Object);

	// Note that we're injecting an HttpContextBase into ErrorController
	// In the real app, this dependency is resolved using Castle Windsor.
	ErrorController controller = new ErrorController(context.Object);

	ActionResult result = controller.NotFound();

	// TODO: inspect ActionResult to check it's looked up the requested page
	// or whatever other behaviour we're expecting.
}

Monday, 15 December 2008

Open Source .NET Exchange

I’ll be presenting a short session on jQuery at the SkillsMatter Open Source .NET Exchange here in London on January 22nd.

open-src-dot-net-exchange-lIf you’re a .NET developer of any kind, you’ve probably seen or heard people talking about stuff like web UI frameworks, object-relational mapping, fluent APIs, asynchronous messaging, aspect-oriented programming – and you might well be wondering what they are, and why they’re relevant. These events are designed as a sort of “tasting menu” of open source frameworks and techniques – six fifteen-minute sessions that’ll give you some idea of what these technologies can do, why you might want to consider using them, and where you can find more information if you’re interested.

In the jQuery session, I’ll be showing you how jQuery’s CSS-based selector syntax and flexible “chaining” API let you add rich, cross-browser behaviour and effects to your web pages. I’ll demonstrate how to add animation, dynamic content and AJAX callbacks to your web pages,  and hopefully include a few examples from the multitude of freely-available plug-ins and libraries built on top of the jQuery framework. Yes, all that in fifteen minutes. Like I said, jQuery makes things easy.

You can see the full programme at SkillsMatter’s site. I’m really pleased that I’m speaking first, because it means I get to relax and listen to the rest of the speakers afterwards – in particular, Mike Hadlow’s session on the repository pattern. Mike’s code (and help!) were invaluable on one of my projects earlier this year, and in particular his Linq-to-SQL repository – part of the Suteki Shop project - was a great example of how this pattern can make your code cleaner and your life easier.

If any or all of this sounds interesting (or if you just fancy an evening of beer, pizza and geek chat) then please sign up and come along - especially if you’ve not come along to an event like this before.

Friday, 12 December 2008

Stuff You'll Wish You'd Known When You Switched To 64-bit Windows

A pagoda at sunset in Kyoto, Japan. This has nothing to do with 64-bit Windows, but it is quite pretty.64-bit Windows is great. I've been running on XP 64 and Vista 64  for about a year now. My extremely old Canon USB scanner isn't supported, and I had to wait a long time for 64-bit drivers for my Line6 Pod XT, but otherwise everything works very nicely - and for running virtual PCs, the extra memory is really worth it.

That said, there's a couple of underlying differences that result in some very odd behaviour in day-to-day usage. It's important to realize that 32-bit and 64-bit Windows subsystems exist as parallel but separate environments. Confusingly, the 64-bit tools and utilities live in C:\Windows\System32, and their 32-bit counterparts live in C:\Windows\SysWOW64. 32-bit processes on x64 Windows are actually running inside a virtual environment, which redirects requests for underlying system resources for 32-bit processes.

64-bit Windows ships with 32-bit and 64-bit versions of lots of common applications - including Internet Explorer and the Windows Script Host. Check out the links below for some more detailed discussion of the architecture and reasoning behind this.

Internet Explorer

You'll find Internet Explorer and Internet Explorer (64 bit) in your Start menu. The 64-bit version can't see any 32-bit components - so no Flash player, no Java, no plugins, no ActiveX controls, nothing. This is useful for testing, but not much else.

Windows Scripting Host

If you run a Windows script (myscript.vbs) from cmd.exe or directly from Explorer, it'll run as a 64-bit process, which means it can't see any 32-bit COM objects like ADO connections and datasets. If you explicitly invoke it using C:\Windows\SysWOW64\cscript.exe, it'll run as a 32-bit process.

The Windows\System32 folder

64-bit apps - like the notepad.exe that ships with Windows - can see C:\Windows\System32\ and it's various subfolders. 32-bit apps - like TextPad - can't see this folder because Windows is "hiding" the system folders from the 32-bit process. This is completely baffling when you try to edit your /etc/hosts file using your normal editor and it appears to be completely missing - even though you had it open in Notepad a second ago. There's a thing called the sysnative file system redirector that you'll need to set up to be able to see these folders from 32-bit apps.

The Registry

The same caveat applies to the registry.  When a 32-bit app asks for a value stored under, say,

HKEY_LOCAL_MACHINE\Software\MyCompany\MyProject,

64-bit Windows will actually return the value from

 HKEY_LOCAL_MACHINE\Software\Wow6432Node\MyCompany\MyProject.

This is normally fine, because most 32-bit apps are installed by a 32-bit installer, so the redirection is in place both during install (when the keys are created) and at runtime (when they're used). If you're manually importing registry keys - e.g. by doubleclicking a .reg file - they'll import into the locations specified in the file, and then your 32-bit apps won't be able to find them. You'll need to manually copy the keys and values into the Wow6432Node subtree (or edit the original .reg file and re-import)

References

http://blogs.msdn.com/helloworld/archive/2007/12/12/activex-component-can-t-create-object-when-creating-a-32-com-object-in-a-64-bit-machine.aspx

http://blogs.sepago.de/helge/2008/03/11/windows-x64-all-the-same-yet-very-different-part-5/

Thursday, 4 December 2008

Fun with Server.GetLastError() in classic ASP on Windows Server 2008

One of our sites, written many moons ago in classic ASP using JScript, uses a bunch of custom error pages to handle 404 errors, scripting errors, and so on.

Our error handling code looks like this:

var error = Server.GetLastError();
var errorMessage = "";
errorMessage += Server.HTMLEncode(error.Category);
if (error.ASPCode) errorMessage += Server.HTMLEncode(", " + error.ASPCode);
var errorNumber = error.number;
errorNumber = ((errorNumber<0?errorNumber+0x100000000:errorNumber).toString(16))
errorMessage += " error 0x" + errorNumber + " (from " + Request.ServerVariables("SCRIPT_NAME").Item + ")\r\n\r\n";
if (error.ASPDescription) errorMessage += "ASPDescription: " + error.ASPDescription + "\r\n";
if (error.Description) errorMessage += "Description: " + error.Description + "\r\n";

// and then we log and useful with errorMessage

On our old server, this worked because the HTTP 500 error page was mapped to a custom URL, /errors/500.asp, which included the code above.

When we migrated our site onto IIS7 recently, this stopped working - the custom page was still executing, but Server.GetLastError() wasn't returning any information about what had gone wrong.

There was a very similar known bug in Vista which was supposedly fixed in SP1, but it looks like the same fix isn't part of Windows 2008 Server yet. There is a workaround, though - if you set the site's default error property (under IIS settings -> Error Pages -> Edit Feature Settings...)to the custom page (see below), IIS will invoke this page whenever an error is not handled by an explicitly configured status-code handler (so your 404, etc. handlers will still work) - but for some reason, handling the error this way means Server.GetLastError() still works properly.

image