Quantcast
Channel: June 2009 – Programming with Palermo
Viewing all articles
Browse latest Browse all 10

The ASP.NET MVC ActionController – The controllerless action, or actionless controller

$
0
0

There has been quite  a bit of discussion about how controllers are really namespaces trying to get out once you use the concept on a nontrivial application. 

Brian Donahue’s post on The anti-controller revolution prompted me to do this little experiment.  He references some twitter posts by Jimmy Bogard, one of my esteemed consultants at Headspring SystemsChad Myers also has opined about the notion of more independent actions and has cited precedence.

My interest in this space is purely practical.  I really don’t care how patterns are published.  I don’t care about “being true” to the MVC pattern or any other pattern.  I’m more interested in being effective with web applications on .Net.  After having experience with MvcContrib, CodeCampServer, and a much larger ASP.NET MVC implementation (200+ screens), I have come to see how controllers end up searching for an identity.  What is a ProductController anyway?  That’s just about as specific as classes called ProductService, ProductManager, ProductUtility, etc. 

Overview

You can SVN checkout the following url to see my spike code:  https://palermo.googlecode.com/svn/actioncontroller/trunk.  You can get it in zip file format here. (I repeat these links below)

In the default ASP.NET MVC project template, there is a HomeController, and then there is an AccountController that hooks up the ASP.NET MembershipProvider.  The AccountController does registering, logging in/out, changing password, and it is WAY too big.  The AccountController lacks cohesion.  The AccountController has more than one reason to change.  Each of the actions seem like they are more cohesive.

I’m going to narrate a before and an after of the ASP.NET MVC default project as I refactored it into being a ActionController-based application.  The controller names were promoted to namespaces and the action names were promoted to controller names.  The requests for a GET and POST of the same url are handled by the same ActionController since the action name is the same.  There are two methods in the class; one that handles GET and one that handles POST.  Within the ActionController, the methods are named “execute” since the name of the action is in the class name.  View structure stayed the same.  Seems there isn’t much pain in the view structure.

Routes

Let’s look at the possible URLs in the default project:

  • / (GET)
  • /Home/About (GET)
  • /Account/LogOn (GET)
  • /Account/LogOn (POST)
  • /Account/Register (GET)
  • /Account/Register (POST)
  • /Account/ChangePassword (GET)
  • /Account/ChangePassword (POST)
  • /Account/ChangePasswordSuccess (GET)

Throughout my refactoring, the urls do not change.  The routes do not change.  The only thing that changes is that the controllers are broken up into multiple classes along action lines.  For instance, There are two LogOn actions.  One for the form rendering, and one to accept the post.  These two are cohesive together, but they are not cohesive when combined with register, like they are by default with the AccountController.

Before

image  Let’s start at the beginning.  To the top (MvcApplication2) is the default project with no modification.  You can check out the code yourself.  The HomeController is pretty easy to dissect, but the Account controller is responsible for 7 independent requests.  5 too many, I think. 

After

imageTo the top(MvcApplication1), we have what the project ended up looking like after the refactoring.

You can see that the actions from the AccountController were promoted to be controllers. 

Let’s take a look at the LogOnController.  I have pushed the two Execute methods to the top for clarity.  With ActionControllers, the controller is only concerned about one action.  In this case, the GET pass of the action renders a form, and the POST pass of the action modifies some server state.  Here is the code:

Sample ActionController

namespace MvcApplication1.Controllers.Account
{
public class LogOnController : ActionController
{
public ActionResult Execute()
{

return View();
}



[AcceptVerbs(HttpVerbs.Post)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification = "Needs to take same parameter type as Controller.Redirect()")]
public ActionResult Execute(string userName, string password, bool rememberMe, string returnUrl)
{

if (!ValidateLogOn(userName, password))
{
return View();
}

FormsAuth.SignIn(userName, rememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("", "Home");
}
}



public LogOnController()
: this(null, null)
{
}

// This constructor is not used by the MVC framework but is instead provided for ease
// of unit testing this type. See the comments at the end of this file for more
// information.

public LogOnController(IFormsAuthentication formsAuth, IMembershipService service)
{
FormsAuth = formsAuth ?? new FormsAuthenticationService();
MembershipService = service ?? new AccountMembershipService();
}

public IFormsAuthentication FormsAuth
{
get;
private set;
}

public IMembershipService MembershipService
{
get;
private set;
}

private bool ValidateLogOn(string userName, string password)
{
if (String.IsNullOrEmpty(userName))
{
ModelState.AddModelError("username", "You must specify a username.");
}
if (String.IsNullOrEmpty(password))
{
ModelState.AddModelError("password", "You must specify a password.");
}
if (!MembershipService.ValidateUser(userName, password))
{
ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
}

return ModelState.IsValid;
}

}
}

What’s at the heart of this, you might think?  There are two things:

  1. A Custom controller factory (to find the right ActionController)
  2. A controller base class, “ActionController”

Download

I spiked out an implementation of the ActionController.  This is completely non-vetted in a real environment, but the sample project is available for download here. (http://palermo.googlecode.com/files/ActionController.zip)  This includes an ASP.NET MVC project with the ActionController class.  You can SVN co here:  https://palermo.googlecode.com/svn/actioncontroller/trunk

Please download the code and check it out.  What it ends up doing for the actions is groups them cohesively and then the concept of the “controller” becomes a namespace.  The controller factory needs work to be able to locate ActionControllers that are unique within the controller namespace but not unique throughout the project.  This is a rough first pass that I did in 30 minutes.

I’m not sure if this is what I’ll commit to MvcContrib for more widespread consumption, but I (and my teams) are feeling a bit of pain with bloated controllers, so it’s worth considering.  What I like most about this approach is that the only thing that changed was the controllers.  The routes don’t change.  The view folder structure doesn’t change.  The Html helpers don’t change.  We merely refer to the concept of a controller as a namespace rather than a class.  We now refer to an action as a class instead of a method.


Viewing all articles
Browse latest Browse all 10

Trending Articles