25 November 2010

Adding a design mode to your MVC app

When developing websites you'll likely have ended up in the situation where you need to make some styling changes to a page that's buried deep within the site. If that page is at the end of a process such as registration or checkout then it can be extremely time consuming entering test data that passes validation in order to navigate to the correct page. Add to that the complexity of maybe needing to log in and also having to do the same thing on multiple browsers and things can get ridiculous. If you’re using the WebForms view engine then you have limited design time capability in Visual Studio but this isn’t satisfactory for ensuring cross-browser compatibility.

What's needed is a dumb version of the site which simply renders the views using a variety of data. Effectively you want to create a load of static pages, each with ViewData, Model etc set up so that they represent a different step in one of the real processes on the site. Using this version you’d be able to get to the correct page straight away, be able to refresh it quickly after making markup or CSS changes and be able to visit the page in all your test browsers. Ideally using this version of the site will require no authentication and it wont have any external dependencies like databases or web services that must be set up or configured.

We can use a set of different controllers to do this, each having some hard coded model data for example:

// A real controller may look like this... 
public class PeopleController : Controller
{
	public ActionResult Index()
	{
		List<Person> people = GetListOfPeopleFromDatabase();
		return View(people);
	}

	private List<Person> GetListOfPeopleFromDatabase()
	{
		// Do some data access
		
		return new List<Person>
			{
				new Person{ Name = "Runtime Person 1" },
				new Person{ Name = "Runtime Person 2" },
				new Person{ Name = "Runtime Person 3" },
			};
	}
}


// And our design time controller like this...
public class PeopleController : Controller
{
	[Description("Empty people list page")]
	public ActionResult EmptyList()
	{
		return View("Index", new List<Person>{});
	}

	[Description("People list page with 5 random people")]
	public ActionResult ListWithFivePeople()
	{
		return View("Index", new List<Person>
			{
				new Person
				{
					Name = "John Smith"
				},
				new Person
				{
					Name = "Betty Davis"
				},
				new Person
				{
					Name = "Steve Jobs"
				},
				new Person
				{
					Name = "Bill Gates"
				},
				new Person
				{
					Name = "John Carmack"
				},
			});
	}
}

This will work best if your model classes or, the data entity classes you're passing on to your views are dumb i.e. they don't try to do any database access when the view renders. If you already have your controllers in a separate assembly then it should be a relatively simple task to swap your design time ones in and use them instead. If however you have the standard MVC setup of controllers, views and models all in the same project and assembly then things are a bit more difficult.

At the very  least we want our design time controllers in a separate folder of our project, away from the real ones. The issue with this is that the default MVC controller factory will find them here anyway. Thankfully we don't need to implement an entire new factory, we can hide them from the default one by simply breaking with the convention it uses to identify them, the easiest way being not naming them "...Controller".

Home page

A nice to have in this "design" mode would be a default page which shows a list of links to all the actions of the design time controllers with descriptions for what each represents. This would be particularly useful when handing the markup and CSS over to a third party to be styled up as it allows them to quickly access each variation of each screen. You'd end up with something like this:

  • Products
    • List products
    • Search products
    • View product
    • Product category
  • Basket
    • Empty
    • Full
    • Saved
  • My Account
    • Addresses
    • Billing details
  • Home
  • Contact us

Variations

In addition to each individual view the design time functionality could also allow for variations of these pages e.g. logged in / logged out  views, special offer views, user customised views etc. Variations could be  defined on an action, a controller or on the whole site and rather than defining the particular data in each of these cases a transform function could be defined which is called before view render. This function could do work along the lines of setting IsAuthenticated booleans for the logged in / logged out case and possibly more complex operations otherwise.

This would allow a wide variety of viewable pages to be created without  needing to specifically define data in all those cases.

Proof of concept

I've put a quick proof of concept up on Github here:
https://github.com/dezfowler/MvcDesignMode

There's the main MvcDesignMode library and an example MVC app based on the standard template site which has few design time controllers named "...Designer" rather than "...Controller". When not in design mode this should prevent them ever being accidentally accessed provided you're using the default controller factory. I have the code to enable design mode in the App_Start of Global.asax.cs and it looks like this:

bool designMode = Convert.ToBoolean(ConfigurationManager.AppSettings["DesignMode"]);
if (designMode)
{
	DesignMode.Activate(typeof(HomeController));
}
else
{
	AreaRegistration.RegisterAllAreas();
	RegisterRoutes(RouteTable.Routes);
}

Here I'm just using a boolean configuration setting in web.config to turn the mode on and off but how you might choose to do it is up to you. If the design mode is activated the standard application startup stuff is skipped mainly because design mode uses a standard set of routes. Any links in your pages built using custom routes wont work correctly but the point of design mode isn't to be able to navigate around the site as normal it is that you can jump straight to a particular page in one click. I’m passing a type in to the Activate method simply to server as a pointer to the assembly where my design time controllers reside.

Once in design mode the design time controller factory hunts down the special controllers ending with "...Designer" and effectively indexes them pulling out action method names and also the text from a Description attribute defined on the methods. Using this index it builds up a special site map listing each controller and its action methods as links.

Conclusion

Have a look at the solution on Github or have a go implementing something similar yourself. On a number of recent projects I could see having a setup like this saving a lot of time and effort not just for styling and markup but probably developing simple JavaScript stuff as well. I'll definitely be using it myself in all my future MVC projects.

1 comment:

Anonymous said...

I'm creating a similar thing with WPF MVVM, except I have three data types, Design data for Blend, Test\Developer data both used for testing and scenario base view access that uses a jump window to get there and Live for normal database access. I think this is the next logical step for both MVVM and MVC.