Dennis van der Stelt

The most votes generally drown out the best votes

Community

News

  • Meet me at PDC08

Email Notifications

I read...

I Use...

Tags

Recent Posts

Archives

Manual check for updates with ClickOnce

I really love [wikipedia:ClickOnce], because it removes the necessity to build a web application. In my years as a developer, I can't count the projects that were web based, simply because of the deployment model. And then ClickOnce came around. Now I can't count the times that I've explained during a training what ClickOnce is and why everyone should love it!

clickonce_autoupdate

Most people use the default behavior of ClickOnce, which presents a dialog window, checking for updates, as shown in the dialog above. I discourage people to use this default behavior because of two reasons. The first reason is that it's plain ugly and annoying. The second reason is that when an update is available but people skip the update process, they're not asked again for installation of the update, until a new version is deployed. We can solve this by manually checking for updates and this is even more interesting when doing it asynchronously.

Here's the simplest code for checking for an update. Be sure to include a reference to System.Deployment and a using to System.Deployment.Application.

ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;

UpdateCheckInfo info = updateCheck.CheckForDetailedUpdate();

//

if (info.UpdateAvailable)

{

  updateCheck.Update();

  MessageBox.Show("The application has been upgraded, and will now restart.");

  Application.Restart();

}

Here you can see we gather information. There's also a property IsUpdateRequired which you can use to force users to upgrade (which is happening in this example, normally you'd give users a choice to update). In the default ClickOnce behavior, this results in automatic update, instead of being asked to update the application.

Of course we can add error checking and a BackgroundWorker to make things a little more pretty. We'll start out with the BackgroundWorker, bind the events for when it should perform work and for when it's completed and then start it up.
Note : In this example, we'll be adding everything in the main form, but it's better to apply the [wikipedia:Single_responsibility_principle] and delegate this to some helper classes.

BackgroundWorker bgWorker = new BackgroundWorker();

bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);

bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorder_RunWorkerCompleted);

bgWorker.RunWorkerAsync();

 

Now we'll have to add the code to peform the check. It's partially the code from code-sample #1, without the updating part.

private enum UpdateStatuses

{

  NoUpdateAvailable,

  UpdateAvailable,

  UpdateRequired,

  NotDeployedViaClickOnce,

  DeploymentDownloadException,

  InvalidDeploymentException,

  InvalidOperationException

}

 

/// <summary>

/// Will be executed when works needs to be done

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void bgWorker_DoWork(object sender, DoWorkEventArgs e)

{

  UpdateCheckInfo info = null;

 

  // Check if the application was deployed via ClickOnce.

  if (!ApplicationDeployment.IsNetworkDeployed)

  {

    e.Result = UpdateStatuses.NotDeployedViaClickOnce;

    return;

  }

 

  ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;

 

  try

  {

    info = updateCheck.CheckForDetailedUpdate();

  }

  catch (DeploymentDownloadException dde)

  {

    e.Result = UpdateStatuses.DeploymentDownloadException;

    return;

  }

  catch (InvalidDeploymentException ide)

  {

    e.Result = UpdateStatuses.InvalidDeploymentException;

    return;

  }

  catch (InvalidOperationException ioe)

  {

    e.Result = UpdateStatuses.InvalidOperationException;

    return;

  }

 

  if (info.UpdateAvailable)

    if (info.IsUpdateRequired)

      e.Result = UpdateStatuses.UpdateRequired;

    else

      e.Result = UpdateStatuses.UpdateAvailable;

  else

    e.Result = UpdateStatuses.NoUpdateAvailable;

}

The first thing you'll notice is that I've added an enumeration. This is for passing a result back to the method that's called when we're done.
Note : This can be done more gracefully, but it'll do for now without a lot of extra code. And it's not even real ugly! ;-)

In the bgWorker_DoWork method we first check if the application is deployed via ClickOnce. Then we gather the detailed information about a possible update and perform some error handling if things don't go the way we expect. Finally we check if an update is available and wether or not it's a required update. We pass this information into the result and end this method.

Now we're in need of the method when the work is done...

/// <summary>

/// Will be executed once it's complete...

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void bgWorder_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

{

  switch ((UpdateStatuses)e.Result)

  {

    case UpdateStatuses.NoUpdateAvailable:

      // No update available, do nothing

      MessageBox.Show("There's no update, thanks...");

      break;

    case UpdateStatuses.UpdateAvailable:

      DialogResult dialogResult = MessageBox.Show("An update is available. Would you like to update the application now?", "Update available", MessageBoxButtons.OKCancel);

      if (dialogResult == DialogResult.OK)

        UpdateApplication();

      break;

    case UpdateStatuses.UpdateRequired:

      MessageBox.Show("A required update is available, which will be installed now", "Update available", MessageBoxButtons.OK);

      UpdateApplication();

      break;

    case UpdateStatuses.NotDeployedViaClickOnce:

      MessageBox.Show("Is this deployed via ClickOnce?");

      break;

    case UpdateStatuses.DeploymentDownloadException:

      MessageBox.Show("Whoops, couldn't retrieve info on this app...");

      break;

    case UpdateStatuses.InvalidDeploymentException:

      MessageBox.Show("Cannot check for a new version. ClickOnce deployment is corrupt!");

      break;

    case UpdateStatuses.InvalidOperationException:

      MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application.");

      break;

    default:

      MessageBox.Show("Huh?");

      break;

  }

}

Here you'll see responses to most error messages and them just showing up in a message box. Interesting is the NotDeployedViaClickOnce status, as this pops up every time you're debugging your application from Visual Studio. You might want to remove that message box. You also might want to remove the messagebox for when no update is available, as users aren't really interested in that. The final default switch should never occur.

Interesting are the switches for when there is an update, and when it's required. In the first case the user can ignore the update which he can't in the case it's required. You might think of something so the user won't be asked dozens of times every time the application is started.

Now we only need the UpdateApplication method.

private void UpdateApplication()

{

  try

  {

    ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;

    updateCheck.Update();

    MessageBox.Show("The application has been upgraded, and will now restart.");

    Application.Restart();

  }

  catch (DeploymentDownloadException dde)

  {

    MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);

    return;

  }

}

Here we again do some error checking for when things go wrong. But all we're doing here is update the application and restart it.

This is now all happening in the background without the users being notified when there's no update and without the annoying popup dialog. It's also really easy to create your own "Check for updates" button or menu option now.

Have fun with ClickOnce and forget about those awful web based applications! ;-)

UPDATE : Here's how you can turn off automatic updates with ClickOnce.

UPDATE 2 : Here's an example solution, without the BackgroundWorker.

Technorati Tags:

Comments

Dennis van der Stelt said:

It's amazing to see how many people can find this blog entry via Google but don't provide any comments or anything! :)

# November 20, 2007 10:38 PM

Dennis van der Stelt said:

I got an e-mail with the question how to turn off automatic checking for updates, when you&#39;re doing

# November 28, 2007 2:40 PM

Antonio said:

Hi,

This post is really interesting. I'm researching on clickOnce to see if we can using in my job. Everything looks great, but I have a problem. For some policies of the company, I need to change the installation path. I can't have clickOnce to install my apps per user in the cache folder.  In my research I found that this is not possible. So I wonder if I can make a classic msi installer to setup the app for the first time (and have it installed where I want), but still using clickOnce for checking updates using your code example.

How can I make the app to 'think' that has been deploy using ClickOnce, so the NotDeployedViaClickOnce status says so?

Is this possible?

# December 11, 2007 6:18 PM

Douglas Ross said:

Hi,

Thanks for the article, it is exactly what I am looking for..... Unfortunately, I am not that good a coder, and do not understand exactly where to make these changes.

I also cannot find where MS Studio makes the wizard changes (other than in properties).

I thought that if I found the default stuff set up by the wizard then I should be able to overwrite it with the delegations you have written above.

Would you be able to post an example forms C# project, that shows exactly where you place this code.

Apologies in advance, if I am being terribly simple and the answer is obvious.

Douglas

# February 3, 2008 10:10 AM

Dennis van der Stelt said:

Hi Douglas,

I'm giving a training today. If I find the time between practices or so, I'll create and upload a solution.

# February 4, 2008 8:50 AM

Dennis van der Stelt said:

Seems like I did it faster than I expected. I added the example for VS2005 without the BackgroundWorker.

The link is just below the article, as UPDATE 2.

If you want one with the BackgroundWorker included, or anything else, let me know.

# February 4, 2008 1:51 PM

nicolas said:

Hi,

How can I manually specify the update location.

The application will be installed in multiple clients where internet is not available. So I need the administrator of each of our clients to manually copy the files to a specific location so that all clients can be updated.

# April 21, 2008 8:57 AM

Rob Kent said:

The main issues I have with ClickOnce are the install directory and config overwrites. Let's assume I want my users to be able to manually update the app.config - firstly, how do they know where it is? Secondly, what happens on next update and I have made changes to the central copy - this would overwrite the customer's changes.

I've looked at lots of ClickOnce articles and have still not seen a satisfactory solution to this problem.

# May 14, 2008 11:40 AM

Constandinos Iezekiel said:

Very helpful article Dennis. It helped a lot :)

# September 22, 2008 10:39 AM

Muttly said:

Hi,

It's an interesting article Dennis.

Without sounding like a complete retard (I have my moments!), what files do we need to put where to set up updates?

Thanks in advance for your help!

Muttly

# October 28, 2008 6:11 PM

Dennis van der Stelt said:

I am preparing a post about doing builds with FinalBuilder, explaining also the entire process that needs to be done if you'd do this by hand.

Hold on for it, I'll finish it any time soon.

# November 4, 2008 10:26 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)