Tweaking the AntiForgeryToken on ASP.Net MVC

A release candidate for ASP.Net MVC has just been released – http://haacked.com/archive/2009/01/27/aspnetmvc-release-candidate.aspx.

It’s only got a few changes from the beta functionally but there is one thing they sneaked in from the futures assembly, the Anti Forgery Helper. It’s well worth taking a look at and Steve Sanderson did a great post on it while it was still in the Futures assembly (incidentally, his book, even in pre-release form is really good). They fixed the bug it had in the futures bundle that caused it to throw an exception if you use it from a virtual directory and presumably tidied it up some so it should be in the final release.

There is one thing it doesn’t appear to do currently however, and that’s allow you to specify the scope of the cookies it sets. In order to do that I created my own helper extension that just wraps around their method. All I do is set the path on the cookie to be the application path so that if it’s in a virtual directory, the cookie only affects that application. That way I can have a test and a live site running off the same box, just in different virtual directories.


public static class MyAntiForgeryExtensions
{
  // Methods
  public static string MyAntiForgeryToken(this HtmlHelper helper)
  {
      return MyAntiForgeryToken(helper, null);
  }

  public static string MyAntiForgeryToken(this HtmlHelper helper, 
                                          string salt)
  {
    // for some reason it doesn't seem to be reading it right and
    // it then writes a cookie with a blank value.  Fatal later on.
    HttpContextBase context = helper.ViewContext.HttpContext;
    context.Request.Cookies.Remove("__RequestVerificationToken");
    string fragment = helper.AntiForgeryToken(salt);
    HttpCookie cookie = context.Response.Cookies["__RequestVerificationToken"];
    cookie.Path = context.Request.ApplicationPath;
    return fragment;
  }

}

That will work seamlessly with the existing helper and attributes, you just put a My in front of the AntiForgeryToken call in your .aspx.

<%= Html.MyAntiForgeryToken() %>

At the moment there is an additional kludge in there to prevent the asp cookie from being read again. Their token reads the old one and uses that to create a new one if it’s found. For some reason on my site that seems to result in it writing a blank value for the cookie. I haven’t figured out why or whether it’s something I’m doing but for now that kludge saves my website from crashing.

Creating batch files that are relocateable and can be run from anywhere.

Use %~dp0 to get the path a batch file is located in. This is useful if you want to access resources that are stored relative to the batch file. This allows you to get around the fact that the current directory for a batch file is the directory you ran it from, not where the batch file is located.

An example would be,

@echo off
call %~dp0program_locations.bat

This would call another script in the same directory as the batch file called program_locations.bat. There is no slash between the two bits because the path returned by %~dp0 has a trailing slash already.

There are a whole bunch of other things you can do with %~ but that’s the one I tend to use the most often. The d stands for drive and the p for path. The overall command is working on %0, the program being run.

Getting a usable errorlevel from MbUnit

I run the MbUnit console program from my command line build process and I want to halt the build process if a test fails. Unfortunately the console program doesn’t appear to return any error code for the version I have (not the latest). To fix that I created a quick and dirty perl script to run it and read the output for the failures. The script also abbreviates some of the output because I didn’t want to clutter up the console too much.

#!/usr/bin/perl

use strict;

my @assemblies = @ARGV;

my $ass = join " ", @assemblies;
my $dir = $ENV{'ProgramFiles(x86)'};
if(!$dir)
{
	$dir = $ENV{'ProgramFiles'} 
}
my $program = $dir . '\MbUnit\MbUnit.Cons.exe';
if(!-f $program)
{
	$program = $dir . '\MbUnit\bin\MbUnit.Cons.exe';
}
my $command = '"' . $program . '" /v ' . $ass;
#print $command, "\n";

open CMD, $command . "|";
my @lines = <CMD>;
close CMD;
foreach my $line (@lines)
{
	if($line =~ /Loading test assemblies/)
	{
	}
	elsif($line =~ /Loading \w+/ || $line =~ /failure/)
	{
		print $line;
	}
	if($line =~ /Tests finished: (\d+) tests, (\d+) success, (\d+) failures/)
	{
		print $line;
		if($3 > 0)
		{
			exit 1;
		}
	}
}


It’s then a simple matter of running your tests from a batch file and aborting if the tests fail,

...
echo Running unit tests
echo.
run_tests.pl UnitTests\bin\%config%\UnitTests.dll WinFormsUnitTests\bin\%config%\WinFormsUnitTests.dll 
if ERRORLEVEL 1 goto :error
...