As I keep doing this on MVC projects, thought I may as well blog it, so the next time I need it I can find it myself :)
To keep ASP.NET MVC controllers clean and organised, I find it much easier to split them so they handle only a single action each. This helps significantly with maintaining some semblance of the Single Responsibility Principle, allowing each controller to deal with a single action, and not have concerns like Logon, Logoff and ChangePassword all start globbing together under a single controller called "Account"
With a little bit of magic in the ControllerFactory you can tell ASP.NET MVC3 to resolve controllers from your own directory and class structure, meaning we can have controllers that look like:
public class IndexController : Controller
{
[Authorize]
public ActionResult Index()
{
return View();
}
}
https://gist.github.com/1075169
The Controller Factory
I tend to use Castle Windsor to do this magic for me, though any IoC container would do, and you could even do it without a container if you were really masochistic!
In Windsor the ControllerFactory looks like:
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel kernel;
public WindsorControllerFactory(IKernel kernel)
{
this.kernel = kernel;
}
public override void ReleaseController(IController controller)
{
kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext context, Type controllerType)
{
var baseNs = typeof(SecureController).Namespace;
var ns = context.RouteData.GetRequiredString("controller").ToLower();
var controllerName = context.RouteData.GetRequiredString("action").ToLower() + "controller";
try
{
var controller = kernel.Resolve<IController>(baseNs.ToLower() + "." + ns + "." + controllerName);
return controller;
}
catch (ComponentNotFoundException ex)
{
throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", context.HttpContext.Request.Path));
}
}
}
https://gist.github.com/1075171
The Controller Installer
So that Windsor has some way of resolving our controllers, you need to register them in the container. This installer will scan for all classes implementing IController and add them into the container against a key matching their namespace:
public void Install(IWindsorContainer container, IConfigurationStore store)
{
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
if (typeof(IController).IsAssignableFrom(type) && !type.IsAbstract)
container.Register(Component.For(type).ImplementedBy(type).Named(type.FullName.ToLower()).LifeStyle.Transient);
}
}
https://gist.github.com/1075176
Which leaves your controller folder structure looking like:

GET and POST
Where an action has a Get and a Post action, for example where you display a form, then take some actions based on the form, you just put the two actions in the same Controller, you aren't breaking SRP here as both actions are inherently linked anyway:
public class ChangePasswordController : Controller
{
private readonly IUsersDB users;
public ChangePasswordController(IUsersDB users)
{
this.users = users;
}
[Authorize]
public ActionResult ChangePassword()
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult ChangePassword(ChangePasswordViewModel model)
{
if (ModelState.IsValid)
{
try
{
users.ChangePassword(User.Identity.Name, model.OldPassword, model.NewPassword);
return RedirectToAction("ChangePasswordSuccess");
}
catch (DataAccessException ex)
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
}
return View(model);
}
}
Posted
07-11-2011 2:08 AM
by
Jak Charlton