Saturday, 21 September 2013

ControllerPathViewEngine - Structuring View Folders based on Controller Namespace Hierarchy in ASP.Net MVC

Introducing ControllerPathViewEngine

ControllerPathViewEngine is an ASP.Net MVC ViewEngine that allows you to structure your views in folders that match the namespaces of your controller, like this:

If you know about git, go ahead and clone the repository on your development machine, otherwise you can download a zip file of the source. If you just fancy a look around, you can browse the code directly on GitHub.

The Problem

One thing that has always annoyed me about ASP.Net MVC is the flat Views folder structure.

When an ASP.Net MVC controller action renders a view, the built-in view engines will, by default, expect your Views folder to be structured based on the following pattern:

Views/{controller name}/{action name}.{extension}

There's no support for structuring views in folders that relate to the namespaces of their related controllers.

In a simple project with a handful of controllers, this probably won't be a problem. However, on a larger project with a lot of controller code, you'll probably find yourself organising your controllers within a deeper namespace hierarchy.

A nested hierarchy makes a complex application manageable by providing an isolated context to the things it contains, keeping things focussed and easier to name. With the flat view structure we lose some of this context and it becomes difficult to relate view to controller.

And don't even think about having more than one controller with the same name! A flat views folder structure is going to lead to naming clashes, unrelated views sharing the same folder and all sorts of shenannigans.

ASP.Net MVC Areas could alleviate this to some extent by allowing you to group and manage related controllers, views and their related routes in a more modular fashion. They don't really solve the core problem though and using an area only adds a single level to your Views folder structure, based on the following default pattern:

Areas/{area name}/Views/{controller name}/{action name}.{extension}

The Solution

Anyway, that's enough moaning justification - writing the above has taken longer than it took to write the code!

A quick look at the ASP.Net MVC source code led me to an extremely simple solution. We simply intercept the call to FindView and FindPartialView and cheekily switch the folder path for the controller name during the call to the base implementation. This allows us to leverage the core caching and view finding functionality.

The project's README contains some background on implementation and the logic used to drive the view folder structure. See the code itself and unit tests to learn more about the implementation.


I had a quick look around at some other approaches, including this one. I had a couple of problems with this implementation however (in fairness to the writer of the post, the main intention was to demo view engine extensibility). Firstly, the cheesy %1 placeholder used in the view location format strings shows up in the exception thrown when a view cannot be located, which doesn't really help the developer work out which view file is missing. Secondly, I'm not sure that it would support the caching functionality that VirtualPathProviderViewEngine uses to optimise view retrieval when dealing with controllers with the name name (a bit of an edge-case but there's potential for a big WTAF there).

Hope you find this useful. Comments and suggestions are welcome as usual.