Magellan Controllers

Back to: Magellan Home

As an MVC framework, controllers are the most prominent object in Magellan. At their simplest, controllers are implemented as classes, and actions are implemented as methods on the class. Actions on a controller must be public methods, and must return ActionResult objects. Here is an example:

public class CustomerController : Controller
{
    public ActionResult Index()
    {
        Model = Customers.GetAll();
        return Page();
    }

    public ActionResult Show(int customerId) 
    {
        Model = Customers.Get(customerId);
        return Page();
    }
}

This page describes how controllers are executed.

Controller Factories

Navigation begins at the Navigator, either by using a command, behavior or calling it explicitly. The Navigator processes the request by resolving a controller from the controller factory, executing the request, and releasing the controller back to the factory:

public void Navigate(NavigationRequest request)
{
    Guard.ArgumentNotNull(request, "request");

    var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
    var controllerResult = controllerFactory.CreateController(request, request.Controller);
    var controller = controllerResult.Controller;
    var context = new ControllerContext(controller, request, controllerResult.Release);
    controller.Execute(context);
}

The controller factory implements the IControllerFactory interface, and provides a method to resolve the controller:

public interface IControllerFactory
{
    ControllerFactoryResult CreateController(NavigationRequest request, string controllerName);
}

The controller factory is set via the ControllerBuilder.Current.SetControllerFactory method. See the topic on using an IOC container for an example on creating your own controller factory.

Controllers

The controllers returned by the controller factory must implement the IController interface, which is quite simple:

public interface IController
{
    void Execute(NavigationRequest request);
}

The NavigationRequest passed to the controller contains information about the controller name, action name, and any parameters for the request. It is up to the controller to decide how to handle the request given this information. If the class/method convention used by Magellan isn't enough, your controller can use different mechanisms to prosecute the request. Magellan's navigator, behaviors, and commands can still be used.

Controllers will usually derive from the Controller base class. When this controller executes a request, it will work with an action invoker to resolve the action, call any action filters, execute the action, and evaluate the result.

The Execute method on the Controller base class is quite simple:

public void Execute(NavigationRequest request)
{
    Request = request;
    ActionInvoker.ExecuteAction(ControllerContext, request.Action, ModelBinders);
}

To make writing controllers easy, a number of helper methods exist for creating the action results, such as View(), Cancel() and Redirect(). You can read more about them in the action results section.

Action Invoker

The action invoker is used by the controller to handle the details of executing the action. This allows you to continue to derive from the Controller class while executing actions in a very different way. By default, the controller's ActionInvoker property is set to the DefaultActionInvoker.

The default action invoker uses reflection to map the requests Action property to a method on the controller. It then performs the following steps:

  1. Executes all pre-action filters
  2. Executes the action
  3. Executes all post-action filters
  4. Executes the action result

Action filters provide a way for you to implement cross-cutting concerns on controllers, such as authorization, logging, state management, and other examples.

When the action has been executed, it will return an Action Result. This is an important point - controllers do not show views directly, they simply return an object which is responsible for resolving and showing the view. This allows you handle the navigation request in a very different way without altering controllers.

Model Binders

One of the tasks the action invoker takes care of is mapping parameters from the current NavigationRequest to arguments passed to the action method on the controller. Rather than putting this logic in the action invoker, it instead calls through to objects known as Model Binders.

The ModelBinders.Binders static property is a registry of active model binders, associated with a type. By default there is only one binder - a DefaultModelBinder which uses type descriptors and casting to perform the conversion. If you wish to customize how request parameters are mapped to action method parameters, you can do so through implementing the IModelBinder interface and registering it with the ModelBinders.Binders collection.

See also:

Back to: Magellan Home

A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on Octopus Deploy Octopus Deploy, an automated deployment tool for .NET applications.

Prior to founding Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm. I also worked on a number of open source projects and was an active user group presenter. I was a Microsoft MVP for WPF from 2006 to 2013.

11 Nov 2009

Hi Paul,

Can you elaborate on the reason for a ReleaseController method, as opposed to having IController "derive" from IDisposable and doing something like this?

public void Navigate(NavigationRequest request)
{
    Guard.ArgumentNotNull(request, "request");

    var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
    using (var controller = controllerFactory.CreateController(request, request.Controller))
    {
        controller.Execute(request);
    }
}
11 Nov 2009

Great question Matt.

It's the same design principle that ASP.NET MVC follows. IDisposable is one way you might like to clean up after a controller. But there could be other things you want to do, like:

  • The controller might not be disposable, but you might create a Transaction/Session around the controller request (with NHibernate, etc) - release is the point where you would do that.
  • There might be other assets you need to release when the controller is finished.
  • Instead of creating new controllers every time, you may decide this is expensive and to pool them instead. In this case, release controller would release back to the pool rather than disposing.
  • Maybe your controllers are IDisposable, but they are also Singletons - you can't dispose them first time, but you plan to dispose them at some point.
  • You might use something like Ninject Activation Blocks, which you want to dispose of at the end of the request to free anything that was allocated.

So ReleaseController provides a more robust way of handling this. This is what the default implementation of IControllerFactory looks like:

public void ReleaseController(IController controller)
{
    var disposable = controller as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose();
    }
}

Hope that explains it,

Paul

11 Nov 2009

I guess it's also just a good rule that since I didn't allocate the controller, I also shouldn't be disposing it.

30 Dec 2009

Update: The comments above refer to an older version of the API in which controller factories also had a ReleaseController method. The API has changed such that the ControllerFactoryResult now takes a delegate which can be used to release the component. It's a minor change, but I'm hoping this will make it easier to write custom controller factories.