Showing posts with label Single Action Controllers. Show all posts
Showing posts with label Single Action Controllers. Show all posts

Monday, March 11, 2013

Single Action Controller: more improvements

It's been more than a year since the last entry and I've done more improvements to the implementation of Single Action Controller in TEAM.Commons.Web. Most of the improvements have been around removing magic strings and using types instead.

The following gist shows some examples of what you get:


If you like type safety and finding errors at compile time rather than build time, you're going to love this approach, together with enabling view compilation.

On top of that I've also added some methods to help with testing of applications built with this library. This is how you verify that an action returns a View:


And this is how you check that an action returns a redirect to another Single Action Controller:

Sunday, January 29, 2012

Single Action Controller - Take 2

After almost a year of using the first version of Single Action Controllers over the ASP.NET MVC (http://rodolfograve.blogspot.com/2011/05/teamcommons-mvc-single-responsibility.html), I have applied a few improvements on it. I particularly didn't like the requirement to inherit from a custom class (SingleActionController), and the boilerplate code required in your Global.asax.

So, the current implementation (also available as a NuGet package at http://nuget.org/packages/TEAM.Commons.Web) doesn't require your controller class to inherit from SingleActionController but from the usual Controller class, and provides a helper method for Autofac users:

Global.asax:

protected void Application_Start()
{
  var builder = new ContainerBuilder();

  // Register your dependencies

  builder.RegisterTypesForSingleActionControllers(Assembly.GetExecutingAssembly(), "Base namespace of your controllers");
}

A controller:

using MyCompany.MyProject.ViewModels.Author;

// Name your namespace after your controller.
// This works very nicely with the directory structure you create in your project.
namespace MyCompany.MyProject.Controllers.Author
{
  // Name your class after your Action and make it inherit from the standard Controller
  public class Index : Controller
  {
    // One single action named Execute
    public ActionResult Execute(FilterDataModel filter) { ... }
  }
}

Saturday, May 14, 2011

TEAM.Commons: MVC + Single Responsibility Principle (SRP) = Single Action Controller

Since a long time ago I've been concerned about the increasing size and complexity of our ASP.NET MVC controllers: we have been clearly going against the Single Responsibility Principle.

UPDATE:

an improved version of this implementation is available here.

Common controllers

This is some sample code to illustrate how one of our typical controllers looked like:

using MyCompany.MyProject.Models.Author;

namespace TEAM.MvcSACDemo.WebUI.Controllers
{

  public class AuthorsController : Controller
  {

    // Dependencies (smell: too many dependencies)
    protected readonly IDbSessionFactory DbSessionFactory;
    protected readonly IIndexQueries IndexQueries;
    protected readonly IEditQueries EditQueries;

    // Actions
    // Smells: too many methods; too many qualifications in the classes' names)
    public ActionResult Index(AuthorsFilterDataModel filter) { ... }

    public ActionResult Create() { ... }

    public ActionResult Insert(AuthorInsertDataModel newAuthor) { ... }

    public ActionResult Edit(long id) { ... }

    public ActionResult Update(AuthorUpdateDataModel) { ... }
  }
}

The violation of the Single Responsibility Principle should be obvious.

Our new approach


My initial thought was to change the rule we have been using to create a controller, which is one controller per entity, and find some other rule that produces more controllers with less actions while keeping or increasing the organization level.

A few days ago I stumbled on this post by Derek Greer, and immediately asked my friend Yaniel Díaz help me to develop this idea. After a couple of iterations we had a couple of classes and extension methods that will let us (and also you if you like) implement Single Action Controllers keeping must of the nice ASP.NET MVC conventions.

First, let's see how do Single Action Controllers look like:

using MyCompany.MyProject.Models.Author.Index;

// Name your namespace after your controller.
// This works very nicely with the directory structure you create in your project.
namespace TEAM.MvcSACDemo.Controllers.Author
{
  // Name your class after your Action and inherit from SingleActionController
  // You could choose to name it IndexAction instead
  public class Index : SingleActionController
  {
    // Only one dependency
    protected readonly IIndexQueries IndexQueries;

    // One single action (notice the use of namespace to remove extra qualification in the classes' names)
    public ActionResult Execute(FilterDataModel filter) { ... }
  }
}

And this is how it all looks in the Solution Explorer:


Except for the "Author" and "OtherEntity" nested folders, it's all like "traditional" MVC.

Using it in your application


All the required classes are available in the TEAM.Commons package under the TEAM.Commons.Web.MVC.SingleActionControllers namespace. TEAM.Commons is also available as a NuGet package, which is the preferred way to share code these days.

After you install TEAM.Commons in your solution (or copy the code you need), you must register your routes using the custom SingleActionControllerRoute:

public static void RegisterRoutes(RouteCollection routes)
{
  routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  // Best practice to avoid MVC handling the favicon request
  routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });

  // Only change the original MapRoute by our MapSingleActionControllersRoute
  routes.MapSingleActionControllersRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Author", action = "Index", id = UrlParameter.Optional },
    new { }
  );
}

and then register SingleActionControllersFactory as the ControllerFactory:

// You must pass the fully qualified name of the assembly
// and the namespace of the controllers. 
// In future versions we'll simplify this step.
ControllerBuilder.Current.SetControllerFactory(new SingleActionControllerFactory(Assembly.GetExecutingAssembly().FullName, "TEAM.MvcSACDemo.Controllers"));

The internals


Thanks to ASP.NET MVC design we just had to implement a custom ControllerFactory, a custom Route and a custom base class for controllers.

Check the source code at the TEAM.Commons repository. It should be self-explanatory and could give you ideas about how to do it yourself in case you don't want to use TEAM.Commons.

Conclusions


The implemented solution is not perfect and I'm sure it could be improved, but we needed to go back to SRP in our controllers as soon as possible. I'll update this post when we find a new solution.

I hope you find this as useful as we do :-)