Skip Navigation LinksHome > View Post

Taming ClickOnce – taking charge of updates

What’s not to love about ClickOnce? I’ll tell you what…

image

There’s no doubt that ClickOnce is a killer deployment technology with many of the benefits of web deployment combined with the rock-hard awesomeness of juicy-fat Windows applications. However, a little piece of me dies whenever I see an otherwise slick application using the mostly lame Launching Application window above (which is almost always inexplicably slow too).

Fortunately, you can still deploy your application using ClickOnce but take charge of the updating mechanism and lose the aforementioned Launching Application window.

I have a number of ClickOnce deployed applications that I look after and so I’ve got used to manually dealing with the update process. I tend to prefer to update in the background and let the user get on with using the version of the application they already have installed. If an update is available I automatically download it and, once installed, prompt the user to ask if they’d like to restart using the new version. Here’s the typical flow these applications follow when background updating. I’ve simplified the code for brevity and clarity – normally there’d be a heap of logging to help me diagnose any issues.

public void StartBackgroundUpdateAsync()
{
    if (!ApplicationDeployment.IsNetworkDeployed)
    {
        // not ClickOnce deployed
        return;
    }

    // Why use the ThreadPool instead of CheckForUpdateAsync?
    // Some network condition, e.g. Hotel or Guest Wi-Fi, can
    // make ClickOnce throw internally in such a way the exception
    // is tricky to catch. Instead, use CheckForUpdates (sync)
    // and handle the exception directly.
    ThreadPool.QueueUserWorkItem(delegate
    {
        try
        {
            var deployment = ApplicationDeployment.CurrentDeployment;

            if (deployment.CheckForUpdate())
            {
                deployment.UpdateCompleted +=
                    new AsyncCompletedEventHandler(
                        deployment_UpdateCompleted);
                deployment.UpdateAsync();
            }
        }
        catch (Exception exc)
        {
            // TODO log exception here.
        }
    });
}



void deployment_UpdateCompleted(object sender,
    AsyncCompletedEventArgs e)
{
    if (e.Error != null)
    {
        // TODO log exception here.
    }

    var dispatcher = Application.Current.Dispatcher;

    // Update installed, prompt the user to see if they'd like to
    // nowrestart
    dispatcher.BeginInvoke((Action)delegate
    {
        if (MessageBox.Show(
            "Application has been updated, restart now?",
            "Restart?",
            MessageBoxButton.YesNo,
            MessageBoxImage.Question) == MessageBoxResult.Yes)
        {
            System.Windows.Forms.Application.Restart();
            dispatcher.BeginInvokeShutdown(
                DispatcherPriority.Normal);
        }
    });
}

There are some interesting pieces to this too. Note how instead of using the CheckForUpdateAsync method, I use its synchronous counterpart but on a ThreadPool thread. This is because the act of checking for updates can baulk in a pretty unpleasant way in some network conditions. I think one example that tripped me up was when the host PC was connected to a hotel network that required you to log in before using the network. Typically, these networks return their login page to all HTTP requests until the user authenticates. This seemed to confuse the life out of ClickOnce and generated an exception deep down on a background thread where I couldn’t easily catch it. This was relatively easily fixed by using CheckForUpdates wrapped in a Try/Catch block on a ThreadPool thread.

Finally, to rid yourself of the Launching Application window once and for all you just need to select the appropriate options in your project’s Publish tab:

 image 

The applicaiton must support offline mode and in the Updates dialog you should disable the checkbox ‘The application should check for updates’. This seems counter-intuitive but is the correct option because you’ll be taking charge of the updates in your code-base.

If you really want to dress your applicaitons updates up to the nines you can subscribe to a heap of other events on the ApplicationDeployment type. Including, for example, UpdateProgressChanged event which would allow you to implement a super-slick progress bar.

Happy updating!

Tags: .NET ClickOnce C#

 
Josh Post By Josh Twist
1:18 PM
14 Sep 2010

» Next Post: Cloud Artwork
« Previous Post: Learning MVC: Backwards compatible routes

Comments are closed for this post.

Posted by Mike Stles @ 14 Dec 2010 5:07 PM
Implementation in VB.NET

I was able to get the code to function in VB.NET. However, the Launching Application window still shows.

Posted by Alex Marshall @ 04 Feb 2011 1:42 PM
Thank you for sharing this information, we use ClickOnce to provide software updates for a road surveying application and the surveyors often use hotel wi-fi whilst out on the road - and this was causing us loads of problems! They weren't able to open the app without disconnecting from the wi-fi first!

© 2005 - 2014 Josh Twist - All Rights Reserved.