Clean shutdown in Silverlight and WPF applications

.NET, Blend, MVVM, Silverlight, Technical stuff, Work, WPF
No Comments

This post was imported from my old blog and had 13 comments which are included as a screenshot at the end of this post.

Update: There was a small error in the Silverlight version of the application. The code used to navigate to a different webpage was wrong (in ApplicationExtensions.cs). I wrote this article in the Austrian mountains and did not have the web to check the code, sorry about that ;) I updated the source code. If you downloaded the source code before the 19th of October, 8AM GMT, you want to load the correct version. Apologies!!

It is often difficult for large applications with multiple, loosely coupled components to cleanly shut down. Once a clean shutdown procedure is established, adding new components can disrupt the whole sequence, and things get ugly fast.

In this article, we will show a way to use a Messenger to create a loosely coupled shutdown sequence, making it easy to add new components if needed. For the sake of the demonstration, we will use the MVVM Light Toolkit Messenger, but of course this can also be realized with other messaging systems.

As a bonus, the sample application will also show how to use the same Messenger class to enable communication between two ViewModels that don’t know each other, making it very easy to refactor the application or to modify the client application (for example by adding new screens, etc…).

CleanShutdown demo from Laurent Bugnion on Vimeo.

What is a clean shutdown sequence?

Easy to request shutdown

To cleanly shut down a system, we need a central point from which the shutdown sequence can be started. This central point (we’ll call it the ShutdownService) should be reachable from any place in the application in an easy way. This way, if we add new components, they can request shut down without the ShutdownService needing any modification.

Possibility to interrupt shutdown

Some components might need a little more time to process important information, to display a “shutdown animation”, to log some information to a web service, etc. Some of these operations might be asynchronous, meaning that the shutdown sequence should be interrupted temporarily. This should be implemented in a way that allows adding new components without the ShutdownService or other components being changed.

Being notified upon shutdown

Some components might need to be notified when shutdown is happening, for example to save important information to the disk. Here too, we need to be able to add new components without changing anything else.

Requesting shutdown

This is quite easy: we simply provide a static method in the ShutdownService class. This method called RequestShutdown can be called by any object in the application. A more sophisticated implementation could include a reason for shutdown, letting the ShutdownService decide if the complete shutdown sequence must be executed, of if it should be ignored (for example, in case of a critical application error, a “catastrophic shutdown”, it could be dangerous or impossible to attempt a clean shutdown. Similarly, we could avoid to use a static method, and instead store an instance of the ShutdownService in an IoC container.

Giving everyone a possibility to interrupt shutdown

The ShutdownService provides a possibility for any object in the application to interrupt shutdown. We use the MVVM Light Toolkit’s Messenger class so that objects can register to be notified when a shutdown sequence starts, and to interrupt it if needed. We will use a NotificationMessageAction<bool>. This message type contains:

  • A string specifying which operation needs to be initiated. In the case of the shutdown sequence, we will send a string meaning ConfirmShutdown. The recipient of the message can take the opportunity to confirm that shutdown is OK, or to request shutdown to be stopped.
  • A callback method (of type Action<bool>) that the recipient can call to prevent shutdown from happening. If the recipient calls the callback with true, the shutdown sequence will be interrupted. If it calls the callback with false, (or if it doesn’t call it at all), the shutdown sequence will continue.

Note that the recipient that interrupts shutdown is responsible for requesting shutdown again, after the asynchronous operation has completed. A more elaborate implementation could use a timeout to force shutdown if the asynchronous operation still did not complete after a certain time.

Notifying of imminent shutdown

When shutdown is really happening, a last message will be sent to notify recipients that this time, it’s for real. this gives a unique opportunity to recipients to take last minute measures, such as saving their state to a file, etc. In the sample application, we use a CommandMessage with a string meaning NotifyShutdown. Recipients who subscribed to this message can do what needs to be done.

Let’s see some code

So time to see some code. The source code can be downloaded here. The application can be run in WPF or in Silverlight, and looks like this:

2009101701

  • The 3 sliders are used to change the color of the application’s background.
  • The big X is used to close the application.
  • On closing, the values of the 3 sliders are saved to a file.
  • When the application starts, the saved values are read from the file and set.
  • An animation is played when the application is closed (rotation, scale animation and fade out).

ShutdownService

This class exposes one static method used by other objects to request shutdown. The class is instantiated on startup by the Application object (in App.xaml.cs).

public static void RequestShutdown()
{
    var shouldAbortShutdown = false;

    Messenger.Default.Send(new NotificationMessageAction<bool>(
        Notifications.ConfirmShutdown,
        shouldAbort => shouldAbortShutdown |= shouldAbort));

    if (!shouldAbortShutdown)
    {
        // This time it is for real
        Messenger.Default.Send(new CommandMessage(Notifications.NotifyShutdown));

        Application.Current.Shutdown();
    }
}
  • We start by setting a local variable to false.
  • Then we send a message that interested objects can register for. This provides them with a callback that they can call to interrupt shutdown. The callback is implemented as a lambda expression. If at least one recipient replies true, the variable shouldAbortShutdown is set to true.
  • Finally, if shouldAbortShutdown is false (meaning that noone wants to interrupt), we send another message, this time announcing NotifyShutdown). If anyone registers for this message, they will know that the shutdown is imminent.
  • The last line shuts down the application.

Playing an animation on shutdown

In our sample application, we want to play an animation when the application is shut down. Animations are asynchronous, so we need to temporarily interrupt the shutdown sequence. To do this, we register for the message type NotificationMessageAction<bool>.

To make it easier to share this code between Silverlight and WPF, we place this code in a class called ShutdownAnimationService. This class is instantiated in the MainWindow (for WPF) or in the Page (for Silverlight).

Note: Some like to see MVVM applications without any code in the code-behind of a View. However, playing animations, showing popups and other functionalities are really the responsibility of the View. There is nothing wrong in placing code in the View, as long as it is View-specific!

Messenger.Default.Register<NotificationMessageAction<bool>>(this, message =>
{
    if (message.Command == Notifications.ConfirmShutdown)
    {
        if (!_shutdownAnimationHasRun)
        {
            var sbd = this.Resources["ShutdownStoryboard"] as Storyboard;
            if (sbd != null)
            {
                message.Execute(true); // true == abort shutdown

                sbd.Completed += ShutdownStoryboardCompleted;
                sbd.Begin();
            }
        }

        // If the animation ran already, no need to reply
        // to the message, allow shutdown.
    }
});
  • When a message of type NotificationMessageAction<bool> is sent (which is what the ShutdownService does), the lambda expression is executed.
  • First we check a global variable that tells us if the animation ran already.
  • If it didn’t run yet, we check if there is a Storyboard named ShutdownStoryboard in the resources. If we don’t find it, we let the shutdown happen (this is just a failsafe in case something went wrong with the Storyboard).
  • If we do find the storyboard, we execute the callback with true meaning that we interrupt the shutdown sequence.
  • Finally, we assign a “completed” event to the Storyboard and we start it.

Remember that it is now the responsibility of this object to restart the shutdown sequence! It is done in the completed event of the Storyboard:

void ShutdownStoryboardCompleted(object sender, System.EventArgs e)
{
    _shutdownAnimationHasRun = true;

    // Now that our pre-shutdown task is done
    // we can request shutdown again.
    ShutdownService.RequestShutdown();
}

Note: The animation is defined in the Page.xaml (for Silverlight) and in MainWindow.xaml (for WPF) and not in MainSkin.xaml. While storing the animation in the external resource dictionary would work in WPF, it fails in Silverlight.

Saving settings to a file

The class SettingsViewModel defines three properties (Red, Green and Blue) that are databound to the 3 sliders. In fact, the SettingsViewModel is the DataContext of the half-white panel on the left of the X button.

public SettingsViewModel()
{
    if (!IsInDesignMode)
    {
        Messenger.Default.Register<CommandMessage>(this, m =>
        {
            if (m.Command == Notifications.NotifyShutdown)
            {
                SaveSettings();
            }
        });
        
        _settingsFile = new SettingsFileHandler();

        var savedSettings = _settingsFile.LoadSettings();
        Messenger.Default.Send<Brush, MainViewModel>(
            savedSettings.ApplicationBackgroundBrush);

        _isLoading = true;

        var solidBrush
            = savedSettings.ApplicationBackgroundBrush as SolidColorBrush;
        if (solidBrush != null)
        {
            Red = solidBrush.Color.R;
            Green = solidBrush.Color.G;
            Blue = solidBrush.Color.B;
        }

        _isLoading = false;
    }
}
  • SettingsViewModel registers to receive messages of type CommandMessage. If the CommandMessage’s content is NotifyShutdown, it means the application is closing, and the object saves its state in a file.
  • The rest of the constructor is used to load existing settings, and send an initial message to the MainViewModel to set the saved brush as the application background (see the section below titled “Sending messages from a ViewModel to another” for more details).
  • Finally, we set the sliders to values corresponding to the saved ones.

The saving itself happens in SettingsFileHandler, which is also responsible for loading the settings when the application starts.

  • In WPF, saving the settings and reading them back is made with the XamlWriter, resp XamlReader. These two classes are very useful to save any CLR object to a XAML file, and to restore it. In this sample, we save to the Temp folder, but a normal implementation would save to the AppData folder.
  • In Silverlight, we don’t have XamlWriter, so we save to a plain text file in the IsolatedStorage instead, as explained in the section titled “Specific implementation details for Silverlight”.

Requesting shutdown when the button is pressed

The MainViewModel is set as the DataContext of the application’s window (in Silverlight, of the Page), with the exception of the half-white area with the 3 sliders (which is bound to the SettingsViewModel). Clicking the X button executes a RelayCommand defined in the MainViewModel. This command is very simple: It just calls the RequestShutdown method.

ShutdownCommand = new RelayCommand(ShutdownService.RequestShutdown);

Sending messages from a ViewModel to another

Another thing the MainViewModel does it define a bindable property called BackgroundBrush. This property, when updated, automatically sets the background of the application (through databinding). However, how do we know when the user changes the Brush?

In this simple application, we don’t really need two ViewModels. However, it makes sense to have them: If we decided to move the 3 sliders to a popup, for example, or to another window, it would be very easy to do, thanks to the separation of concerns: MainViewModel takes care of the MainWindow, SettingsViewModel takes care of the settings.

This poses a problem however: Since both ViewModels are separated, how can they communicate without being tightly coupled, which we really want to avoid. Well this is another usage for the Messenger: The MainViewModel registers for any message of type Brush:

public MainViewModel()
{
    if (IsInDesignMode)
    {
        BackgroundBrush = new SolidColorBrush(Colors.Orange);
    }
    else
    {
        Messenger.Default.Register<Brush>(
            this, 
            true, 
            m => BackgroundBrush = m);

        ShutdownCommand
            = new RelayCommand(ShutdownService.RequestShutdown);
    }
}
  • Whenever a message of type Brush is received, we just set the BackgroundBrush to it.
    The Messenger class can be used to send any message type, from simple values to complex objects!

  • Note that we use an overload of the Register application: The second parameter is set to true, indicating that we want to receive all messages of type Brush, and derived types too (SolidColorBrush, LinearGradientBrush, etc…).

  • If we are in design mode (running in Expression Blend for example), we set the brush to Orange, so that we have something to see (otherwise the window would be transparent).

Finally, we need someone to send updates! This is the SettingsViewModel’s task: When the user moves the sliders, we send a message of type SolidColorBrush, corresponding to the value of the 3 sliders. We do, however, send this message only to MainViewModel, thus avoiding too much unnecessary communication.

private void SendBrushUpdate()
{
    if (_isLoading)
    {
        return;
    }

    Messenger.Default.Send<Brush, MainViewModel>(
      GetCurrentBrush());
}

private Brush GetCurrentBrush()
{
    return new SolidColorBrush(
      Color.FromArgb(255, Red, Green, Blue));
}

Specific implementation details for Silverlight

  • Most objects in this application are shared between WPF and Silverlight.
    Reminder: To share objects, you can right click on a folder, select “Add, Existing Item” from the context menu. Browse to the item you want to share, then instead of clicking on the “Add” button, click on the small arrow within this button, and choose “Add as a Link” from the menu that opens.
  • Instead of saving the settings to the file system (which is not allowed in Silverlight), we save to the IsolatedStorage. The class SettingsFileHandler is not the same in Silverlight than in WPF, and takes care of this implementation detail.
  • Similarly, we do not have a XAML Writer in Silverlight, unfortunately. Instead of saving the settings to XAML (which is very easy and convenient), we use an extension of the partial class Settings (available only in the Silverlight version of the application) to serialize to a text format, and to deserialize from it.
  • Finally, we cannot shut down the application in Silverlight. We can, however, navigate to a different web page. Since the Shutdown() method doesn’t exist in the Application class in Silverlight, we provide an extension method (in the class ApplicationExtensions) with the same name.

These few techniques (partial class with specific Silverlight extensions, extension methods, adding items as link) help to “bridge the gap” between WPF and Silverlight and to write less code while providing similar functionality.

About NotificationMessageAction

This useful message type (allowing to send a notification and providing a callback to the recipient) is not yet part of the MVVM Light Toolkit V2, but will be included in future versions. An implementation is provided as part of the sample application, but is not final yet. Comments or feedback is welcomed!

Source code

The sample application’s source code can be downloaded from my site.

Conclusion

This sample application shows two usages of the Messenger class:

  • Enable communication between decoupled ViewModels.
  • Create a clean shutdown sequence that can easily be extended with new objects if necessary.

There are of course many other situations where a Messenger can be useful in an application. I hope to have awaken your interest for this very powerful yet simple to use component. For more information about the Messenger, RelayCommands or other parts of the MVVM Light Toolkit, please refer to the Get Started page.

GalaSoft Laurent Bugnion
Laurent Bugnion (GalaSoft)

Share on Facebook

This post was imported from my old blog. Original comments screenshot: 12-14-2013 5-06-52 PM

Leave a Reply