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:
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.
Back to: Magellan Home