Localisation of your ASP.NET MVC 3 Routes

Our core product has recently undergone a localisation exercise as we plan to launch it in other european countries.  One of the first things we needed was to localise the routes on a per-country basis.

We started out remarkably luckily in that every route we delivered in the app was already custom.  We didn’t like the default route handler (Controller/Action/{stuff}) URL structure, and although we could have gone down the custom route handler approach, there were a few things that steered us away from that.

  1. we wanted full flexibility from an SEO point of view – as we dev’d we had no idea what would work well from an SEO point of view, so having each potential route customisable to whatever our SEO company desired was going to be a bonus.
  2. longer term plans will see us delivering a content management system to deliver an awful lot of the content – at that point, we may well be delivering custom routes via the DB too, so having a flexible routing system was essential.

Why not the default routes?

An example of some of the ‘out of the box’ routes we’d have gotten with the default route handler, versus what we actually wanted:

/MyAccount/UpdatePersonalDetails –> my-account/personal-details

/Winners/ByGame/{GameName} –> winners/by-game/{game-name}

Although generally, the conversion was a hyphen between caps and a full lowercasing, we found that replacing the default route handler with a custom ‘HyphenateAndLowercaseRouteHandler’ just didn’t answer enough of our use cases.

I’m sure google, bing and the other search engines will happily look at pascal cased words and discern them, though I as a human find it easier to read /our-new-game-has-paid-out-3-million-so far than /OurNewGameHasPaidOut3MillionSoFar.

One of the big selling points for not using the default routing was flexibility – we can change the routes without having to refactor/rename controllers or action methods, so there is a real separation there.

So, we started to build up our routing table with custom entries for each controller/action such as:

 routes.MapRoute("GameHistory", "game-history/{gameName}/{room}",
						new
						{
							controller = "BingoGamesHistory",
							action = "Index",
							gameName = "Bandit",
							room = "the-ship"
						}, namespaces);

and to date, across the whole front end application we have 183 custom routes.

Localising the Routes

It almost feels sham-like to be writing a blog post about this, though I still see questions on stack overflow about it, so thought I’d write this up.

What we did in the above example was replace the string (“game-history/{gameName}/{room}” with a localised resource – we now have a LocalRoutes which has something like the following:

image

and the routes.MapRoute command in global.asax replaces the string representation of the route with LocalisedRoutes.GameHistory_General.

Obviously from this point on, it’s then just a matter of adding a LocalisedRoutes.GameHistory.it, or LocalisedRoutes.GameHistory.es etc. to get the represnetation of the routes for those countries, and in our CI deployment the plan is to alter the web.config depending upon the deployment:

<globalization uiCulture="it-IT" culture="it-IT" />

Jobs a good un Smile

What next?

As I say, the next big phase of our project will include a content management system, so may well require us to have runtime routes injected into the routing table – I’ve never done it, but it’s something to be aware of. 

Sample Project

I’ve put together a simple project that demonstrates the above which will be something that folks can base their solutions upon if they they are having difficulty with the above description.  The example only localises routes, so the UI still remains in english, but you get the idea.

Download the example at google code

Unit Testing with DataAnnotations outside of MVC

This past week has seen us start on a big project at work to re-architect the site into .net and MVC2.  Naturally we have our models in a separate project, and we have two separate test projects (Unit and Integration) setup to use NUnit.

As it’s early days for us, and our first “real” MVC project I thought I’d write this up, a) as an aid to learning for me, but b) to try to gain feedback from the community on what they do with regards validation on their models.

I can see a few different ways we could have done this (annotate the ViewModels we’ll use on the front end, build in logic into our setters to validate, etc. etc.) but we’re now going down a route that so far feels ok.  That said, we’re focussing solidly on the modelling of our business logic at present, so haven’t yet brought the model “out to play” as it were.

Hopefully the above gives a wee bit of insight into where we are with it.

We’ve decided to plump for the MetaData model approach to keep the main objects slightly cleaner – an example for us would be:

namespace MyCompany.Models.Entities
{
	///

	///
	/// 

	[MetadataType(typeof(MyCompanyUserMetaData))]
	public class MyCompanyUser
	{
		public int UserId { get; set; }

		public string Username { get; private set; }
		...

		public void SetUsername(string newUsername)
		{
			if (Username != null)
				throw new ArgumentException("You cannot update your username once set");

			//TODO: where do we ensure that a username doesn't already exist?
			Username = newUsername;
		}
	}
}

and then in a separate class:

namespace MyCompany.Models.Entities
{
	public class MyCompanyUserMetaData
	{
		[Required(ErrorMessage="Your password must be between 6 and 20 characters.")]
		[StringMinimumLength(6, ErrorMessage="Your password must be at least 6.")]
		public string Password { get; set; }

		[Required(ErrorMessage="Your username must be between 6 and 20 characters.")]
		[StringLength(20, MinimumLength=6, ErrorMessage="Your username must be between 6 and 20 characters.")]
		[MyCompanyUserUsernameDoesNotStartWithCM(ErrorMessage="You cannot use the prefix 'CM-' as part of your username")]
		[CaseInsensitiveRegularExpression(@"^[\w\-!_.]{1}[\w\-!_.\s]{4,18}[\w\-!_.]{1}$", ErrorMessage = "Your username must be between 6 and 20 characters and can only contain letters, numbers and - ! _ . punctuation characters")]
		public string Username {get;set;}
	}
}

With all of this in place you’re all well and good for the MVC world, though unit testing just doesn’t care about your Annotations so your simple unit tests:

[Test]
public void SetUsername_UsernameTooShort_ShouldThrowExceptionAndNotSetUsername()
{
	// Arrange
	testUser = new MyCompanyUser();

	// Act

	// Assert
	Assert.Throws(() => testUser.SetUsername("12345")); // length = 5
	Assert.That(testUser.Username, Is.Null, "Invalid Username: Username is not null");
}

won’t give you the expected results as the logic of that is based upon the DataAnnotation.

What was our solution?

After much reading around (there didn’t seem to be an awful lot out there covering this) we took a two step approach.  First was to allow SetUsername to validate against the DataAnnotations like so:

public void SetUsername(string newUsername)
{
	if (Username != null)
		throw new ArgumentException("You cannot update your username once set");

	Validator.ValidateProperty(newUsername, new ValidationContext(this, null, null) { MemberName = "Username" });

	//TODO: where do we ensure that a username doesn't already exist?
	Username = newUsername;
}

Validator is well documented and there are a few examples out there of people doing this within their setters.  Essentially validating the input for a particular MemberName (Username in this case).

The second step was necessary because of the approach we’d taken with the MetaData class above, and it was a mapping in the TestFixtureSetup within our unit tests:

TypeDescriptor.AddProviderTransparent(new AssociatedMetadataTypeTypeDescriptionProvider(typeof(MyCompanyUser), typeof(MyCompanyUserMetaData)), typeof(MyCompanyUser));

This line (though I’ve yet to look down at the source code level) would appear to just be a standard mapping for the class to tell it where to find the metadata/annotations.

After putting those two things in place, the unit tests successfully validate against the annotations as well as any coded business logic, so jobs a good un!

Was it the right solution?

This is where I ask you, the person daft enough to suffer this blog post!  I have no idea if there is a better way to do this or how this will pan out as we propagate up to the MVC level – will I be causing us headaches taking this approach, will it simply not work because of overlap between the way MVC model binder validates versus what we’ve done down at the domain level?

It’s still early days for the project, and the above feels like a nice way to validate down at a business domain level, but how it pans out as we propagate wider and start letting other projects consume, hydrate and update the models… well that’s anyone’s guess!

Comments very much welcome on this one folks :)

NerdDinner, and initial thoughts on MVC

Although I’ve not yet finished it, I thought I’d start my wee reflection on MVC as learned through NerdDinner.

Obviously, the immediate thing that hits you is that you aint in Kansas any more – ignore the asp.net postback model, it’s all change and there is going to be some significant re-learn before I get anywhere near good I think.

I do love the separation of concerns, the theory behind it is sound from a maintenance and extensibility point of view.  Keeping my model tucked away nicely, and using it to provide the meat that the controller feeds of, which then in turn drives the View I think makes perfect sense.  I need to work far more heavily on the URL Routing before starting to design anything bigger just to see how a richer navigation hierarchy will sit.

I love the way postbacks are handled (at least in the NerdDinner app) and AcceptVerbs() just makes sense to me.  I can see I’m going to have to read up a bit more on programming against an interface, as I haven’t covered so much of this.  I wasn’t a big fan of the Respository pattern, I’d have perhaps gone down the facade route, or (when and if I understand it) perhaps IoC will help with this, though obviously this was just one example.

It’s my first successful play with Linq to SQL, and I’m liking the abstraction and the codified approach to it, though I’ll have to run some heavier tests through SQL Profiler to see how it works in terms of performance.

I’m going to have to look through the source code to find out just how all of the helper methods work rather than just use them – chucking Html.ActionLink() on the page is all well and good, but I want to know what it actually does in the code (easily enough done now that MVC source code is available)

I’m only just getting now to the integration/work with Ajax, which I think will be interesting – I shall keep the blog updated with stuff as I cover it.