Enabling IoC in ASP.NET ActionResults (or, a better ActionResult)


One of the more interesting abstractions in ASP.NET MVC is the concept of an action result.  Instead of calling a method to direct the result of calling an action, such as frameworks like Rails allows:

def create
      @book = Book.new(params[:book])
      if @book.save
            redirect_to :action => 'list'
      else
            @subjects = Subject.find(:all)
            render :action => 'new'
      end
   end

Instead, the return value of an action directs the MVC pipeline on what to do next:

public ActionResult Index()
{
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    return View();
}

Instead of displaying the view when the View method is called, the command to render a view is packaged up into a ViewResult object, containing all of the information needed to render a view.  This is the basic command pattern, and each derived ActionResult is a different command.  There are quite a few subclasses of ActionResult, each representing a different command for the MVC pipeline:

  • JsonResult
  • ViewResult
  • RedirectResult
  • FileResult
  • etc

If you want custom processing of the result of an action, a custom ActionResult is what you’re looking for.  We have quite a few in our applications, including:

  • Streaming a CSV version of the current screen
  • Executing a domain command
  • Executing a delete

And a few more.  While the abstraction of a parameter object to represent a command is a fantastic idea, the design of the ActionResult type is hindered by too many responsibilities.  Namely, an ActionResult is responsible for:

  • Representing the parameter object of a command
  • Executing the command

It’s that second responsibility that can hinder some more interesting scenarios.  Because the controller action needs to instantiate a parameter object as the result of an action, I’m often reduced to sub-optimal service location to do the actual work of the executing action.  For example, the delete action result might look something like:

public class DeleteRequestResult<TModel> : ActionResult
{
    private readonly TModel _model;
    private readonly Func<ActionResult> _successRedirect;

    public DeleteRequestResult(TModel model, Func<ActionResult> successRedirect)
    {
        _model = model;
        _successRedirect = successRedirect;
    }

    public TModel Model { get { return _model; } }
    public Func<ActionResult> SuccessRedirect { get { return _successRedirect; } }
    
    public override void ExecuteResult(ControllerContext context)
    {
        // Service location, boooooo!
        var repository = IoC.GetInstance<IRepository<TModel>>();
        var logger = IoC.GetInstance<IDomainLogger>();

        repository.Delete(Model);
        logger.LogDelete(Model);

        var redirectResult = SuccessRedirect();

        redirectResult.ExecuteResult(context);
    }
}

I have to resort to service location (blech) to load up the right dependencies to do the actual work of processing a delete request.  However, instead of relying on the ActionResult to be responsible for executing the command, I’ll instead allow something else take over that responsibility.

Separating the ActionResult concerns

First, I need to define an abstraction of something that can execute an ActionResult.  That sounds like an IActionResultInvoker to me:

public interface IActionResultInvoker<TActionResult>
    where TActionResult : ActionResult
{
    void ExecuteResult(TActionResult actionResult, ControllerContext context);
}

The signature is very similar to ActionResult, but this time, it’s a separate object that receives the parameter object (the ActionResult), the ControllerContext, and actually performs the work.  Next, I need to now direct the default action invoker to invoke action results through my abstraction, instead of going through the regular action result itself.  However, because all of the built-in action results aren’t going to change, I need to handle that scenario.  To accomplish this, I’ll create a new ActionResult type:

public abstract class BetterActionResult : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        throw new NotImplementedException("Should be executed with an IActionResultInvoker");
    }
}

That should be enough to provide some notification when we’re developing if we haven’t hooked things up correctly.  Next, I’ll need the default action invoker overrides:

public class IoCActionInvoker : ControllerActionInvoker
{
    private readonly IContainer _container;

    public IoCActionInvoker(IContainer container)
    {
        _container = container;
    }

    protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
    {
        if (actionResult is BetterActionResult)
        {
            // Close the IActionResultInvoker<> open generic type
            var actionResultInvokerType = typeof (IActionResultInvoker<>).MakeGenericType(actionResult.GetType());

            // Get the invoker from the container
            var actionResultInvoker = _container.GetInstance(actionResultInvokerType);

            // Get the generic ExecuteResult method
            var executeResultMethod = actionResultInvokerType.GetMethod("ExecuteResult");

            // Call the ExecuteResult method
            executeResultMethod.Invoke(actionResultInvoker, new object[] { actionResult, controllerContext });
        }
        else
        {
            base.InvokeActionResult(controllerContext, actionResult);
        }
    }
}

It’s a lot of code, but what I’m basically doing here is looking for derived BetterActionResult action results, and using my StructureMap container to load up the correct IActionResultInvoker for that derived ActionResult type.  In the example above, I use just basic reflection for calling the ExecuteResult method, but in practice, I’ll use caching and an optimized mechanism for reflection.

###

Connecting the action invoker to the MVC pipeline

Now, I could have used a looser-typed signature for IActionResultInvoker, and not needed to do this reflection business.  However, it’s critical that I load up the correct IActionResultInvoker for my derived BetterActionResult.  Using generics is a great way to use the type system for routing to the correct handler.  To replace the existing action invoker, I have a couple of choices.  One simple way of doing so is to modify the controller factory (that I’m already overriding for using IoC to instantiate my controllers):

public class ControllerFactory : DefaultControllerFactory
{
    private readonly IContainer _container;

    public ControllerFactory(IContainer container)
    {
        _container = container;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        if (controllerType == null)
            return null;

        var controller = (Controller)_container.GetInstance(controllerType);

        controller.ActionInvoker = _container.GetInstance<IoCActionInvoker>();

        return controller;
    }
}

My custom controller factory is already hooked up using a technique like this one, I don’t have to do anything extra to make sure my action invoker gets executed.  I just overrode the method I needed, hooked up the action invoker on controller construction time, and I’m ready to go.

The next piece is to actually define my custom action invoker, and refactor my existing DeleteRequestResult type.

Defining a new invoker and action result

First, I’ll need a new ActionResult type for my original representation of a delete request.  This time, I’ll only have the parameters needed to perform the action, and my action invoker will take care of the rest.  The custom action result type now becomes very simple:

public class DeleteRequestResult<TModel> : BetterActionResult
{
    public DeleteRequestResult(TModel model, Func<ActionResult> successRedirect)
    {
        Model = model;
        SuccessRedirect = successRedirect;
    }

    public TModel Model { get; private set; }
    public Func<ActionResult> SuccessRedirect { get; private set; }
}

I’m no longer mixing the concerns of parameter object and command execution any more.  Instead, I’ll define a new custom action invoker:

public class DeleteRequestResultInvoker<TModel> 
    : IActionResultInvoker<DeleteRequestResult<TModel>>
{
    private readonly IRepository<TModel> _repository;
    private readonly IDomainLogger _logger;

    public DeleteRequestResultInvoker(IRepository<TModel> repository, IDomainLogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public void ExecuteResult(DeleteRequestResult<TModel> actionResult, ControllerContext context)
    {
        _repository.Delete(actionResult.Model);
        _logger.LogDelete(actionResult.Model);

        var redirectResult = actionResult.SuccessRedirect();

        redirectResult.ExecuteResult(context);
    }
}

Now, when my IoCActionInvoker looks for an action invoker for DeleteRequestResult, it will find my custom action invoker, instantiate it with IoC, and use its ExecuteResult method.  The controller action becomes pretty small now:

public DeleteRequestResult<Product> Delete(Product productId)
{
    return new DeleteRequestResult<Product>(productId, () => RedirectToAction("Index"));
}

When I go to test this action now, I only need to make sure that the product supplied is correct and the success redirect is correct.  Because deletions don’t really change in our domain, we abstracted deletions into a separate ActionResult and IActionResultInvoker.

Wrapping it up

The piece I didn’t show here is how to hook up your IoC container to tell it that when it looks for an IActionResultInvoker<DeleteRequestResult>, it loads up the DeleteRequestResultInvoker.  That code is different for every container, so I’ll go into that piece in a future post.

The interesting part about this pattern is that I’m able to separate the concerns of a parameter object representing the results of an action, and the actual processing of that parameter object.  These two concerns usually have different reasons for change, and I’d rather not rely on things like service location for opaque dependency resolution in the execution of the ActionResult.  Because our IoC registration discovers new invokers quite easily, I never really need to add any new code for new action result invokers.  I merely need to create a new BetterActionResult type, define an invoker, and my IoC container does the rest.

The custom ActionResult made it easy to represent common execution patterns, in a way that doesn’t pollute my controllers with duplication and enables much easier testing.  Just as I don’t test a ViewResult for the contents of a view, in my example, I just make sure the custom ActionResult gets the correct parameters.  While many areas in MVC are designed for IoC scenarios in mind, others are not.  However, with some creative changes, we can enable places like ActionResult execution pipeline for powerful IoC scenarios.

InfoQ interview on ASP.NET MVC in Action up