arrow-left arrow-right brightness-2 chevron-left chevron-right circle-half-full dots-horizontal facebook-box facebook loader magnify menu-down RSS star Twitter twitter GitHub white-balance-sunny window-close
Magellan View Engines
3 min read

Magellan View Engines

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

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

Paul Stovell's Blog

Hello, I'm Paul Stovell

I'm a Brisbane-based software developer, and founder of Octopus Deploy, a DevOps automation software company. This is my personal blog where I write about my journey with Octopus and software development.

I write new blog posts about once a month. Subscribe and I'll send you an email when I publish something new.

Subscribe

Comments