A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on Octopus Deploy, an automated deployment tool for .NET applications.

Prior to founding Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm. I also worked on a number of open source projects and was an active user group presenter. I was a Microsoft MVP for WPF from 2006 to 2013.

Back to: Magellan Home

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

(This page is a work in progress)

High Level Architecture

  • User Journeys
  • Deployment
  • UI approaches
  • Navigation Model
  • Inversion of Control
  • Managing third-party dependencies
  • Solution structure

Separated Presentation

  • Model View Presenter/Supervising Controller
  • Model View ViewModel/Presentation Model
  • Model View Controller
  • Pub/sub Eventing
  • Observer pattern

Composition

  • Top down composition
  • Bottom up composition
  • View-based composition
  • Model-based composition
  • Modular applications
  • Plug Ins

Solutions to Common Problems

  • Threading
  • Validation
  • Editing and IsDirty
  • Contextual awareness
  • Shared Layouts/Master Pages
  • Disposability
  • Unit testing
  • Automated UI testing
  • Authentication/authorization
  • Enforcing UI standards
  • Forms
  • Performance and memory management
  • Online/offline/limited access mode
  • Enabling automation/macros
  • Theming
  • Help

Enterprise Applications

  • Centralized configuration
  • Localization
  • Designer support

Client Server Applications

  • Proxy management
  • Routing
  • Messaging
  • Server events/duplex bindings
  • Client vs. Server side domain models
  • Sharing code between Client and Server
  • Sharing validation between Client and Server

Development processes

  • View specifications
  • Designer/developer interaction
  • Migration

Cross Cutting Concerns

  • Logging
  • Exception management
  • Caching

Appendix:

  • Coding standards

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

(This page is a work in progress)

As the primary Microsoft platform for building client applications, Windows Presentation Foundation has a rich validation system. This article is going to look at the approaches that WPF enables for validation, how to style validation messages to suit your needs, as well as some of the limitations of the system. This article is targeted at .NET 3.5 SP1.

How to Validate

WPF allows you to declare validation logic in the following places:

  • You can declare a rule in XAML and apply it to a binding (UI only)
  • You can throw exceptions in property setters (models)
  • You can implement the IDataErrorInfo interface (models)

Validation Rules via XAML

WPF allows your rules to be encapsulated within a class, and to have the rule applied via XAML. You begin by defining a rule:

Code sample goes here

Your rule can then be applied by using object element syntax to declare your binding:

XAML code goes here

This will be enough to have error messages appear on screen:

Screenshot goes here

We will talk more about what you can do with validation rules later.

Throwing Exceptions in Setters

An easy way to perform validation in models is to throw an exception in the setters of your properties:

Code sample goes here

When you declare a binding, you can either use object element syntax, as per below:

XAML syntax

Or make use of a handy property that was introduced in .NET 3.5:

XAML syntax (shorter)

This approach is useful because it allows you to consolidate validation logic in your models, but it has a few drawbacks which we'll discuss later. A better approach to model validation is the IDataErrorInfo interface.

Implementing IDataErrorInfo

WPF in .NET 3.5 introduced IDataErrorInfo support out of the box. The implementation is quite simple:

Code sample goes here

As with model binding, you can either use object element syntax:

XAML sample here

Or a handy attribute that was introduced in .NET 3.5:

XAML sample (shorter)

How to display validation results

WPF makes it easy to display validation errors against a UI element.

Back to: Magellan Home

Because the Magellan framework handles many parts of the navigation lifecycle, there are a number of things that can go wrong. I want to dedicate this page to explaining the common exceptions that are thrown by Magellan, and look at techniques for handling them.

Exceptions thrown by Magellan

Where logical, Magellan throws standard .NET exceptions such as ArgumentNullException and InvalidOperationException. However, Magellan also has it's own set of custom exceptions that can be thrown. All custom Magellan exceptions derive from an abstract NavigationException class, which you can use if for some reason you want to handle all Magellan exceptions in a specific way (such as from a common exception policy handler).

NavigationConfigurationException

This is usually thrown if there is something in Magellan that has not been setup properly. This can be thrown when:

  • You forget to set the controller factory via ControllerBuilder.Current.SetControllerFactory when the application starts up. See the section on controllers and controller factories for more details.
  • There are no View Engines registered, because they have been cleared and none were added. By default the View Engines are automatically registered by Magellan, so the application must have code somewhere that clears the view engines for this to happen. See the topic on View Engines for more details.

ImpossibleNavigationRequestException

This is thrown when a navigation request has been made, but there is no logical way that Magellan could handle it. An example that causes this is when you use Navigator.Default to navigate to an action, and the action attempts to navigate to a page. Magellan can't navigate to the Page without knowing the NavigationService, so there is no way to fulfill this request. See the section on the Navigator for more details.

ActionNotFoundException

This exception is thrown when an action is not found on the controller. This usually indicates that the controller exists, but the name of the action is incorrect, or the wrong controller was specified. Generally, actions should be public, non-static/shared methods, and must be declared as returning ActionResult or a derived type.

UnhandledActionInvocationException

This is thrown when an action threw an exception, and it wasn't handled by any of the Action Filters. Some pseudo code for how actions are invoked is below:

try 
   call action()
catch ex
   ask any of the action filters to handle it
   if handled = true, do nothing
   else, throw UnhandledActionInvocationException(ex)

As you can probably guess, the reason for throwing a custom exception instead of re-throwing the original, is that Magellan would lose the stack trace, which would make tracking the real exception down very hard. So instead, Magellan wraps it in this exception. You can check the InnerException property to see the real cause for the exception.

As the code above suggests, Action Filters get a chance to handle exceptions. I'll talk more about that below.

UnhandledActionResultException

This guy is thrown when the action was executed fine, but when the ActionResult which it returned threw an exception. This can happen for example if you return a StartProcessResult, but the process name doesn't exist.

As with action invocation, the action filters will get a chance to see and handle this exception too. If they choose not to, this exception will be thrown, with the real exception nested safely inside the InnerException.

ViewNotFoundException

This hopefully self-explanatory exception is thrown when you return a Page, Window or Dialog action result, but the view name you specify doesn't exist. This exception also includes the names of all of the attempted search locations where Magellan looked for the view in the aptly-named SearchLocations property.

Techniques for Handling Exceptions

As with any exception handling guidance, the best approach is to avoid exceptions from happening in the first place. However, that's not always possible.

To avoid UnhandledActionInvocationException and UnhandledActionResultException, you can make use of Action Filters. The action filters section has more details.

Here is an example action filter that logs all navigation errors and shows a message box when they occur:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class HandleErrorsAttribute : Attribute, IActionFilter, IResultFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Exception != null)
        {
            Trace.WriteLine(context.Exception);
            context.ExceptionHandled = true;
            MessageBox.Show(context.Exception.Message);
        }
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        if (context.Exception != null)
        {
            Trace.WriteLine(context.Exception);
            context.ExceptionHandled = true;
            MessageBox.Show(context.Exception.Message);
        }
    }
}

This can then be applied to a controller as simply as:

[HandleErrors]
public class MyController : Controller ...

Your filter can also override the exception - for example:

public void OnResultExecuted(ResultExecutedContext context)
{
    if (context.Exception is SqlException)
    {
        Trace.WriteLine(ex);
        context.Exception = new GenericException("A database issue occurred.");
    }
}

That said, for the most part if your intention is to just log and suppress all exceptions, you can do so via the Application.Current.DispatcherUnhandledException event without involving Magellan. There are occasions where this form of handling could come in handy however.

Back to: Magellan Home

Back to: Magellan Home

When navigating between pages, transitional animation can provide a powerful means of communicating context with the user. The Transitionals library from Microsoft is a popular way to set up transitions when content changes, and comes with a number of out of the box transitions such as 3D cube, blinds, dissolve and star wipe.

When it comes to page navigation, we often want transitions to relate to the navigation we are performing. For example, if we click a "Next" button, we might expect a transition that 'slides' the next page into view. If we then clicked "Back", we would expect a slide in the opposite direction.

Magellan has out of the box support for the Microsoft Transitionals library. Here I will assume that you have a Magellan project ready (you can create one manually using this Quickstart, or just use the Magellan project template).

You will need to add a reference to the following DLL's:

  • Magellan.Transitionals.dll
  • Transitionals.dll

You will then need to update the entry point of your application (usually App.xaml.cs) to set up the transition mappings:

NavigationTransitions.Table.Add("Back", "Forward", () => new SlideTransition(SlideDirection.Back));
NavigationTransitions.Table.Add("Forward", "Back", () => new SlideTransition(SlideDirection.Forward));
NavigationTransitions.Table.Add("ZoomIn", "ZoomOut", () => new ZoomInTransition());
NavigationTransitions.Table.Add("ZoomOut", "ZoomIn", () => new ZoomOutTransition());

These entries define a transition, as well as the 'reverse' transition that will be used when navigating "Back".

To enable the transitions, you can edit the control template of any Frame elements to use the transitional navigation element. Below is an example:

<Frame Name="mainFrame">
    <Frame.Template>
        <ControlTemplate TargetType="{x:Type Frame}">
            <DockPanel ClipToBounds="True">
                <NavigationTransitionPresenter Content="{TemplateBinding Content}" />
            </DockPanel>
        </ControlTemplate>
    </Frame.Template>
</Frame>

Finally, you will need to change any code which performs a navigation request to specify the transition to use. Procedurally, code like this:

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

Should become:

Navigator.For(mainFrame).NavigateWithTransition("Home", "Index", "ZoomIn");

And likewise, Behaviors like this:

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

Should become:

<Button Content="Try Again">
    <i:Interaction.Behaviors>
        <NavigateWithTransitionBehavior Transition="Forward" Controller="Home" Action="Index" />
    </i:Interaction.Behaviors>
</Button>

And that is all! Navigating between pages should now make use of transitions.

When defining your transitions using the TransitionTable, you can provide any transition from the WPF Transitionals library, or one of the four out of the box Magellan transitions designed specifically for navigation applications (slide forward, slide backward, zoom in and zoom out). For example, below is a roll transition in action:

Roll transition

NavigationTransitions.Table.Add("Forward", "Back", () => new Transitionals.Transitions.RollTransition());

Writing your own transitions is also relatively easy - see the Transitionals source code for examples.

Back to: Magellan Home

I was getting bored with dark colour schemes in Visual Studio, so about a month ago I put together a colour scheme that matches PaulStovell.com. The colours my blog uses are inspired by StackOverflow, with a few small differences to make it fit with the other colours I use.

Here is how C# code looks:

C# code

Here is how XAML looks:

XAML code

And XML files:

XML code

Download the colour scheme (for Visual Studio 2008).

You can also get it from StudioStyles:

http://studiostyles.info/schemes/paulstovell-com

I thought it would be interesting to list out some of the technologies that run PaulStovell.com. I wonder how similar my setup is to others and any recommendations people have on improving it.

Infrastructure

The infrastructure that runs PaulStovell.com is housed in a massive dedicated data center in Brisbane. And by massive data center, I actually mean my apartment. Next to the TV in my living room is a black desktop that used to be a media center, until I installed Windows Server 2008 on it and started using it as a server. It's a quad core AMD Phenom, 64-bit, with 6 GB RAM. The machine is still connected to the TV via HDMI - if I need to change anything, I use my remote control to flip to HDMI 2 and use a wireless keyboard to log into the machine. I also tend to notice when the machine is offline since the room becomes a little quieter :)

The other hardware that makes it all happen is a wireless ADSL model from iiNet, and a 1 TB external hard disk that I use for backup. The internet comes through an iiNet business plan (business 3), which gives me upload speeds as fast as download speeds and a static IP address. I tend to get fast response times in Australia but I'm not sure what the experience is like overseas.

Source Control

All of my source code is kept in Subversion, at svn.paulstovell.com. It's actually the Visual SVN server, running on a local IP address of 10.1.1.30. Since I only have one external IP address, I have IIS 7 acting as a reverse proxy to the Visual SVN server (which is really just Apache) based on host name.

Builds

When I check code into source control, it's built automatically using JetBrains TeamCity, at build.paulstovell.com. Unfortunately since some of my builds make use of TeamCity's parameter system for passing information like passwords to builds, I can't make the build server public (I'd love some tips on parameterizing the builds safely so I can open it up).

The TeamCity build dashboard

It might seem overkill to have a build server at home, but one of the niceties is that I can check in a change for Magellan or PaulPad (below), and the build server can build it, run the tests and deploy it for me - everything is automated, which means I'm more likely to make small changes.

Blog

The site that you are looking at is a custom built Wiki+Blog engine that I wrote in a couple of weeks (after 6 months procrastinating) called PaulPad. The source code to PaulPad is also under Subversion. PaulPad was written ASP.NET MVC using NHibernate and a splash of jQuery. The content is composed using Markdown, and I use the reverse engineered WMD editor for composing the posts. I use Prettify for code highlighting because it allows me to keep the markup perfectly clean and the highlighting is done automatically. Comments are sent to Akismet for spam checking.

One of my favourite PaulPad features, apart from the clean markup, is that I can selectively publish page edits to an RSS feed when editing a page. For example, if I publish something, and correct a mistake a few minutes later, it will appear in the current feed. But in 6 months time, if I overhaul the page, I can "re-publish" it back to the feed so it appears in RSS readers again. That makes the blog+wiki concept really work for me.

File Server

When my TeamCity build server produces builds, I wanted to make them available via a page that simply showed files off the file system. However, the default IIS file list pages look a little ugly for me. So I created FileServer, which is the engine behind get.paulstovell.com.

Statistics

I use the SmarterStats package for web statistics. It's installed at stats.paulstovell.com, but there doesn't seem to be a way to make it public without giving everyone a login. I love that it's written in ASP.NET and the graphs use Silverlight. It also just looks nice. It's a shame my only visitor is my Mum :)

My SmarterStats dashboard for the last 7 days

To make the statistics more accurate, I send a 1x1px tracking image with all of the RSS feed entries that I serve up. The images all point to "/(page-name)/via-feed", which allows me to see pretty accurately how many people read my content via feeds as opposed to viewing the pages directly. I'd like to know if anyone thinks this is dishonest.

Uptime

It's not that I don't trust myself, but, I have a habbit of monkeying around with my server, which impacts the general uptime. I try to keep it to a limit, and I use Pingdom.com to give me statistics about my uptime. Pingdom can also SMS me when the site is down, but given that the computer is in my lounge, I usually know when it's down, and if I'm not home I'm probably unlikely to fix it.

You can see the Pingdom graphs online: www.pingdom.com/reports/8vrndjlwezen/.

Mail

This is about the only thing I don't host myself. I use Google Apps to handle paulstovell.com mail.

Backups

Given how half-baked this whole setup is, I figure I should at least have a good backup system. I use MozyPro to upload all content, database backups, and Subversion stores as soon as changes are made and also during a nightly backup process. I also use MozyPro on my laptop.

I also use a 1 TB external hard drive, and a scheduled task in Windows to robocopy a set of folders to the drive. It's really a "just in case" should the internet go down while I'm out of town, and it's also just nice to have more backups.

For the database I've setup a maintenance task that backs up all databases every four hours and ships them to a folder to be backed up by Mozy. I recently had to restore a backup from Mozy for the PaulStovell.com database after, well, I screwed up :) The backup worked well, so it's nice to know I can rely on it. I should probably make a point to test my backups more often.

Summary

In the past I have used shared web hosting, virtual machine hosting and dedicated hosting with a few different providers, but I always missed the kind of control I wanted. Even with dedicated hosting, the thought of having to remote desktop into the servers usually put me off, not to mention the costs. Hosting it all in house was very worthwhile, and given my Pingdom uptime of 97% I'm pretty happy with the reliability.

Something I'm very proud of is that there isn't a single line of PHP or Perl on my setup - it's all .NET (with a little Apache and Java). In fact PaulPad's best feature is that it isn't Wordpress :)

The Subversion and Team City combination works really well together, and because everything is on the LAN it is very fast. I notice my workflow at home is very different to my workflow at work. Here's my build log from TeamCity when I was working on Magellan:

TeamCity build log

What other services should I try? What are some alternatives that you prefer?

I just added a page on Magellan Action Filters. One of the reasons I'm excited about this is that action filters and view filters will allow me to handle a common and complicated issue - re-activating an existing view.

Take a controller action like this:

public ActionResult Show(int customerId)
{
    var customer = Customers.Find(customerId);
    Model = new CustomerDetailsModel(customer);
    return Window("CustomerDetails");    
}

If you clicked a pair of customers in a list, you would end up with a couple of Windows open, one for each customer as expected. However, if you clicked the same customer twice, you would also end up with two Windows - this is contrary to what most people would expect. Instead, most people would expect the first Window to be re-activated. This typically isn't an issue on the web, but it's a common issue for smart clients.

Action filters could combine with view filters to re-use the view automatically. For example, suppose we added this filter:

[ReuseView(CacheByParameters = new[] { "customerId" })]
public ActionResult Show(int customerId)

When executed, this action filter would track whether the action has already been executed. If it had, it would return a result that allowed the previous Window to be re-activated instead of executing it again. This provides a very cross-cutting way of solving a common problem, by combining two simple pieces of architecture.

The ReuseView action and result filter could be implemented as follows:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ReuseViewAttribute : Attribute, IActionFilter, IResultFilter
{
    private static readonly Dictionary<string, WeakReference> _windowReferences = new Dictionary<string, WeakReference>();

    public ReuseViewAttribute()
    {
    }

    public string[] CacheByParameters { get; set; }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var cacheKey = CalculateCacheKey(context.ControllerContext);
        var window = GetFromCache(cacheKey);
        if (window != null)
        {
            window.Activate();
            context.OverrideResult = new CancelResult();
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {

    }

    public void OnResultExecuting(ResultExecutingContext context)
    {

    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        if (context.Result is ViewResult == false)
            return;

        var cacheKey = CalculateCacheKey(context.ControllerContext);
        AddToCache(cacheKey, context.Result);
    }

    private string CalculateCacheKey(ControllerContext controllerContext)
    {
        return string.Format("Controller={0};Action={1};Params=[{2}]",
            controllerContext.Request.Controller,
            controllerContext.Request.Action,
            string.Join(",",
                controllerContext.Request.ActionParameters
                .Where(x => CacheByParameters.Contains(x.Key))
                .Select(x => x.Key + "=" + x.Value.ToString())
                .ToArray()
                )
            );
    }

    private static Window GetFromCache(string cacheKey)
    {
        if (!_windowReferences.ContainsKey(cacheKey))
            return null;

        var window = _windowReferences[cacheKey].Target as Window;
        if (window == null || !window.IsVisible)
            return null;

        return window;
    }

    private static void AddToCache(string cacheKey, ActionResult result)
    {
        var viewResult = result as ViewResult;
        if (viewResult == null)
            return;

        var windowViewEngineResult = viewResult.ViewEngineResult as WindowViewEngineResult;
        if (windowViewEngineResult == null)
            return;

        var window = windowViewEngineResult.RenderedInstance;
        _windowReferences[cacheKey] = new WeakReference(window);
    }
}

Download the Sample

I am tempted to pull this into Magellan, but I think it's a scenario that needs more thought and would probably need to be customized per application, so a fully generic solution might be hard to achieve. What do you think? Perhaps it's time for a Magellan Contrib? :)