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.

It seems that every WPF developer has written a Model-View-ViewModel library, and I was starting to feel left out. Having written an MVC and MVP framework, I figured I may as well write an MVVM library. But I want it to be different - I want the ViewModels to be as small as possible. That's how MicroModels was born.

MicroModels is inspired by Fluent NHibernate and uses TypeDescriptors to dynamically define properties, collections and commands.

In the example below, the view model exposes the FirstName and LastName properties of the customer object. The LastName property is renamed to Surname, and a FullName property is defined using the two names. It also exposes a Save ICommand property that saves the customer to the repository.

public class EditCustomerModel : MicroModel
{
    public EditCustomerModel(Customer customer, CustomerRepository customerRepository)
    {
        Property(() => customer.FirstName);
        Property(() => customer.LastName).Named("Surname");
        Property("FullName", () => string.Format("{0} {1}", customer.FirstName, customer.LastName));
        Command("Save", () => customerRepository.Save(customer));
    }
}

What? That's it? Where's the INotifyPropertyChanged? The getters and setters? Don't worry, it's all done at runtime by MicroModels :)

The dynamic properties are available for data binding in XAML:

<DockPanel>
    <ToolBar DockPanel.Dock="Top">
        <Button Content="Save" Command="{Binding Path=Save}" />
    </ToolBar>

    <Border Background="#f0f0f0">
        <StackPanel>
            <WrapPanel Margin="1">
                <Label Margin="1" Width="130">FirstName</Label>
                <TextBox Margin="1" Width="50" Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged}" />
            </WrapPanel>
            <WrapPanel Margin="1">
                <Label Margin="1" Width="130">Surname</Label>
                <TextBox Margin="1" Width="200" Text="{Binding Path=Surname, UpdateSourceTrigger=PropertyChanged}" />
            </WrapPanel>
            <WrapPanel Margin="1">
                <Label Margin="1" Width="130">Full Name</Label>
                <Label Margin="1" Width="200" Height="50" Content="{Binding Path=FullName}" />
            </WrapPanel>
        </StackPanel>
    </Border>
</DockPanel>

Dependency Analysis

MicroModels makes use of expression trees to analyse the dependencies a property has. In the code example above, MicroModels can figure out that the FullName property depends on the FirstName and LastName properties. If the customer raises a PropertyChanged event for either of those properties, MicroModels will raise a property changed event for FullName.

Multiple Sources

Perhaps instead of a view model exposing properties from a single object, your ViewModel will union multiple objects together. That's easy:

public class CompareCustomersModel : MicroModel
{
    public CompareCustomersModel(Customer left, Customer right)
    {
        AllProperties(left).WithPrefix("Left");
        AllProperties(right).WithPrefix("Right");
    }
}

From XAML you could bind to properties such as LeftFirstName and RightFirstName.

Wrapping Child ViewModels

If you had Order and LineItem business objects, it's common to create an OrderViewModel and LineItemViewModel, and to expose the Order's LineItems as a collection of LineItemViewModel's.

With MicroModels, this is no longer necessary. MicroModels can expose a collection of child items, and automatically wrap each item in a MicroModel. The example below shows how LineItems might be exposed as a collection, and adds a LineTotal property to each child item:

public class InvoiceViewModel : MicroModel
{
    public InvoiceViewModel(Order order, IEnumerable<LineItem> lineItems, IOrderService orderService)
    {
        AllProperties(order);

        Collection("LineItems", () => lineItems)
            .Each((item, model) => model.Property("LineTotal", () => item.UnitPrice * item.Quantity));

        Command("Save", () => orderService.Save(order, lineItems));
    }
}

When the Quantity of a single LineItem changes, MicroModels detects the changes and raises an event for the dynamic LineTotal property.

Get Started

You can download the binaries and reference MicroModels.dll. ViewModels just have to inherit from the MicroModel base class. You can also check out the source which includes a couple of samples. There is some work to do around documentation, improving the extensions system and cleaning it up. The public API needs some work, and I'd be really interested in what people think about some of the naming conventions used.

I have seen some confusion around the naming of the major UI presentation patterns, often with code being described as one pattern when actually it uses a different one. This usually happens because the goals behind each pattern are similar and the descriptions are a little too theoretical. I want this page to serve as a practical description of each pattern and to provide some concrete examples of what differentiates the patterns in the wild.

The Three Major UI Patterns

The three UI patterns commonly used in WPF applications are Model-View-Controller, Model-View-Presenter and Model-View-ViewModel (MVC, MVP and MVVM respectively).

Model View Controller

MVC was the original pattern, but the description of the pattern and the reality of how it is used have evolved. Fowler's page on the subject describes a controller as co-ordinating the behavior of a view, as if there were one controller per view. From the original Smalltalk MVC paper:

Each view is associated with a unique controller and vice versa

By that definition, we should expect controllers to look like this, with each controller clearly associated with one and only one view:

public class EditCustomerController : Controller<EditCustomerView>

Anyone who has used a web based MVC framework, however, has seen a very different style of implementing MVC. The success of recent MVC frameworks like Ruby on Rails, ASP.NET MVC and Zend have changed the working definition of MVC to a controller that handles many views. Controllers in this style typically look like:

public class CustomerController : Controller
{
    public ActionResult List();
    public ActionResult Show(int customerId);
    public ActionResult Edit(int customerId);
    public ActionResult Save(Customer customer);
    public ActionResult Delete(int customerId);
}

Where each of the actions return a different view.

I think there is an argument to be made that Zend, ASP.NET MVC and Rails aren't actually MVC, because Controllers deal with multiple views, when the original definition clearly said they should be unique. But that's only at design time - at runtime, when a request is received, the controller typically only creates one view per request, so perhaps it does meet the definition. It's a very blurry line that I'm not sure I want to get in the middle of.

Either way, MVC has come to mean a controller that handles many requests for different views, so that's the definition I use. Controllers tend to focus on navigation between views, rather than long-running interactions with a single view. Magellan has this focus.

Model View Presenter

MVP is described as a derivative of MVC. But when you see working examples of MVP, they tend to look just like the original definition of MVC, with a unique presenter per view. WebFormsMVP is an framework for this for ASP.NET. Caliburn is an MVP framework for WPF. I've also posted an example of building your own for WPF.

MVP has been further split into two approaches - Supervising Controller and Passive View. Supervising Controller is the version that almost everyone uses, and closely matches the original MVC definition. Passive View provides even more testability, and would be possible in WPF by exposing every Routed Event and the controls directly to the presenter, but I have never seen it implemented in WPF (and I don't think I would want to).

MVP in the form of Supervising Controller looks extraordinarily similar to MVC. The major difference is suggested in this snippet on Supervising Controller:

For input response the controller operates in the presenter style. The user gestures are handled initially by the screen widgets, however all they do in response is to hand these events off to the presenter, which handles all further logic.

This is the critical difference. In MVC, the controller gets the event first, before the view does. In MVP, the view gets the event first and gives it to the presenter.

On the web we can see how that works - user input happens in the form of HTTP requests, so controllers handle those and then render the view. But in WPF, controls always accept the user input first - the controller can never handle the input before the view.

Model View ViewModel

Fowler described this as the Presentation Model pattern, but WPF and Silverlight developers have come to know it as the MVVM pattern thanks to a John Gossman article. The Wikipedia article on MVVM has the history, but basically, MVVM is a specific example of Presentation Model in WPF and Silverlight applications. Personally, I use it because the first two patterns start with "MV" so the third may as well too.

MVVM is similar to MVP, but the ViewModel contains both state and behavior, while in MVP the state is generally kept in the model. The other major difference is that in MVP, where the Presenter references the View and can tell it what to do, in MVVM the ViewModel doesn't reference the View - instead, the ViewModel exposes properties and raises events that the View subscribes to.

Summary of Patterns

  • Model View Controller: Controller is concerned with navigation, and handles requests for many views. Controller typically creates the view and forgets about it. Controller gets user input before view.
  • Model View Presenter: Presenter is concerned with a single View. The presenter and view have a meaningful conversation. Model holds state, presenter contains behavior. View gets user input and passes to presenter.
  • Model View ViewModel: Much like MVP, but ViewModel contains both state and behavior. The ViewModel shouldn't have a reference to the View.

Identifying the Patterns

When you build a framework or spot some code, here are some concrete things to look for to identify the pattern. I'll use the term co-ordinator to refer to the third object in each pattern (Controller, Presenter or ViewModel).

  • If the co-ordinator has a reference to the view, you might be using MVP.
  • If the view is bound to properties on the co-ordinator, or the co-ordinator is the DataContext of the view, you might be using MVVM.
  • If the co-ordinator deals with navigation between multiple views, you might be using MVC.
  • If the view calls methods on the co-ordinator, you might be using MVVM.
  • If the view has to pass user input events to the co-ordinator, you might be using MVP.

Smalltalk-style MVC is impossible in WPF

As described above, in MVC, the controller gets input first, before the view. In MVP, the UI gets the input, and forwards it to the presenter.

An example is MVC4WPF. Controllers in MVC4WPF are associated with a single view and typically look like:

public class CustomerController : WPFViewWindowControllerBase<ICustomerView>

This looks similar to the Smalltalk type of MVC. But when I click a button, the view forwards that event to the controller. Based on the Supervising Controller definition above, this is really MVP, not MVC. The only way to achieve this style of MVC in WPF would be to have the controller handle events before the view - impossible with WPF.

I have owned a copy of Sams Teach Yourself WPF in 24 Hours for about a year, and I still find it an interesting book. One thing I like about this book is that doesn't just show off WPF features - it shows how to use the Model-View-Presenter pattern in doing so. The book was written by Rob Eisenberg and Christopher Bennage from BlueSpire, who are also the guys behind the Caliburn WPF framework, so they know a thing or two about the Model-View-Presenter pattern.

Like the Model-View-ViewModel pattern, we can mostly agree upon what the MVP pattern is, but in practice nearly every implementation looks different. I want to highlight some of the differences between my approach to MVP and theirs, and I have attached a sample application that demonstrates these.

Sample

As in the Teach Yourself WPF in 24 Hours book, my sample application is a contact manager. Here are the key points:

  • The application has a shell, and a docking panel like Visual Studio.
  • There are two types of views that can be opened - a Contact List, a Contact Details for an individual contact
  • It uses Composite WPF for region management and loosely coupled communication
  • It uses Autofac 2.0 Beta for IOC
  • It uses AvalonDock for tab management

You can download the code to follow along. You can also download the Teach Yourself WPF Contact Manager from the Sams website (you may need to register before the download appears).

Contact Manager Composite WPF with MVP Sample

Model-View-Presenter Differences

First let's highlight some differences in terms of approaches to the MVP pattern between the two. Martin Fowler splits the MVP pattern into Passive View and Supervising Controller, but if anything I think both approaches are Supervising Controller, just in a different way.

View->Presenter Communication

The first example is in how views and presenters communicate. In Teach Yourself WPF, views can hold a reference to the presenter - the following code is for a button click event handler:

private void Save_Click(object sender, RoutedEventArgs e)
{
    Presenter.Save();
}

An illustration of View-Presenter communication in Teach Yourself WPF

In my approach to MVP, Views should be independent of the presenter and not hold a reference to it, though they may be bound to a Model. If a view really wanted to communicate with a presenter, it would do so via raising an event:

event EventHandler SaveRequested;

private void Save_Click(object sender, RoutedEventArgs e)
{
    var handler = SaveRequested;
    if (handler != null) handler(this, EventArgs.Empty);
}

An illustration of View-Presenter communication the traditional way

(Of course, this is assuming that in both examples, we aren't using WPF Commands)

Some considerations to keep in mind here are:

  • In the Teach Yourself WPF way, the Presenter needs a reference to the View, but the View also needs a reference to the Presenter. For loose coupling to work, both of these should be interfaces, which may require a second interface. Using events, you normally create an interface for the view - there is no need for an interface for the presenter, since the view does not need to reference it.
  • On the other hand, testing is made a little easier with Teach Yourself WPF approach. To simulate the click event, you just have to call the method on the presenter. But to simulate the click event in the eventing approach, you have to mock the view and raise the mock event.

Presenter is DataContext?

One pattern the Teach Yourself WPF book uses, and Caliburn too, is that the Presenter is normally the DataContext for the view. This is different to the approach I've normally used, where data context is a separate object - either a domain model object, or a view model object that the presenter manipulates.

One benefit of this is that in the Teach Yourself WPF approach, properties on the presenter are available for direct binding. A presenter can use some code like this:

public void Initialize() 
{
    Contact = LoadContactFromDatabase();
}

public Contact Contact { get; set; }

However, in my approach, the presenter normally sets properties on a Model object, which it assigns to the view:

public void Initialize() 
{
    View.Model = new ContactDetailsModel();
    View.Model.Contact = LoadContactFromDatabase();
}

The Model property on the view is just a wrapper around the data context, but the key point is the DataContext is not the presenter - it is a different object. The presenter can manipulate the model, and the view can manipulate the model, but the view doesn't touch the presenter (except perhaps indirectly via raising events).

A similar thing happens with Commands. In Teach Yourself WPF/Caliburn, a command would be exposed to the view by a property on the presenter. In my approach, the presenter gives the command to the model, and the view gets it from the model.

public void Initialize() 
{
    View.Model = new ContactDetailsModel();
    View.Model.Contact = LoadContactFromDatabase();
    View.Model.SaveCommand = new DelegateCommand(SaveExecuted);
}

Here are some points to consider:

  • The Teach Yourself WPF approach is simpler because it requires less code - you don't have to create a separate class for the model.
  • However, it does put both state and behavior in the same object. In the approach I have used, state and behavior are separated.

Is it MVVM?

Though Teach Yourself WPF/Caliburn use the term presenters, some of the points above make me feel almost as if they are closer to the MVVM pattern than the MVP pattern. In MVVM:

  • The ViewModel is usually the DataContext (check)
  • The View binds to properties on the ViewModel (check)
  • The View holds a direct reference to the ViewModel (check)
  • The ViewModel typically contains both the state of the view and it's behavior (check)

The one thing Caliburn and Teach Yourself WPF do that wouldn't fit the MVVM profile is that the Presenter holds a reference to the View - something ViewModels should never do. However, in many ways it feels that the Presenters in Teach Yourself WPF/Caliburn are closer to the MVVM pattern than they are to the traditional MVP pattern.

Model-View-Presenter-Parameter

Another object that I have created for each view is a Parameter object. When you open a view, it's common to need to pass arguments to the presenter - such as a customer ID, filter information, and so on. Instead of making these properties on the presenter, the pattern I follow is to put them in a separate object. This is a technique I learned at from a client, and though it seemed redundant at first, it has grown on me.

For example, in the sample application, when you click a contact, the ContactDetailsPresenter is shown for that contact. The parameter object looks like this:

public class ContactDetailsParameter : Parameter
{
    public ContactDetailsParameter() : this(0)
    {
    }

    public ContactDetailsParameter(int contactId)
    {
        ContactId = contactId;
    }

    public int ContactId { get; set; }
    public bool IsNew { get { return ContactId == 0; } }
}

The presenter can make use of these arguments when initializing the view:

public class ContactDetailsPresenter : Presenter<IContactDetailsView, ContactDetailsModel, ContactDetailsParameter>
{
    protected override void Initialize()
    {
        View.Model = new ContactDetailsModel();
        View.Model.SaveCommand = new DelegateCommand(SaveExecuted);
        View.Model.Contact = Parameter.IsNew 
            ? new Contact() 
            : _contactRepository.GetContact(Parameter.ContactId);
    }
}

The key benefit that parameters have is that you can use them to identify information about a view, without actually creating the presenter or view. This is common when you want to re-activate an existing view.

In the sample application, you can click a contact and they will open in a new tab. If you click another contact, another tab opens. But if you click the same contact a second time, you don't want a new tab to appear - instead, you might want the currently open tab to get focus.

To open a view, I normally raise a Composite WPF event:

private void ShowContactExecuted(Contact contact)
{
    EventAggregator.GetEvent<ShowViewEvent>().Publish(new ShowViewRequest(typeof(ContactDetailsPresenter), "ContentPane", new ContactDetailsParameter(contact.Id)));
}

The arguments are:

  1. The type of presenter
  2. The region name to insert the view into
  3. The parameter, with all the arguments the presenter needs

There is a singleton event listener which receives that event and works something like this (simplified):

if (region.HasMatchingView(parameter))
{
    region.ActivateView(parameter);
}
else 
{
    var presenter = Container.Resolve(presenterType);
    region.Add(presenter.View, parameter);
}

The logic for deciding whether to re-use the view in the parameter is implemented like this:

public ContactDetailsParameter(int contactId)
{
    ContactId = contactId;

    // Use the same view when ContactID's match, but for new views, never reuse them.
    ReuseViewBy(() => IsNew ? (object)Guid.NewGuid() : ContactId);
}

While this approach has many upsides, it does increase the number of files needed for a full MVP view:

Solution Explorer showing views

The upside however is that you do get a flexible, consistent approach to views that can handle pretty much any scenario - and having used this pattern in a fairly complicated LOB application, I'm pretty confident that it can handle anything. A good New Item template for Visual Studio can greatly speed up the getting started time too.

Some things to consider:

  • A parameter is a good way to encapsulate the arguments for a presenter
  • But it does introduce an extra class, though that in itself has some benefits

Composite WPF Regions vs. Presenter Hierarchies

I have used Composite WPF in five client projects now and I'll continue to do so. It's a fantastic piece of work that does help you to solve many common scenarios without being overly controlling. The major feature Composite WPF brings for me is regions, which allow you to compose and nest views, and allow the to communicate via the event aggregator.

Composition is an aspect of Teach Yourself WPF/Caliburn that I really don't feel comfortable with, especially from a Composite WPF background. Check out the Caliburn documentation on presenters. Depending on your UI, you can choose from:

  • Presenter
  • Navigator
  • PresenterManager
  • MultiPresenter
  • MultiPresenterManager?

Each of these are kinds of presenters and serve to fulfill a specific composition pattern. They are necessary because, for example, if you have a view with two child views, the parent view's presenter needs a reference to the child presenters, and vice-versa sometimes, so they can communicate. It reminds me of the CAB SmartPart/Workspace approach.

The Composite WPF region approach does away with all of this. Here's the start up code for the sample application:

_container.Resolve<IEventAggregator>().GetEvent<ShowViewEvent>().Publish(new ShowViewRequest(typeof(ShellPresenter), "Shell", new ShellParameter()));
_container.Resolve<IEventAggregator>().GetEvent<ShowViewEvent>().Publish(new ShowViewRequest(typeof(MenuPresenter), "Menu", new MenuParameter()));
_container.Resolve<IEventAggregator>().GetEvent<ShowViewEvent>().Publish(new ShowViewRequest(typeof(ContactListPresenter), "ContentPane", new ContactListParameter()));

This code opens the Shell, and injects the Menu and ContactList into it. However, the Shell view has no notion of either of them - the ShellPresenter is just a presenter, not some kind of MultiPresenterManager. If it needed to communicate with them, it could do so via a loosely coupled pub-sub model called the Event Aggregator.

Keep in mind that the Composite WPF region system would work just as easily with a Teach Yourself WPF presenter approach (presenter as DataContext, etc.). The composition approaches aren't necessarily exclusive either - hierarchical presenters as used with Caliburn can work alongside regions.

Conclusion

In this post I focused on two alternative approaches to implementing the Model View Presenter pattern - a traditional, strict approach with strong separation between state and behavior, and the Teach Yourself WPF/Caliburn approach which is a nice intermediary between MVP and MVVM. I think both patterns have their strengths and I've tried to provide some ideas on what those might be. I also contrasted the related composition approaches which aren't necessarily exclusive. I'd love your comments on additional strenghts/weaknesses you've experienced with either pattern.

And if you are building WPF line of business applications and haven't picked up a copy of Teach Yourself WPF, I'd definitely recommend it. This post is also not a thorough representation of the features that Caliburn nor Composite WPF provide - Caliburns Actions are definitely worth a look.

I enjoyed reading this question on Stack Overflow: What is (functional) reactive programming? I thought I'd take a stab at an explanation of what reactive programming means to me. Let me take you to the future.

The year is 2051, and our team of code anthropologists discovered some ancient script in the long-abandoned ruins of a decaying open source project repository. With some deciphering, we discover the the script is written in a primitive dialect of P#, the dominant language of the year 2051. It reads:

var a = 10;
var b = a + 1;
a = 11;
b = a + 1;

Through careful analysis of the script, we eventually decipher this to be an ancient parable. It tells the story of two brothers. a is the first born, proud and independent. b is the younger of the two, but it is dependent; it needs a. But the relationship is short lived. Once the second line is executed, and b grows up, the relationship between the adventurers ends - b is no longer dependent on a. When a changes, b does not. a must attempt to re-establish the once-lost relationship, but even that does not last.

We can relate to this as if it were the code version of Cats in the Cradle. First there is a, and b who needs a. But by the third line, b has grown up, and has no time for a. It is only when a calls that the two are momentarily re-connected, but even that is only fleeting.

Thanks to these cave paintings, we can draw the conclusion that the primitive compilers weren't smart enough to figure out that the destinies of these two proud brothers were intertwined. It seems that ancient programmers had to continually re-establish the relationship, or risk data being out of sync. As modern day code anthropologists, it's hard to imagine how it might have felt to write code like this.

In the year 2051, reactive programming is the norm. Language creators discovered the destiny operator decades ago, and the old ways were quickly forgotten. For example, in P#, we can write:

var a = 10;
var b <= a + 1;
a = 20;
Assert.AreEqual(21, b);

As you can see, the statement establishes b and a as having intertwined destinies, which are unbroken and forever. They are bound. The relationship between them isn't implicit, an idea that only exists in the mind of the programmers; it's explicit, a part of the language, and it exists for all time.

Although the destiny operator is wide spread, the way it works is a closely guarded secret. Some say that when the compiler encounters code that changes a, it inserts the corresponding change for b, such that they are always in sync. Others say that a, instead of being a lowly 4-byte integer, is ascended into a higher plane of existence. It becomes an observable, an object whose changes reverberate throughout the software at runtime, with the aid of event handlers created by the compiler. Old wives tales even tell of a great timer that constantly ticks, re-aligning all the variables after every change.

Nevertheless, through understanding the parable of the dependent brothers and getting a glimpse into the ways of ancient programmers, we can be even more thankful for the destiny operator and reactive programming. Instead of slaving over repetitive code and dealing with bugs, modern programmers can express relationships that last forever.

NB: P# is just the nickname for Microsoft Visual C#++Basic.NET on Rails, created by Paul Stovell in the year 2021

Back to: Magellan Home

Darren Neimke pointed me to Simone Chiaretta's useful reference of ASP.NET MVC extension points. Since Magellan's MVC Framework is inspired by ASP.NET MVC, I thought it would be useful reference to draw the connection between the extension points that Magellan supports and highlight some of the key differences.

#ASP.NET MVC Extension PointMagellan Implementation
1 RouteConstraint As Magellan does have the concept of user-visible URL's, routes and routing in general aren't required.
2 RouteHandler As above.
3 IControllerFactory IControllerFactory. Magellan also makes use of controllers, and controller factories manage their life cycle. Releasing works a little differently, but I'm hoping it's an improvement on the model. The documentation on controllers has more information, and there is also a sample on how to use them with IOC containers.
4 ActionInvoker IActionInvoker. Magellan controllers derived from the ControllerBase base class will defer to an IActionInvoker to call the action. The default implementation of IActionInvoker handles action and result filters, uses reflection to locate the methods, and generally works like the ASP.NET MVC equivalent. There is also an asynchronous version.
5 ActionMethodSelectorAttribute This isn't supported in Magellan, though I did consider it. It makes sense in ASP.NET MVC since you are dealing with HTTP requests and public URL's, and you may want to take other HTTP information into account (HTTP verbs are common). Since Magellan is in the rich client world, this isn't a consideration. Since not implementing this feature meant I could keep the implementation simpler, I didn't build it. If this is important, you can derive from the ActionInvoker base class and provide your own selection logic.
6 IAuthorizationFilter ASP.NET MVC draws a distinction between authorization filters and action filters, but the only real difference I've been able to see between them is authorization filters are invoked before action filters. I can think of few reasons why authorization couldn't be implemented with an action filter, so I just went with action filters in Magellan. I'm open to being convinced of why authorization filters should be added.
7 IActionFilter IActionFilter. Magellan supports action filters just like ASP.NET MVC, and they can do most of the same things. They can short-circuit the action, handle exceptions, change the return values, and so on.
8 IModelBinder IModelBinder. Magellan supports model binders too, and the default model binder is used for mapping parameters to actions. However, model binders in ASP.NET MVC exist because you often need to convert raw HTTP input into a rich object. Since Magellan exists in the rich client world, rich objects can be used natively, so I think it would be rare to use model binders. That said, they are supported.
9 ControllerBase ControllerBase and Controller. Controller is the most common base class, but you can derive from ControllerBase if you want to reuse the ActionInvoker usage without the friendly helpers Controller provides. You can also implement IController directly if you wish.
10 IResultFilter IResultFilter. Magellan also supports result filters. They can be used for changing the result, handling rendering exceptions, view reuse, and so on.
11 ActionResult ActionResult. Magellan uses action results as does ASP.NET MVC. The out-of-the-box results support views, cancellation, redirection, and a handful of other features. There's probably less use for custom action results in Magellan than in ASP.NET MVC, but they still come in handy.
12 IViewEngine IViewEngine. View engines are integral in locating and rendering views, so of course they are supported. Magellan ships with three out of the box - one for windows and dialogs, another for pages, and a third for Composite WPF support. I've also shown how to implement a view engine for Windows Forms.
13 HtmlHelper You can probably guess that this doesn't exist in Magellan. Magellan does provide a couple of useful extensions when writing views though, such as navigation behaviors and shared layouts.

Back to: Magellan Home

Rob Relyea might not like this, but today I had an idea.

When referencing controls from another assembly in XAML, one can either use xmlns:foo="clr-namespace:MyNamespace;assembly=MyAssembly" syntax, or a URI mapped with the XmlnsDefinition attribute. MSDN can tell you all about the attribute. Currently in Magellan I use it like this:

[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Abstractions")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Commands")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Behaviors")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Controls")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Framework")]
[assembly: XmlnsDefinition("http://xamlforge.com/magellan", "Magellan.Views")]

This allows you to reference any Magellan object with a simple reference:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:magellan="http://xamlforge.com/magellan" 
    >

The annoying thing about this, though, is you end up with ugly prefixes all over the XAML:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:magellan="http://xamlforge.com/magellan" 
    x:Class="Wizard.Views.Shared.Main" 
    >
    <DockPanel>
        <magellan:ZonePlaceHolder ZoneName="Left" DockPanel.Dock="Left" Width="300" />
        <magellan:ZonePlaceHolder ZoneName="Right" DockPanel.Dock="Right" Width="300" />
        <magellan:ZonePlaceHolder ZoneName="Content" />
    </DockPanel>
</UserControl>

Today I had an idea: I can usurp the Microsoft WPF XML namespaces by adding my own controls to them. It would be as simple as changing my XmlnsDefinition attrbutes to:

[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Abstractions")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Commands")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Behaviors")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Controls")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Framework")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Magellan.Views")]

Which lets me use this:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Wizard.Views.Shared.Main" 
    >
    <DockPanel>
        <ZonePlaceHolder ZoneName="Left" DockPanel.Dock="Left" Width="300" />
        <ZonePlaceHolder ZoneName="Right" DockPanel.Dock="Right" Width="300" />
        <ZonePlaceHolder ZoneName="Content" />
    </DockPanel>
</UserControl>

Which not only reads nicer, but is also more accessible, since there's no need to manually add an import (ReSharper will add xmlns entries for you, but unfortunately it doesn't recognise XmlnsDefinition attributes).

My initial reaction is that this is evil, but in thinking about it, there are no Magellan classes that would conflict with classes in the WPF XML namespace. And if other libraries had the same names, you could use prefixes for those to differentiate, so that would just work. It also works fine in Blend. So the only problem that could arise is if someone else used this trick. But since it's so evil, I'd be the only one doing it, so there's no problem ;)

What do you think? Would you be disturbed by this, or would you actually find it a better experience?

Edit: It turns out that even if another library used this technique and was referenced, you can still differentiate between them by using an explicit xmlns:xyz reference. So I am not seeing much of a downside.

Back to: Magellan Home

When building WPF pages and windows, it's common to want a consistent layout across views. In ASP.NET, this is often accomplished with Master Pages. Magellan brings a similar concept to WPF, in the form of Shared Layouts.

The Magellan source code includes a new Wizard example application that demonstrates the shared layout feature. In this page I'll describe how it works.

The new Wizard sample

Creating and Using Shared Layouts

To create a shared layout, I typically add a Shared folder under Views:

A Visual Studio Project with a Shared folder containing Main.xaml, a shared layout, and a number of pages which use the layout

The shared layout, Main.xaml, is simply a UserControl with a number of ZonePlaceHolders. Each ZonePlaceHolder is given a Name, which we'll refer to later. The example below shows how a three-column layout might be declared. Since the layout is a UserControl, it can contain any XAML you wish:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:magellan="http://xamlforge.com/magellan" 
    x:Class="Wizard.Views.Shared.Main" 
    >
    <DockPanel>
        <ZonePlaceHolder Name="Left" DockPanel.Dock="Left" Width="300" />
        <ZonePlaceHolder Name="Right" DockPanel.Dock="Right" Width="300" />
        <ZonePlaceHolder Name="Content" />
    </DockPanel>
</UserControl>

Now that our layout is declared, we can reference it from any other Window, Page or UserControl. Here's what a page might look like:

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:magellan="http://xamlforge.com/magellan" 
    x:Class="Wizard.Views.Wizard.AccountDetailsPage" 
    Title="Account Details"
    >
    <Layout Source="/Wizard;component/Views/Shared/Main.xaml">
        <Zone ZonePlaceHolderName="Content">
            <TextBlock Text="The center content goes here" />
        </Zone>

        <Zone ZonePlaceHolderName="Left">
            <TextBlock Text="This content will appear on the left" />
        </Zone>

        <Zone ZonePlaceHolderName="Right">
            <TextBlock Text="This content will appear on the right" />
        </Zone>
    </Layout>
</Page>

The page uses the Layout control to reference the shared layout. The Source property is the URI to the the XAML file that contains the shared layout.

Composition

The Layout element is a control that takes a Source and a set of Zones. When it is loaded into the scene, it loads the shared layout user control and sets it as the content. Any ZonePlaceHolders inside the control will then have their content injected, based on the zone names matching.

The logical tree of the final page will look like this:

A diagram showing the logical tree of the page, layout and zones

Since the Layout is a child of the Page, this opens a number of possibilities:

  • The layout will inherit the DataContext of the page. If your DataContexts for each page have the same properties in common - such as using a shared View Model - the layout can make use of those.
  • The Layout can use WPF's Routed Events to communicate with the page.
  • The layout can use RelativeSource FindAncestor bindings to get properties from the page, such as the page title.

Default Zone Content

The ZonePlaceHolders are content controls, and their content is overridden when the layout is merged into the page, but only if the page specifies a corresponding zone. This means you can set default content for zones, and allow individual pages to override them.

For example, wizards often have a Back button. Using WPF Pages, the ZonePlaceHolder could be written as:

<ZonePlaceHolder Grid.Column="0" Name="BackNavigation">
    <Button Content="Back" Command="NavigationCommands.BrowseBack" />  
</ZonePlaceHolder>

If a page that refers to the layout doesn't specify a Zone with the same name, the back button will appear. But a page could override the content, for example:

<Zone ZonePlaceHolderName="BackNavigation"> 
    <Button Content="Cancel" Command="{Binding CancelWizardCommand}" />
</Zone>

Or it may just choose to clear the content, removing the button from the tree:

<Zone ZonePlaceHolderName="BackNavigation" Content="{x:Null}" />

Default Shared Layouts

Since the Layout control's Source property is a dependency property, you can use a Style to set the default layout source:

<Style TargetType="Layout">
    <Setter Property="Source" Value="/Wizard;component/Views/Shared/Main.xaml" />
</Style>

When writing pages, you can now just use a Layout element without a source:

<Layout>
    <Zone ZonePlaceHolderName="Content">
        <TextBlock Text="The center content goes here" />
    </Zone>

Or a page may choose to override the Layout:

<Layout Source="/Wizard;component/Views/Shared/Alternative.xaml">
    <Zone ZonePlaceHolderName="Content">
        <TextBlock Text="The center content goes here" />
    </Zone>

Interestingly, thanks to dependency properties, you could also use a Trigger or data binding to selectively change the layout based on user preferences.

Designer Support

Shared Layouts have good designer support with Expression Blend. For example, this is how the Main.xaml page from the Wizard example appears:

The Main.xaml file in Expression Blend

The individual pages that reference the shared layout also work in Blend:

A page in Blend that uses the shared layout

The only caveat to make it work in blend is that the URI must contain the assembly name of the layout. For example, this will work at runtime, but not design time:

<Layout Source="/Views/Shared/Main.xaml" />

However this will work at both design time and runtime:

<Layout Source="/Wizard;component/Views/Shared/Main.xaml" />

Nested Layouts

Nested layouts allow you to apply "inheritance" to layouts. For example, you can set up a common layout that provides a Title and Content zone. You can then create another layout that references the first layout, and sub-divides the content into two column, Left and Right. You might create another layout subdividing the two column layout even further - it's layouts all the way down.

A nested layouts example

Nested layouts are easy to create. First the Main.xaml:

<UserControl 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:magellan="http://xamlforge.com/magellan"
    >
    <DockPanel>
        <ZonePlaceHolder Name="Title" DockPanel.Dock="Top" />
        <ZonePlaceHolder Name="Content" />
    </DockPanel>
</UserControl>

Now the TwoColumn.xaml, which references Main.xaml:

<UserControl 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:magellan="http://xamlforge.com/magellan"
    >

    <Layout Source="/MyAssembly;component/Layouts/Main.xaml">        
        <Zone ZonePlaceHolderName="Title">
            <ZonePlaceHolder Name="Title" />
        </Zone>

        <Zone ZonePlaceHolderName="Content">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <ZonePlaceHolder Grid.Column="0" Name="Left" />
                <ZonePlaceHolder Grid.Column="1" Name="Right" />
            </Grid>
        </Zone>
    </Layout>
</UserControl>

Note how this layout references the first. To re-expose the Title zone, we create a Zone with a ZonePlaceHolder. We also expose a Left and Right zone by splitting the Content zone using a Grid.

Summary

Shared Layouts allow you create a consistent look and feel for your views while minimizing XAML and code behind. They can be used not only on Pages, but from any XAML - you might create a Shared Layout for Dialogs with OK/Cancel buttons, or for tab pages within an options dialog.

To make use of shared layouts, you just need a reference to Magellan.dll. You don't have to use Magellan's MVC framework to use this feature, as they are completely independent.

I'd love any feedback on how to make this feature more useful.

Back to: Magellan Home

Back to: Magellan Home

Magellan makes use of TraceSources internally for providing diagnostic information. By default nothing is traced, but you can configure the trace sources to see details.

Configuring the trace sources can be done through the application configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="Magellan" switchValue="Verbose">
        <listeners>
          <clear />
          <add name="Console" type="System.Diagnostics.ConsoleTraceListener" />
        </listeners>
      </source>
    </sources>
  </system.diagnostics>
</configuration>

Or through code, for example, in the App.xaml code behind:

Magellan.Diagnostics.TraceSources.MagellanSource.Switch.Level = SourceLevels.Verbose;

Here is an example of what is logged at the Information level:

Magellan Information: 0 : The Navigator is executing the request 'Controller=Home; Action=Home; RequestID=6ce8e373-f2d9-48d0-b1c2-9438094feeaf'.
Magellan Information: 0 : Controller 'iPhone.Applications.Home.Controllers.HomeController' is executing request 'Controller=Home; Action=Home; RequestID=6ce8e373-f2d9-48d0-b1c2-9438094feeaf'.
Magellan Information: 0 : The PageViewEngine is rendering the page 'iPhone.Applications.Home.Views.HomeView'.
Magellan Information: 0 : Request completed: 'Controller=Home; Action=Home; RequestID=6ce8e373-f2d9-48d0-b1c2-9438094feeaf'.

Here is what is logged at the Verbose level:

Magellan Information: 0 : The Navigator is executing the request 'Controller=Home; Action=Home; RequestID=2704ddc6-0dca-412f-88fc-b08a008ee586'.
Magellan Verbose: 0 : Resolving controller 'Home' for request 'Controller=Home; Action=Home; RequestID=2704ddc6-0dca-412f-88fc-b08a008ee586'
Magellan Information: 0 : Controller 'iPhone.Applications.Home.Controllers.HomeController' is executing request 'Controller=Home; Action=Home; RequestID=2704ddc6-0dca-412f-88fc-b08a008ee586'.
Magellan Verbose: 0 : DefaultActionInvoker found the action 'Home' as method 'Magellan.Framework.ActionResult Home()'
Magellan Verbose: 0 : DefaultActionInvoker found the following action filters for action 'Home': ''.
Magellan Verbose: 0 : DefaultActionInvoker found the following result filters for action 'Home': ''.
Magellan Verbose: 0 : The ViewEngineCollection is consulting the view engine 'Magellan.Framework.PageViewEngine' for the view 'Home'.
Magellan Information: 0 : The PageViewEngine is rendering the page 'iPhone.Applications.Home.Views.HomeView'.
Magellan Verbose: 0 : The PageViewEngine has navigated to the page 'iPhone.Applications.Home.Views.HomeView'.
Magellan Verbose: 0 : The PageViewEngine is clearing the navigation history.
Magellan Information: 0 : Request completed: 'Controller=Home; Action=Home; RequestID=2704ddc6-0dca-412f-88fc-b08a008ee586'.

The Warning and Error levels are rarely used - warning is used when Magellan decides on a course of action that is potentially incorrect, and errors are usually logged before an exception is thrown.

See also:

Back to: Magellan Home

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

Back to: Magellan Home

Magellan does not natively support Windows Forms, but adding support is as easy as writing your own View Engine.

As described in the documentation on View Engines, there are two classes we need to write. The first is an object that implements IViewEngine, which is responsible for finding the view. The second is an object inheriting from ViewEngineResult, which contains logic for rendering the form (i.e., calling Form.Show()).

There are also a couple of design considerations:

  • Magellan controllers like to create View Models, and it's the ViewEngineResult's job to make them available for binding on the view.
  • Windows Forms applications typically use naming conventions like frmCustomerDetails, CustomerDetailsForm or CustomerDetailsWindow. Our engine should support these.

Binding Models to Forms

To support the model, we're going to create an interface that allows the model to be given to the form. The form can then figure out how to display the model (either using a Windows Forms BindingContext, or manual code).

public interface IBindableForm
{
    void Bind(object model);
}

Our form implementation could look like this:

public partial class MainForm : Form, IBindableForm
{
    public MainForm()
    {
        InitializeComponent();
    }

    public void Bind(object model)
    {
        Text = model.ToString();
    }
}

The Controller

Here is what our Controller might look like:

public class HomeController : Controller
{
    public ActionResult Launch()
    {
        Model = "Hello world!";
        return Window("Main");
    }
}

Notice that there is no reference to Windows Forms here. The Window() return value merely indicates that we want to show a Window - it doesn't indicate what kind of Window should be shown. This should make it easier to convert to WPF in the future.

The View Engine

The view engine is also pretty short. Since we want to reuse the naming conventions and reflection code that the other view engines use, we can derive from ReflectionBasedViewEngine:

public class FormsViewEngine : ReflectionBasedViewEngine
{
    private readonly IViewActivator _viewActivator;

    public FormsViewEngine(IViewActivator viewActivator)
    {
        _viewActivator = viewActivator;
    }

    protected override IEnumerable<string> GetAlternativeNames(string viewName)
    {
        yield return viewName;
        yield return viewName + "Form";
        yield return viewName + "Window";
        yield return viewName + "Dialog";
        yield return "frm" + viewName;
    }

    protected override bool ShouldHandle(ControllerContext controllerContext, ParameterValueDictionary viewParameters, string viewName)
    {
        var viewType = viewParameters.GetOrDefault<string>("ViewType");
        return viewType == "Window" || viewType == "Dialog";
    }

    protected override IEnumerable<Type> FilterCandidateTypes(ControllerContext controllerContext, ParameterValueDictionary viewParameters, string viewName, IEnumerable<Type> candidates)
    {
        return candidates.Where(x => typeof (Form).IsAssignableFrom(x));
    }

    protected override ViewEngineResult CreateViewResult(ControllerContext controllerContext, ParameterValueDictionary viewParameters, Type type)
    {
        return new WindowsFormViewEngineResult(type, _viewActivator, viewParameters);
    }
}

Notice how we override GetAlternativeNames to support our conventions. We also override FilterCandidateTypes to restrict the view to objects derived from the Windows Forms Form class.

The View Engine Result

The View Engine above has taken charge of finding the view. When a view is found, it creates a WindowsFormViewEngineResult. The view engine result has the job of instantiating and rendering the view:

public class WindowsFormViewEngineResult : ViewEngineResult
{
    private readonly Type _formType;
    private readonly IViewActivator _viewActivator;
    private readonly ParameterValueDictionary _viewParameters;

    public WindowsFormViewEngineResult(Type formType, IViewActivator viewActivator, ParameterValueDictionary viewParameters) : base(true, new string[] {})
    {
        _formType = formType;
        _viewActivator = viewActivator;
        _viewParameters = viewParameters;
    }

    public override void Render()
    {
        var form = (Form)_viewActivator.Instantiate(_formType);
        var bindableForm = form as IBindableForm;
        if (bindableForm != null)
        {
            var model = _viewParameters.GetOrDefault<object>("Model");
            bindableForm.Bind(model);
        }

        var isDialog = _viewParameters.GetOrDefault<string>("ViewType") == "Dialog";
        if (isDialog) form.ShowDialog();
        else form.Show();
    }
}

We make use of two critical view parameters - the ViewType, which indicates whether to show it as a Window or a Dialog, and the Model, which is populated from the Model property that was set on the controller.

Wiring It Up

To support our view engine, we just have to add it to the list of registered view engines:

ViewEngines.Engines.Add(new FormsViewEngine(new DefaultViewActivator()));

The DefaultViewActivator implements the IViewActivator interface, which is used for actually constructing the form (it's passed to the view engine result). If you want, you can use an IOC container instead.

The final Windows Form

Summary

Hopefully this illustrates how easy it is to write a View Engine. The biggest benefit to the Magellan approach is that our controllers are consistent whether we are using WPF or Windows Forms. There's also the added benefit that if we upgrade a view to WPF, our controllers don't change. Likewise if we change our naming conventions for the forms, we don't need to change the controllers. Finally, our controllers and models remain testable, which is always important.

Back to: Magellan Home