Magellan Asynchronous Controllers

Back to: Magellan Home

Magellan 1.1 brings support for asynchronous controllers. First we'll look at what it enables, then I will show some ways to configure it.

The controller action below makes a WCF call to load a list of customers, which are used as the model for the view. This service call could take some time to evaluate:

public class CustomerController : Controller
{
    public ActionResult List()
    {
        Model = CustomerService.GetCustomers();
        return Page("CustomerList");
    }
}

With asynchronous controllers enabled, Magellan will invoke the List action on a background thread. Since page navigation has to take place on the UI thread, the page rendering will be automatically dispatched to the UI thread, but this happens after the controller action has been executed. The result is that the UI remains snappy and responsive.

Enabling Asynchronous Controllers

There are three simple options for enabling asynchronous controllers. The first option is to inherit from AsyncController instead of Controller:

public class CustomerController : AsyncController

The second option is to assign the AsyncActionInvoker when the controller is constructed (this is actually what both AsyncControllerFactory and AsyncController do):

public class CustomerController : Controller
{
    public CustomerController() 
    { 
        ActionInvoker = new AsyncActionInvoker();
    }
}

The preferred approach is to rely on controller factories to set the action invoker. Instead of using CoontrollerFactory, you can use AsyncControllerFactory:

var controllerFactory = new AsyncControllerFactory();
controllerFactory.Register("Home", () => new HomeController());
controllerFactory.Register("Customer", () => new CustomerController());

If you are using a custom controller factory, you just need to replace the ActionInvoker - for example:

public ControllerFactoryResult CreateController(NavigationRequest request, string controllerName)
{
    var controller = // Create controller
    if (controller is ControllerBase)
    {
        ((ControllerBase) controller).ActionInvoker = new AsyncActionInvoker();
    }
    return new ControllerFactoryResult(controller);
}

Reporting Progress

Now that navigation is occurring on a background thread, it's nice to show a progress indicator while navigation is happening. For this, Magellan now provides an INavigationProgressListener interface that you can implement.

public interface INavigationProgressListener
{
    void UpdateProgress(NavigationRequest request, NavigationStage navigationStage);
}

For example, the code behind for the Window is going to show a progress bar:

public partial class MainWindow : Window, INavigationProgressListener
{
    public MainWindow()
    {
        InitializeComponent();
        NavigationProgress.Listeners.Add(this);
        Loaded += (x, y) => Navigator.For(Frame).NavigateWithTransition("Home", "Home", "ZoomIn");
    }

    public void UpdateProgress(NavigationRequest request, NavigationStage navigationStage)
    {
        Dispatcher.Invoke(
            new Action(delegate
            {
                switch (navigationStage)
                {
                    case NavigationStage.BeginRequest:
                        ProgressBar.Visibility = Visibility.Visible;
                        break;
                    case NavigationStage.Complete:
                        ProgressBar.Visibility = Visibility.Collapsed;
                        break;
                }
            }));
    }
}

The iPhone sample application uses a spinning circle to indicate navigation progress:

A spinning indicator is used

The NavigationStage enum provides an indication as to where the request is up to. The table below explains each state:

StepStageDescription
1 BeginRequestThis occurs at the start of the navigation request before the controller has been resolved.
2 ResolvingControllerThis indicates that the controller is about to be resolved by name from the current controller factory.
3 ResolvingActionThis indicates that the controller has been resolved, and the action is now about to be resolved.
4 PreActionFiltersThis indicates that the action has been resolved, and pre-action filters are about to be invoked.
5 ExecutingActionThis indicates that pre-action filters have been invoked, and the action is about to to executed. This event does not always occur (pre-action filters can cancel the navigation request, for example).
6 PostActionFiltersThis indicates that the action has been invoked, and post-action filters are about to to executed. This event does not always occur (pre-action filters can cancel the navigation request, for example).
7 PreResultFiltersThis indicates that the action has been executed and all action filters have been invoked. The result is now about to be evaluated. This event does not always occur (action filters can cancel the navigation request, for example).
8 ExecutingResultThis indicates that the pre-result filters have been executed, and the result is about to be executed (this is typically when views are rendered). This event does not always occur (action filters or pre-action filters can cancel the navigation request, for example).
9 PostResultFiltersThis indicates that the result has been executed (and views have been rendered) and the post-result filters are about to be executed.
10 CompleteThis indicates that the navigation request has been completed (whether successfully or failed), any views have been rendered and any resources from the navigation request have been cleared up.

Summary

The benefit of this approach is that controller code is the same whether we are using single threads or background threads, which also allows unit tests to remain singly threaded while at runtime the application is multi-threaded. Enabling this feature is quite easy, and so long as your controllers don't depend on the UI thread it should Just Work.

Back to: Magellan Home

A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on 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.