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.

Progress on Magellan has been a little slow this week as I have been experimenting with some ways to optimize memory usage when dealing with WPF pages. A lot of it comes down to issues with WPF's navigation system and some of the limitations it has.

The major problem we encounter when using WPF Page objects is that if you were to do something like this:

NavigationService.Navigate(new MyPage());

You just got yourself an object that will never be garbage collected.

This happens because the WPF navigation journal has to have a way of returning 'back' to that page if the user clicks a back button. Since you created it, the navigation service has no way of knowing how to re-create it, so it just keeps a reference to it. The only way of clearing these out is to call NavigationService.RemoveBackEntry a to remove the page from the navigation journal completely.

Using URI's

So the solution is to use URI's for navigation:

NavigationService.Navigate(new Uri("/MyPage.xaml", UriKind.Relative));

Now when the WPF navigation service adds the page to the journal, it just stores the URI rather than keeping a reference to the instance of the page. Problem solved, right?

If you think so, you're about to learn about the stupidest feature that ever made it into WPF :)

Metadata Journaling

When most people think ViewState, they think ASP.NET web forms, and Notepad.exe screens full of encoded data taking up the top half of an ASPX page. They think of a confusing page lifecycle and a leaky abstraction that takes weeks to comprehend. Well, it turns out WPF has it's own version of ViewState - although it's only used when clicking "Back".

When URI's are used for Page navigation, the WPF navigation framework creates a journal entry with the URI. Then it walks the visual tree, and any dependency property that it encounters with the FrameworkPropertyMetadata.Journal flag will be serialized. Most properties don't have this flag however, so only small parts of the page are saved.

What this means is that if we use a Model-View-ViewModel pattern and set the DataContext on our page, when go 'Back', we'll no longer have a data context, since the DataContext property isn't serialized. All data will be missing from the page.

Using CustomContentState

A solution to this is to use WPF navigation's IProvideCustomContentState feature. This allows you to preserve information about the page before it is added to the journal, and to restore it when you return to the page. Here is an example:

public class PageView : Page, IProvideCustomContentState
{
    public PageView()
    {
    }

    public CustomContentState GetContentState()
    {
        return new RestoreModelContentState(DataContext);
    }
}

[Serializable]
internal class RestoreModelContentState : CustomContentState
{
    private readonly object _model;

    public RestoreModelContentState(object model)
    {
        _model = model;
    }

    public override void Replay(NavigationService navigationService, NavigationMode mode)
    {
        var element = navigationService.Content as FrameworkElement;
        if (element == null) return;
        element.DataContext = _model;
    }
}

Now, when you leave a page, the page will be garbage collected but not before the DataContext is moved into the journal. If you click 'Back' a new page will be created, and the DataContext will be restored.

The problem now, though, is that while our pages are being garbage collected, the DataContext's aren't. If we have a big model, that model will sit around forever unless we clear the navigation journal. This might be OK if your models aren't too big.

Solutions

Right now I'm at a point where I don't think there's a good clean solution for this that doesn't involve memory being wasted.

The cop-out solution seems to be to avoid the problem entirely by rolling back the journal at major points during the application, so that the journal never gets too big. A good example is to clear the journal after a major task completes, or when the user is taken back to the home page. If this were the case, the combination of CustomContentState to restore the model plus URI's could work.

I do have a very tricky solution in mind, which involves only using a single page with a query string in the URI, and having it re-invoke the controller when you click back. This way, models wouldn't need to be kept alive at all as the page would just be re-fetched. The other benefit to this is when you go back, the page would be up-to-date rather than an old copy of the page, which seems to suit the rich client model better. It would involve a breaking change (your views would have to become ContentControls or UserControls instead of Pages) but might have the best result overall.

Finally, I'm also considering throwing out WPF navigation completely and rolling my own. It wouldn't be tied to Magellan's MVC model specifically, but it would support scenarios that Magellan is trying to accommodate as well as getting around other WPF navigation limitations (like the stupid PageFunction Return event handler must be a Page limitation).

I'd love to get your thoughts on what you would expect from something like Magellan.

I am after opinions on an API change to Magellan.

When you write actions on a controller, you can return a ViewResult with the name of the view you want to show. For example:

public ActionResult Show(int customerId)
{
    var customer = repository.GetCustomer(customerId);
    Model = new CustomerDetailsModel(customerId);
    return View("CustomerDetails");
}

Magellan will then invoke a list of View Engines to track down the view. For example, it will look for:

  • CustomerDetails.xaml
  • CustomerDetailsWindow.xaml
  • CustomerDetailsPage.xaml
  • CustomerDetailsView.xaml

When it finds a match, it will use the view type to "render" the view. Windows will be shown via Window.Show(), pages will be navigated to via the NavigationService, and so on.

What this means from the Controllers point of view however is that the controller action can't be sure what kind of view will get shown; and therefore can't (easily) add view-specific arguments. For example, with a Window, it would be nice to state whether the Window should be shown as a modal dialog or as a non-modal window.

Now, the ViewResult class does store a dictionary of custom parameters that the view engines can use. So this means extension methods can easily be used to specify the settings above. For example:

return View("CustomerDetails").AsDialog();

What I don't like about this is we're mixing the generic concept of a view, with the specific concept of a dialog. Which makes the first kind of useless. So I'm considering changing the API's to use more specific results:

return Window("CustomerDetails");
return Dialog("CustomerDetails");
return Page("CustomerDetails");

Unit testing the controllers will be just as easy as they currently are, but it should make the controllers a little less ambiguous. What do you think?

Update: based on feedback I have gone with the seperate return actions instead of just View(). You'll find this in the latest build.

Back to: Magellan Home

Magellan now comes with a Visual Studio Project Template that you can use to get started with quickly. You will need Visual Studio 2008.

Magellan-Setup.msi

Setup

Magellan Installer

The installer will guide you through the installation process. During installation it will also register the Visual Studio project templates. If Visual Studio is already running, your will be prompted to close it at this time.

Once the installer finishes, your installation directory will contain the Magellan assemblies (in the Public directory), plus supporting files for the project template:

Explorer

Creating Projects

In Visual Studio under the Windows node, you will now have the ability to create a Magellan project:

New Project

Before the project is created, you will get a chance to decide whether to create unit tests, and how to handle the Magellan references. Magellan is not installed into the GAC. You can either reference the assemblies directly from the installation directory, which will mean other developers also need to install the MSI, or you can have the project wizard copy the assemblies locally (the default option):

New Project Prompt

When the project wizard has finished, you will have a new Magellan project - it should compile and run first try and the generated test should pass.

The Solution Explorer

You can also set up the same project manually without using the MSI. See the manual quickstart page for more information.

Back to: Magellan Home

Back to: Magellan Home

This page is meant to describe how Magellan and the WPF Starter Kit compare, and when either might be chosen.

I see Magellan as ASP.NET MVC, where the starter kit is more like the User Interface Process Application Block and ASP.NET Web Forms. The Starter Kit provides navigation via XML configuration and using the MVVM pattern, while Magellan uses MVC (plus MVVM or any other pattern/non-pattern you wish), and avoids configuration. More to come soon.

Back to: Magellan Home

Back to: Magellan Home

Magellan is all about navigation, an important part of which is showing views. In Magellan, a view can be a Window, Dialog or Page. With the Composite WPF extension, a view can also be any user control that can be added to a Region. The system is also extensible - see the page on Windows Forms support for an example on writing your own view engine.

View Results

As described previously, controller actions return an Action Results. These contain the logic for prosecuting the request. Derived from ActionResult are three view types:

Magellan ViewResult-derived classes

Each of the derived classes defer to the ViewResult base class to handle the execution. The internal execution code looks like this:

protected override void ExecuteInternal(ControllerContext controllerContext)
{
    ViewEngineResult = _viewEngines.FindView(controllerContext, ViewParameters, viewName);

    if (ViewEngineResult.Success)
    {
        ViewEngineResult.Render();
    }
    else
    {
        throw new ViewNotFoundException(...);
    }
}

_viewEngines is a collection of objects that implement the IViewEngine interface. The ViewParameters property provides additional information to the view engines, such as limitations on the type returned. Each of the derived types populate these - PageResult specifies that the view engines should only locate pages, WindowResult asks only for Windows, and so on.

View Engines

As we've seen above, View Results defer to View Engines to locate the view. As you can guess, View Engines are quite simple, though their job is a little complicated:

public interface IViewEngine
{
    ViewEngineResult FindView(
        ControllerContext controllerContext, 
        ParameterValueDictionary viewParameters, 
        string view);
}

When a View Engine figures out which view to show, it returns a custom object derived from ViewEngineResult, which encapsulates how to render the view. This way, the View Engine is only concerned with locating the view - the ViewEngineResult contains the actual logic for showing it.

Magellan comes with two View Engines that share a common base class:

The two Magellan view engines

Although there are three view results (PageResult, WindowResult and DialogResult), we only have two View Engines. This is because the logic of finding the view and the constraints are the same - the only difference is the call to Show() vs. ShowDialog(), which is rendering logic, not finding logic.

View Location

As shown in the diagram above, Magellan's View Engines derive from a ReflectionBasedViewEngine.

When your controller specifies something like this:

public ActionResult Home()
{
    return Page("Index");
}

The ReflectionBasedViewEngine will find all types in the assembly (you can use different assemblies by passing them in the view engine's constructor). It then passes them to the derived types to be filtered - for example, WindowViewEngine will restrict the list to only include types derived from the WPF Window class, and PageViewEngine will filter by the Page base class.

Once the ReflectionBasedViewEngine has a set of candidate types, it uses some rules to find the best view. The rules are based firstly on whether the names match, and secondly by namespace proximity, taking some conventions into account. The conventions are encapsulated in an IViewNamespaceProvider interface, which is also passed into the View Engine's constructor.

In the code above, we specified the "Index" view. Assuming our controllers are in a namespace called MyCompany.MyProduct.Controllers, and the controller is named HomeController, Magellan will look for these combinations of view names, via the DefaultViewNamespaceProvider, working through the list in order:

MyProject.Controllers.Views.Home.Index
MyProject.Controllers.Views.Home.IndexPage
MyProject.Controllers.Views.Home.IndexView
MyProject.Controllers.Views.Index
MyProject.Controllers.Views.IndexPage
MyProject.Controllers.Views.IndexView
MyProject.Controllers.Home.Index
MyProject.Controllers.Home.IndexPage
MyProject.Controllers.Home.IndexView
MyProject.Controllers.Index
MyProject.Controllers.IndexPage
MyProject.Controllers.IndexView
MyProject.Views.Home.Index
MyProject.Views.Home.IndexPage
MyProject.Views.Home.IndexView
MyProject.Views.Index
MyProject.Views.IndexPage
MyProject.Views.IndexView
MyProject.Home.Index
MyProject.Home.IndexPage
MyProject.Home.IndexView
MyProject.Index
MyProject.IndexPage
MyProject.IndexView
Index
IndexPage
IndexView

Hopefully the list above makes sense. Effectively, we're looking for the first view we can find as close to the controller as possible. Some conventions like a sub folder called "Views" are also taken into account.

If a view cannot be found, Magellan will provide a friendly exception. The screenshot below show what would happen if my controller mis-spelled the name of a view:

An exception thrown when Magellan cannot find a view. It also lists the set of searched locations.

To change the conventions, simply create a class implementing IViewNamespaceProvider, and pass it to the view engine:

ViewEngines.Engines.Add(new PageViewEngine(new DefaultViewActivator(), new CustomNamespaceProvider()));

See also:

Back to: Magellan Home

Back to: Magellan Home

Action Results are a concept from ASP.NET MVC and other MVC frameworks, and they are also supported by Magellan. They provide a powerful mechanism for decoupling the controller from UI specific concerns. Before discussing how they work, we should discuss why they exist.

Why Action Results?

Suppose you weren't using Magellan and you had an action like this:

public void Login(string username, string password) 
{
    var window = IsValidLogin(username, password)
        ? new WelcomeWindow();
        : new LoginWindow();
    window.Show();
}

This code is nice and small, but unfortunately it would be difficult to test. Your test would have to set up a WPF environment, call the controller method, and check to see which Window was opened.

To make the code more testable, you might inject a service that supports showing Windows:

public void Login(string username, string password) 
{
    if (IsValidLogin(username, password))
        _windowService.Show<WelcomeWindow>();
    else _windowService.Show<LoginWindow>();
}

This is a little more testable - your test can use a mocking library to provide a test version of the IWindowService, and you can test to see if it was called without necessarily creating any Windows at test time. Unfortunately, it forces you to do interaction based testing instead of state based testing. You can't just call the controller and verify the result; you have to set up a record/replay mock to see what the controller did. Mocks are great, but if you can avoid mocks, that's even better in my books.

To make controller actions more testable, Magellan uses ActionResults that allow controllers to declare their intentions, rather than actually doing anything. ActionResults are an example of the Command Pattern. At runtime Magellan takes care of executing them, but from a testing point of view, you can just interrogate them to see what the controller specified.

Here's a Magellan version of the action above:

public ActionResult Login(string username, string password) 
{
    return IsValidLogin(username, password)
        ? Window("Welcome")
        : Window("Login");
}

The tests would be as simple as:

[Test]
public void InvalidLoginShouldShowLoginWindow() 
{
    var controller = new MyController();
    var result = controller.Login("fred", "invalidPassword") as WindowViewResult;
    Assert.IsNotNull(result);
    Assert.AreEqual("Login", result.ViewName);
}

[Test]
public void ValidLoginShouldShowWelcomeWindow() 
{
    var controller = new MyController();
    var result = controller.Login("fred", "validPassword") as WindowViewResult;
    Assert.IsNotNull(result);
    Assert.AreEqual("Welcome", result.ViewName);
}

Could they be any simpler?

Types of Action Results

The ActionResult class is actually an abstract base class, with many derived types. The class is actually super simple:

public abstract class ActionResult
{
    public void Execute(ControllerContext controllerContext)
    {
        ExecuteInternal(controllerContext);
    }
    protected abstract void ExecuteInternal(ControllerContext controllerContext);
}

Derived from the ActionResult base class are:

  • ViewResult, which has some derived types of its own:
    • PageResult, for navigating to a WPF Page
    • WindowResult, for showing a WPF Window in non-modal state
    • DialogResult, for showing a WPF Window in a dialog state
  • BackResult, for sending a GoBack() request to the current navigation service
  • RedirectResult, for invoking a different action on a potentially different controller
  • StartProcessResult, which launches a process via Process.Start()
  • CancelResult, which does nothing

Controller Helpers

To use these derived types, you could simply write:

public ActionResult MyAction() 
{
    return new PageResult("MyPage");
}

However, the Magellan Controller base class defines a number of helpful methods for creating these results. They make the controller actions a little shorter and more declarative, for example:

public ActionResult MyAction() 
{
    return Page("MyPage");
}

Here are some examples of the methods in action. It should be easy to guess which type of ActionResult is in play:

return Page("MyPage");                  // Navigates to the page
return Page("MyPage", true);            // Navigates to the page, and removes all 'back' entries from the journal
return Window("MyWindow");              // Creates the Window and calls Window.Show()
return Dialog("MyWindow");              // Creates the Window and calls Window.ShowDialog();
return StartProcess("calc.exe");        // Launches calc.exe
return StartProcess("calc.exe", true);  // Launches calc.exe and waits for the user to close it
return Back();                          // Navigates back in the navigation service
return Back(true);                      // Navigates back and removes the page from the journal
return Redirect("MyAction");            // Executes MyAction and returns that instead
return Cancel();                        // Does nothing

The StartProcessResult is another good example to consider. If your controller actually called Process.Start(), how would you test it? With the result, you can just cast it to StartProcessResult and verify the properties to see what process the controller wanted to start.

The ViewResult derived classes share some logic in common for dealing with View Engines, which get their own section.

Rolling your Own

Because the ActionResult base class is so simple, rolling your own is easy. Let's take a MessageBox for example. Here's what our controller might look like:

public class MyController : CustomController
{
    public ActionResult SayHello()
    {
        return MessageBox("Hello world!");
    }
}

A test for such a controller can look like this:

[Test]
public void ShouldShowMessageBox()
{
    var controller = new MyController();
    var result = controller.SayHello() as MessageBoxResult;
    Assert.IsNotNull(result);
    Assert.AreEqual("Hello world!", result.Message);
}

We probably want to create a base class for our controllers with the useful helpers:

public class CustomController : Controller
{
    public MessageBoxResult MessageBox(string message)
    {
        return new MessageBoxResult("My Application", message, MessageBoxButtons.OK);
    }

    public MessageBoxResult MessageBox(string title, string message) 
    {
        return new MessageBoxResult(title, message, MessageBoxButtons.OK);
    }
}

And finally, the star of the show, the MessageBoxResult:

public class MessageBoxResult : ActionResult
{
    private readonly string _title;
    private readonly string _message;
    private readonly MessageBoxButtons _buttons;

    public MessageBoxResult(string title, string message, MessageBoxButtons buttons)
    {
        _title = title;
        _message = message;
        _buttons = buttons;
    }

    public string Title
    {
        get { return _title; }
    }

    public string Message
    {
        get { return _title; }
    }    

    public MessageBoxButtons Buttons
    {
        get { return _buttons; }
    }    

    protected override void ExecuteInternal(ControllerContext controllerContext)
    {
        MessageBox.Show(_title, _message, _buttons);
    }
}

I hope you find action results as interesting as I do.

PS: if there's enough interest, I'll consider extending the MessageBoxResult and rolling it into Magellan.dll.

See also:

Back to: Magellan Home

Back to: Magellan Home

Action Filters are typically attributes that you can apply to a Magellan controller or controller action in order to intercept the call and provide an alternative way of handling the request. They provide a poor man's Aspect Oriented Programming mechanism for controllers.

Action Filters

The sample below shows how an Action Filter might be used. The Log attribute can be applied to either methods or classes:

public class MyController : Controller
{
    [Log]
    public ActionResult Show(int customerId) 
    {
        ...
    }
}

The Log action filter attribute might be implemented as follows:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class LogAttribute : Attribute, IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("Entering: {0}({1})",
            context.Request.Action,
            string.Join(", ", context.Request.ActionParameters.Select(x => x.Key + "=" + x.Value).ToArray())
            );
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("Exiting: {0}({1})",
            context.Request.Action,
            string.Join(", ", context.Request.ActionParameters.Select(x => x.Key + "=" + x.Value).ToArray())
            );
    }
}

As you can see above, action filters have two hooks - before the action is invoked, and after the action is invoked. Before the action, you can:

  • See information about the request
  • Rewrite the parameters in the request
  • Short-circuit or 'cancel' the action by setting the Result property

After the action, you can:

  • Get access to the result of the action
  • Change the result of the action
  • See the exception that was thrown
  • Replace or suppress the exception that was thrown

Result Filters

Magellan also supports Result Filters via the IResultFilter interface. These are invoked after the action has been executed, and before the result is handled. These give you the ability to handle exceptions caused when rendering the view (rather than just when processing the action).

The logging example above could be extended to support Result Filters:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class LogAttribute : Attribute, IActionFilter, IResultFilter
{
    // Other code here

    public void OnResultExecuting(ResultExecutingContext context)
    {
        Console.WriteLine("Rendering: {0}({1})",
            context.Request.Action,
            string.Join(", ", context.Request.ActionParameters.Select(x => x.Key + "=" + x.Value).ToArray())
            );
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        Console.WriteLine("Rendered: {0}({1})",
            context.Request.Action,
            string.Join(", ", context.Request.ActionParameters.Select(x => x.Key + "=" + x.Value).ToArray())
            );
    }
}

Examples

Here are some useful ways to use Action and View Filters:

  • Logging of all controller requests
  • Enforcing exception handling policies
  • Caching of request results (bypassing the action the second time around)
  • Performing permission checks and redirecting to another view if the user doesn't have permissions
  • Reusing views

Back to: Magellan Home

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