This project is read-only.
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:

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

Step  Stage                 Description
1     BeginRequest          This occurs at the start of the navigation request before the controller has been resolved.
2     ResolvingController   This indicates that the controller is about to be resolved by name from the current controller factory.
3     ResolvingAction       This indicates that the controller has been resolved, and the action is now about to be resolved.
4     PreActionFilters      This indicates that the action has been resolved, and pre-action filters are about to be invoked.
5     ExecutingAction       This 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     PostActionFilters     This 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     PreResultFilters      This 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     ExecutingResult       This 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     PostResultFilters     This indicates that the result has been executed (and views have been rendered) and the post-result filters are about to be executed.
10    Complete              This 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.

Last edited Aug 22, 2010 at 3:42 PM by stovellp, version 2

Comments

No comments yet.