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.

Back to: Magellan Home

In web applications, requests are initiated from the web browser, and are sent to a web server for processing. In smart clients, the application is responsible for both initiating the request and processing it. To this end, Magellan provides a Navigator class and INavigator interface

(Work in progress)

Back to: Magellan Home

Back to: Magellan Home

The web typically uses hyperlinks and postbacks to navigate between pages. With Magellan, we can use either a command, C# code, or using an Expression Blend Behavior.

When you reference the Magellan assemblies, the NavigateBehavior will appear in the Assets tab in Blend 3. You can drag and position it onto any UI element:

Navigate behavior

You can use the properties of the behavior to specify the values. The navigation behavior also allows parameters to be passed to the action, using the Parameters collection button.

In XAML, the behavior will appear as shown below:

<Button Content="Calculate">
    <i:Interaction.Behaviors>
        <NavigateBehavior Controller="Home" Action="Add">
            <NavigateBehavior.Parameters>
                <Parameter ParameterName="left" Value="{Binding Path=Left}" />
                <Parameter ParameterName="right" Value="{Binding Path=Right}" />
            </NavigateBehavior.Parameters>
        </NavigateBehavior>
    </i:Interaction.Behaviors>
</Button>

The parameters passed to the behavior should match the method arguments on the controller action - see the controller section for details. Note that the parameters support data binding - this allows you to pass properties from the model or other UI elements automatically.

See also:

Back to: Magellan Home

Back to: Magellan Home

The web typically uses hyperlinks and postbacks to navigate between pages. With Magellan, we can use either C# code, Expression Blend Behaviors, or using a command.

The command option works especially well for Magellan applications that use the MVVM pattern. Below is an example controller:

public class CustomerController : Controller 
{
    public ActionResult Show(int customerId)
    {
        Model = new CustomerDetailsModel(GetCustomer(customerId));
        return Page();
    }

    public ActionResult Delete(int customerId)
    {
        DeleteCustomer(customerId);
        return Redirect("Home");
    }
}

The CustomerDetailsModel may look like this:

public class CustomerDetailsModel
{
    public CustomerDetailsModel(Customer customer) 
    {
        Customer = customer;
        DeleteCustomer = new NavigateCommand("Customers", "Delete", new { customerId = customer.Id });
    }

    public ICommand DeleteCustomer { get; private set; }
    public Customer Customer { get; private set; } 
}

The view can then simply bind the command to the UI elements:

<Button Content="Delete" Command="{Binding Path=DeleteCustomer}" />

Most MVVM frameworks also provide a RelayCommand or DelegateCommand or similar that allows you to pass a callback. This could also be used with Magellan; for example:

DeleteCommand = new DelegateCommand<Customer>(
    customer => Navigator.Default.Navigate("Customers", "Delete", new { customerId = customer.Id }
    );

See also:

Back to: Magellan Home

Back to: Magellan Home

The MVVM Light Toolkit is an MVVM framework by WPF MVP Laurent Bugnion, the author of Silverlight 2 Unleashed. It works well alongside Magellan and makes it easy to put behaviors behind views. The integration model with Magellan is quite similar to using the Microsoft MVVM toolkit.

One difference is that the MVVM Light Toolkit typically uses resources to refer to the ViewModel, limiting the amount of code behind that is required. Since Magellan controllers typically create the model, this does need to change.

As with using the Microsoft MVVM toolkit, we use the controller to create the ViewModel:

public class HomeController : Controller
{
    public ActionResult Main()
    {
        Model = new MainViewModel();
        return Page();
    }
}

MVVM Light views typically use a locator for creating the view model - this is a problem because our controller above will be creating the VM, and Magellan is taking care of assigning it.

<Window x:Class="MvvmLight1.MvvmView1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    DataContext="{Binding Main, Source={StaticResource Locator}}"
    >

To keep Blend support, change the XAML to use d:DataContext instead of DataContext.

<Window x:Class="MvvmLight1.MvvmView1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModel="clr-namespace:MvvmLight1.ViewModel" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    d:DataContext="{x:Type ViewModel:MainViewModel}"
    >

Back to: Magellan Home

Back to: Magellan Home

Magellan's MVC framework is designed to handle navigation between views. However, the views themselves, and how they are implemented, is outside of Magellan's concern. Views can be simple XAML pages, or they can be driven by an MVVM or MVP pattern, or any other way you might like.

Microsoft provide a Visual Studio project template known as the MVVM Toolkit, which makes it easy to get started using the MVVM pattern.

To use Magellan with the MVVM toolkit, you can set up a new MVVM project using the MVVM project template. Then configure it the same way as described in the Magellan quickstart.

By default with the MVVM toolkit, you typically create the ViewModel and View and assign them yourself, as shown in the project template:

Views.MainView view = new Views.MainView();
view.DataContext = new ViewModels.MainViewModel();
view.Show();

Instead, with Magellan, you can assign the controller's Model property to you ViewModel:

public class HomeController : Controller
{
    public ActionResult Main()
    {
        Model = new MainViewModel();
        return Page();
    }
}

The view engines will create the page or Window, and set the DataContext to the Model for you. It will then navigate to the page or show the Window.

Your ViewModels can make use of commands, event managers, data binding, and all the other common MVVM patterns. MVVM would typically handle:

  • Local interaction with the view
  • Validation
  • Control state, such as whether a button should be enabled based on data

While Magellan would be used for:

  • Navigating to another page or window

A good way to think about this is to think in terms of the web. On the web, JavaScript is typically the "view model" - it handles the logic for a particular page. The server processes requests for many pages and navigation between pages - that's the job of Magellan.

There is also a NavigateCommand that can be used in ViewModels, instead of the need to use a DelegateCommand/RelayCommand for common navigation events.

Back to: Magellan Home

Back to: Magellan Home

Magellan was designed to work with Composite WPF from day one. Composite WPF provides support for multiple modules, loosely coupled pub/sub eventing, and regions for sub-dividing zones in the UI. However, Composite WPF does not enforce any particular UI pattern - MVVM, MVP and MVC could all work.

Magellan and Composite WPF can work well together to create a composite navigation-oriented application using the MVC pattern. Here are some examples:

  • Composite WPF modules could contain views and controllers
  • Composite WPF events could be used for navigation - i.e., a Navigate event that could be raised by different modules and services
  • Instead of pages, Magellan view results could return UserControls that are added to regions

Region Support

For region support, Magellan.Composite.dll contains some extensions that can be used. A controller may look like this:

public class ShellController : CompositeController 
{
    public ActionResult Explorer()
    {
        return CompositeView("Explorer").InRegion("LeftRegion");
    }
} 

On application startup, the view can be navigated to via:

Navigator.Primary.Navigate("Shell", "Explorer");

Lastly, an additional Region View Engine needs to be registered. As discussed in the IOC topic, you can also use a custom view activator to control how views are instantiated, if you want to use IOC. In this case we'll use the Microsoft common ServiceLocator:

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new PageViewEngine(new ServiceLocatorViewActivator()));
ViewEngines.Engines.Add(new WindowViewEngine(new ServiceLocatorViewActivator()));
ViewEngines.Engines.Add(new CompositeViewEngine(new ServiceLocatorViewActivator()));

When the InRegion extension method is used, and the view class derives from UIElement, the region view engine will use the service locator to resolve the default RegionManager, and then add the view to the region.

Controller Factory

There is also a new controller factory that can be used with the Common Service Locator. It will automatically back onto ServiceLocator.Current to resolve controllers, so you just have to register them in the container:

ControllerBuilder.Current.SetControllerFactory(new ServiceLocatorControllerFactory());

See also:

Back to: Magellan Home

Back to: Magellan Home

Magellan was designed to work without an Inversion of Control or Dependency Injection container, to keep it simple and accessible. However, as applications become more complicated, modern WPF applications can benefit immensely from IOC containers.

Magellan's extensibility points make using a container easy. Magellan resolves and instantiates two main types of objects - controllers and views. The out of the box implementation uses conventions, but they can be overridden.

To take control of resolving controllers, implement the IControllerFactory interface. Here is an example using Ninject:

public class NinjectControllerFactory : IControllerFactory
{
    private readonly IKernel _container;

    public NinjectControllerFactory(IKernel container)
    {
        _container = container;
    }

    public IController CreateController(NavigationRequest request, string controllerName)
    {
        var controller = _container.Get<IController>(controllerName);
        return new ControllerFactoryResult(controller);
    }
}

The ControllerFactoryResult has a second overload that also allows you to pass a callback that is invoked when the request has been processed - this can be used for disposing the controller if required.

Then assign it as the default controller factory:

var container = new StandardKernel();
container.Bind<IController>().To<HomeController>().Named("Home");
container.Bind<IController>().To<SettingsController>().Named("Settings");
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory(container));

When a navigation request is processed, the Navigator will consult the ControllerBuilder.Current for the controller factory. It will use this to ask the factory for a controller, then execute the controller. The ReleaseController method will be called as soon as the navigation request has completed.

Typically, each navigation will create a new controller - if you want to use the same controller, you could have your controller factory recycle the objects, or make them singletons.

The second object that Magellan creates is views. This is done through the IViewActivator implementation. The default implementation looks for a public, parameterless constructor.

Again, let's use Ninject to control view instantiation.

public class NinjectViewActivator : IViewActivator
{
    private readonly IKernel _container;

    public NinjectViewActivator(IKernel container)
    {
        _container = container;
    }

    public object Instantiate(Type viewType)
    {
        return _container.Get(viewType);        
    }
}

We then need to tell the view engines that it is available. Since the view engines are automatically registered, we need to manually re-register them:

var activator = new NinjectViewActivator(container);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new PageViewEngine(activator));
ViewEngines.Engines.Add(new WindowViewEngine(activator));

If you are using Magellan with Composite WPF, you will also need to register the region view engine:

ViewEngines.Engines.Add(new CompositeViewEngine(activator));

Back to: Magellan Home

Back to: Magellan Home

This guide will walk you through getting started with Magellan. You will need a copy of Visual Studio 2008 with Service Pack 1.

Project Setup

  1. Download the Magellan library. Unzip it to a known location.
  2. Create a new WPF Application project using Visual Studio 2008.
  3. Add references to Magellan.dll and System.Windows.Interactivity.dll from the ZIP file that you downloaded.
  4. Create the following folder structure:

A new VS project, ready for Magellan

Create a model

In the Views/Home folder, add a class named AddModel. This will represent the view model of an addition action:

public class AddModel
{
    public int A { get; set; }
    public int B { get; set; }
    public int Result { get; set; }
}

Create a controller

Create a new class in the Controllers folder named HomeController. It will contain two actions - Index, which will be the input page, and Add, which will show the results:

using Magellan.Framework;

namespace MagellanHelloWorld.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return Page();
        }

        public ActionResult Add(int a, int b)
        {
            Model = new AddModel
            {
                A = a,
                B = b,
                Result = a + b
            };
            return Page();
        }
    }
}

Now wire up the controller in App.xaml.cs:

using System.Windows;
using Magellan.Framework;
using MagellanHelloWorld.Controllers;

namespace MagellanHelloWorld
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            var controllerFactory = new ControllerFactory();
            controllerFactory.Register("Home", () => new HomeController());
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);

            base.OnStartup(e);
        }
    }
}

Set up the shell

In Window1.xaml, add a Frame element to the content:

<Window 
    x:Class="MagellanHelloWorld.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    >
    <Grid>
        <Frame Name="mainFrame" />
    </Grid>
</Window>

In the Window1 constructor, navigate the mainFrame to the Index action on your controller.

using System.Windows;
using Magellan;

namespace MagellanHelloWorld
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Navigator.For(mainFrame).Navigate("Home", "Index");
        }
    }
}

Create the views

In the Views/Home folder, create two new Page files: Index.xaml and Add.xaml.

Index.xaml will be used to capture the inputs, and send them to the Add action on the controller. Notice how the Parameters are defined as part of the Navigation behavior:

<Page 
    x:Class="MagellanHelloWorld.Views.Home.Index"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:magellan="http://xamlforge.com/magellan" 
    Title="Index"
    >
    <StackPanel>
        <TextBox Name="parameterA" />
        <TextBlock Text="+" />
        <TextBox Name="parameterB" />

        <Button Content="Calculate">
            <i:Interaction.Behaviors>
                <NavigateBehavior Controller="Home" Action="Add">
                    <NavigateBehavior.Parameters>
                        <Parameter ParameterName="a" Value="{Binding ElementName=parameterA, Path=Text}" />
                        <Parameter ParameterName="b" Value="{Binding ElementName=parameterB, Path=Text}" />
                    </NavigateBehavior.Parameters>
                </NavigateBehavior>
            </i:Interaction.Behaviors>
        </Button>
    </StackPanel>
</Page>

The Add.xaml will show the output of the calculation. Note how it assumes the use of DataContext in the bindings.

<Page 
    x:Class="MagellanHelloWorld.Views.Home.Add"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    Title="Add"
    >
    <StackPanel>
        <WrapPanel>
            <TextBlock Text="{Binding Path=A}" />
            <TextBlock Text="+" />
            <TextBlock Text="{Binding Path=B}" />
            <TextBlock Text="=" />
            <TextBlock Text="{Binding Path=Result}" />
        </WrapPanel>

        <Button Content="Try Again">
            <i:Interaction.Behaviors>
                <NavigateBehavior Controller="Home" Action="Index" />
            </i:Interaction.Behaviors>
        </Button>
    </StackPanel>
</Page>

The Magellan view engine will take care of setting the Page's DataContext to the Model from the controller.

Done

That's all there is to it - just hit F5 and admire the results of your handywork! At this point you should be able to enter the values, and they will appear on the next page. Wasn't that easy?

Index.xaml Add.xaml

Magellan took care of:

  • Locating the controller
  • Invoking the actions
  • Mapping the strings from the text boxes to integer parameters on the action (using Model Binders)
  • Locating the view
  • Connecting the model with the view

Tests

Of course, it wouldn't be complete without a test. Here's what the unit test for our Add action might look like:

[Test]
public void AddTest()
{
    var controller = new HomeController();
    controller.Add(1, 4);
    var model = (AddModel)controller.Model;

    Assert.AreEqual(1, model.A);
    Assert.AreEqual(4, model.B);
    Assert.AreEqual(5, model.Result);
}

What next?

Back to: Magellan Home

Magellan is now hosted on Google Code

Magellan is a lightweight MVC framework that makes it easy to build WPF navigation applications. The design is drawn deeply from the ASP.NET MVC framework, and it should feel familiar to anyone who has worked with ASP.NET MVC.

Back to: Magellan Home

Magellan is a lightweight framework that makes it easy to build WPF navigation applications. It is inspired by the ASP.NET MVC framework. The main features are:

  • Model-View-Controller support
  • Action filters for cross-cutting concerns such as authorization and redirection
  • Blend behaviors to make navigation easy
  • Transitions between pages

Magellan was drawn from a number of samples I had put together early this year and some work done on a client project.

The source download includes an "iPhone" application for demonstrating the features.

The sample iPhone application

We start with a simple project structure:

A VS2008 project with a number of folders for controllers, models and views

A controller implementation typically looks like this:

public class PhoneController : Controller
{
    public ActionResult Group(Group group)
    {
        var contacts = _contactRepository.GetContacts(group);

        Model = new GroupViewModel(group.Name, contacts);
        return Page();
    }

Views are XAML Page objects, and can optionally have a model. Here's an example:

View models

The idea is that upon navigation, a controller is created, the action is executed, and the view and view model are created. The view then becomes the focus of the frame. Put simply, the view and viewmodel are stateful, and the controller is stateless.

Navigation between views (with nice transitions) can be done either programatically:

Navigator.For(Frame).NavigateWithTransition("Home", "Main", "ZoomOut");

Or through Blend behaviors:

Blend Navigate behavior

The framework supports the ASP.NET MVC concepts of Action Filters, Model Binders, View Engines and more - I'll cover them in a later post.

Back to: Magellan Home