“Controllerless” MVC: A high-level overview of my attempt

July 1, 2009

So there has been a lot of talk recently about doing away with controllers in MVC. My first read of it was from when Brian first mentioned it. The concept didn’t gain much traction until the discussion was renewed by Chad Myers’ post, which was then followed up by another post from Brian and then Jeffrey Palermo’s post. Phew! In any case, the basic concept is to map a request to one or more commands that are relevant for that request. The purpose of the command is simply to return data, that’s it. It should have no knowledge of anything regarding the request (no Context, no Request or Response objects).

I liked the idea but didn’t have a chance to work on it, until now. I have a new project I’m starting up at work and thought it would be a good chance to try my hand at seeing how I could implement this sort of framework.

Why I Don’t Like Controllers

Part of my current job is upgrading some existing sites to use the ASP.NET MVC framework. As I was doing this, one thing kept bugging me: the controller was doing too much! Not only did I have to retrieve data, but I also had to know how to validate it, save it, redirect to the appropriate view if necessary, etc. Since one controller had to manage every action on a single page, it quickly became large. Frankly, I felt like we were moving back towards the web forms code-behind model, where everything the page needed was in its code-behind.

The other aspect I didn’t like about how the framework worked (out of the box) was that, even when using partials, the main page still needed to contain all of the data so that it could send the correct model to any partials that implemented it. This didn’t feel right to me. At least when I did Model-View-Presenter in web forms I could have user controls that contained their own presenter, so that the containing page really didn’t need to know anything about what the controls were doing, or what data they needed. This was a step back, in my opinion.

So Why Did I Do What I Did

From Scratch vs. Utilizing ASP.NET MVC
What I am currently doing on my new project is attempting to implement the concept above (one request to one or more commands). I was torn between starting from scratch, or building it on top of the existing ASP.NET MVC framework. I decided to do the latter, as I thought the MVC framework was extensible enough to plug in my command implementation, and it also kept me from having to reinvent the wheel (ex: rendering the correct view, routing, etc).

The basic concept is I implemented was to create a single controller (utilizing the front controller pattern) that handled all requests. This would then find and call “Execute” on the correct commands. Finally, the value returned from the Execute method would be loaded to the ViewDataDictionary.

I also liked the idea of building on top of the MVC framework because it will make it simpler down the line for me to upgrade our existing projects to use this new approach, should we decide to.

Why Not Use Existing Ideas?
The first answer to this is because I thought this would be fun. :) The second is none that are out there didn’t quite meet the need, as I saw it. For example:

  • FubuMVC still revolves around a controller.
  • Jeffrey Palermo’s spike (linked above) simply limits a controller to one action, definitely better but not quite there.
  • Subcontrollers (via MvcContrib) require passing in the parent controller as a parameter to the action – eek! I definitely don’t want to be coupling controllers like that.

So How Did I Do It

Before I go into too much detail, a word of warning: nothing of what you see here is anywhere near production ready. My hope was to get some ideas out there and let people go from there (or, if my ideas suck, now they know what not to do).

Now that that’s out of the way, here are some notes on what I did. The code below is purposely incomplete, as I’m still developing this. As it gets more mature I will put it all on Google code to be downloadable, but not just yet.

The Controller and Controller Factory

As I mentioned above, the ASP.NET MVC framework is pretty extensible, so you can create your own custom versions of many of the classes it uses under the covers to work. The first thing I did was to create my own controller factory. Its main purpose in life is simply to create my front controller. It can also do one other thing, but that is outside of the scope of this high level post. In any case, here’s my factory:

   1: public class ConfigControllerFactory : CommandControllerFactory
   2: {
   3:     public override IController CreateController(RequestContext requestContext, string controllerName)
   4:     {
   5:         var commandController = new ConfigController();
   6:         return commandController;
   7:     }
   8: }

You’ll notice that it extends the CommandControllerFactory class (an abstract class with one method that I won’t get into now). You can also see that all it’s doing is returning my front controller. Here’s that controller:

   1: public class ConfigController : CommandController
   2: {
   3:     public override Type[] CommandTypes
   4:     {
   5:         get { return typeof (MyCommand).Assembly.GetTypes(); }
   6:     }
   7: }

This also implements an abstract class, CommandController, which does all of the magic (I’ll show more of it later). The purpose of this controller’s implementation is to pass to my abstract controller all commands that exist in my system.

So theoretically by implementing these two abstract classes, your web app can now use this framework. That is of course, after some additions to the global.asax.

Global.asax Changes

The first thing I changed is the default route data. Since we no longer are using controllers, I didn’t like the idea of having {controller}/{action} in my route. The MVC framework uses the controller value to find the controller, so it’s moot now anyway. So I changed my route to be the following:

   1: routes.MapRoute(
   2:     "Default",                                              // Route name
   3:     "{context}/{action}/{id}",                           // URL with parameters
   4:     new { controller = "Command", action = "DefaultAction", id = "" }  // Parameter defaults
   5: );

I liked the idea of “context” and “action” because it better showed how the commands are grouped. We have a context (like an account) and an action (saving a new account). Yeah it’s a rip-off of the BDD naming style, but I felt it was appropriate.

The other thing I had to add to the global.asax was to set the MVC framework up to use the correct factory. I did this by adding this to Application_Start:

   1: // Tell the MVC framework to use the CommandControllerFactory to create controllers.
   2: ControllerBuilder.Current.SetControllerFactory(typeof(ConfigControllerFactory));

That’s it!

The CommandController

This class is where the magic happens. Again, it’s early on (and therefore not very clean), so forgive the code. It is simply here as a reference, not as a production ready “copy and pastable” class.

The CommandController inherits from the MVC framework’s Controller class and overrides two methods: ViewResult and ExecuteCore.

ExecuteCore is where the request first comes in. At a high level, we get the context and action from the route data object, and from that we generate a unique key, which lets us map a request to the appropriate commands. Here are the simple properties:

   1: private string Context
   2: {
   3:     get { return RouteData.GetRequiredString("context"); }
   4: }
   5:  
   6: private string Action
   7: {
   8:     get { return RouteData.GetRequiredString("action"); }
   9: }
  10:  
  11: private string ContextActionKey
  12: {
  13:     get { return string.Format(NamespaceFormat, Context.ToLower(), Action.ToLower()); }
  14: }

Now that we have that key, the ExecuteCore method simply loops through all of the actions, and executes any that contain that key in their namespace. This is the link between a request and its associated actions. That way a request can execute any number of relevant actions. Here’s the loop:

   1: foreach (var commandType in CommandTypes)
   2: {
   3:     // If it's a command, execute it
   4:     if (typeof(ICommand).IsAssignableFrom(commandType) && !commandType.IsInterface)
   5:     {
   6:         // Ensure the command is in the correct namespace for the view - we only want to execute commands related to the current view
   7:         if (commandType.Namespace.ToLower().Contains(ContextActionKey))
   8:         {
   9:             var commandObject = IoC.Resolve(commandType) as ICommand;
  10:  
  11:             // Add result to ViewData if one was returned
  12:             ExecuteCommandAndLoadResultToViewData(commandObject);
  13:         }
  14:     }
  15: }

And here’s how we store whatever is returned from the command’s Execute method call:

   1: result = commandToExecute.Execute();
   2: if (result != null)
   3: {
   4:     try
   5:     {
   6:         ViewData.Add(result.GetType().FullName, result);
   7:     }
   8:     catch (ArgumentException)
   9:     {
  10:         throw new ArgumentException(string.Format("The type: {0} has already been added to the ViewData collection", result.GetType().Name));
  11:     }
  12: }

So the basic concept is each model is a unique entry into the ViewDataDictionary (for better or worse). This allowed me to create generic base classes for all of my views to inherit from. Basically it’s a class that takes a generic type. The view inherits from this class, passing in the model it expects as the generic type. Then the class retrieves the model object from the ViewDataDictionary. This means my views are now independent of the commands, they only know what model to look for in the dictionary. This also means I can create truly independent partials, since they do not rely on models being passed into them.

Summary

So there’s a lot more I didn’t touch on (like how to load an object from the view model, so that the command can use it). That will be coming up in my next posts. I’m just hoping this whets your appetite, and also generates feedback to improve this functionality.

Advertisement

3 Responses to ““Controllerless” MVC: A high-level overview of my attempt”

  1. Peter Weissbrod said

    Sounds interesting.
    It sounds like youve taken the work of a controller and dispersed it out to disjoint classes mapped by an IOC container.

    These classes build viewmodels as their result. I like it!

    What happens if the result from the sub-class is null?

    Also, how would you consider aspects such as security and get/post requirements?

  2. Erik said

    @Peter: You're correct. In a way, it is kind of moving the action from a method to a class, since an action can do a lot of things (and often does).

    A null value returned is handled cleanly. At the moment, the "front controller" simply checks if the result of the command's Execute method is null. If it is, do nothing, if it's not null, add it to the ViewDataDictionary. I'm actually using it in some save scenarios at the moment, when all the action has to do is save something, so I don't need to add anything unless an error occurred.

    I haven't specifically addressed the get/post aspects yet, but have a few ideas. One is, the front controller can tell if the request is a get or post and simply fire the appropriate command (somehow). Another way is simply by using attributes, like in the current MVC framework. I haven't decided yet, but will let you know as it develops. :)

  3. [...] espoused in several blog posts.  I was able to mess around with my own framework, and I gave a high-level overview of it a while back.  Since then, I’ve been able to flesh out the framework as it was dog-fooded [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.