The philosophy behind NAppUpdate

.NET, English posts, NAppUpdate, WinForms, WPF

Comments

8 min read
About 2 years ago I was building a .NET desktop application, and needed an easy way to allow it to auto-update itself. I looked for libraries that do that, and all I could see was either complicated, commercial, or geared towards a very particular (usually common) use case. I didn't want anything of the sort, so this is how NAppUpdate was born. NAppUpdate is a very lightweight library, taking no dependencies and doesn't use anything fancy (it actually runs on .NET 2.0). It was designed to be able to perform any update process you can think of, and do all the heavy lifting for you. Using very few simple API calls you can get your application to self-update from the web, local network, BitTorrent or whatever, and along the way perform DB schema updates, registry changes, additional installations and what not. Some functionality is supported out-of-the-box, and whatever is not - can be very easily added. With very few lines of code you can make it behave any way you want. NAppUpdate is being used quite widely, but has nearly no documentation. I believe simple software doesn't really need docs, although some sort of explanation on how it works and what it is capable of doing is still important to have. This is what this post is about, and while at it I will discuss some of the key principle behind the design of the library. For future readers, please note all code samples work with version 0.2.

Getting started

To get started, you will need to reference the NAppUpdate DLL (only one DLL) from your project. Grab the latest release binaries or compile from source. NAppUpdate is implemented as a singleton, and the public facing API is called UpdateManager. Once you got it referenced, all the operations will be available to you using UpdateManager.Instance. You don't need to initialize anything, it will just work. NAppUpdate only needs to know where to get the data from, but that's about the only thing you will need to do to get started. This is done by simply providing NAppUpdate with an IUpdateSource implementation. Bundled with NAppUpdate currently are basic implementations for getting data from the web using SimpleWebSource (HTTP, FTP, Proxies and all that stuff), from a UNC source, and an in-memory source. Common usage would look something like this - and you want to put it when your app starts: [code lang="csharp"] UpdateManager.Instance.UpdateSource = new NAppUpdate.Framework.Sources.SimpleWebSource("http://mydomain.com/feed.xml"); // provided is the URL for the updates feed UpdateManager.Instance.ReinstateIfRestarted(); // required to be able to restore state after app restart [/code]

Checking for updates

It's as simple as it gets: [code lang="csharp"] if (UpdateManager.Instance.CheckForUpdates()) { DialogResult dr = MessageBox.Show( string.Format("Updates are available to your software ({0} total). Do you want to download and prepare them now? You can always do this at a later time.", UpdateManager.Instance.UpdatesAvailable), "Software updates available", MessageBoxButtons.YesNo); if (dr == DialogResult.Yes) { UpdateManager.Instance.PrepareUpdatesAsync(OnPrepareUpdatesCompleted); } } else { MessageBox.Show("Your software is up to date"); } [/code] You can do this in a blocking manner as shown above, or async. Since this will usually involve network traffic, it is recommended to have this running on a non-UI thread. If you don't have any spare thread handy, just call CheckForUpdatesAsync, it will do all the heavy lifting for you. The common practice is to call CheckForUpdateAsync with a callback. In the call back you can handle the news of new updates as you see fit. CheckForUpdates will retrieve the updates feed using the IUpdateSource implementation you provided originally (or you can pass it a new one), and will parse it to produce a list of update tasks. The feed is parsed using an IUpdateFeedReader implementation. You can roll your own, or use NauXml.

NauXml: Tasks and Conditions

Internally, NAppUpdate executes update tasks (concrete classes implementing IUpdateTask), and allows you to define conditions on them. Task without any conditions, or with trivial ones, will always execute. Conditions are simply concrete classes implementing the interface IUpdateCondition. There is quite a handful of them built-in, like FileVersionCondition, FileChecksumCondition, OSCondition and many more. It is quite trivial to add any other condition as well. To reflect that structure in the best way possible, NAppUpdate defines an XML schema we call NauXml. It is quite trivial to understand what's going on, and to write one yourself: [code lang="xml"] <?xml version="1.0" encoding="utf-8"?> <Feed> <Tasks> <FileUpdateTask hotswap="true" updateTo="http://SomeSite.com/Files/NewVersion.dll" localPath="CurrentVersion.dll"> <Description>Fixes a bug where versions should be odd numbers.</Description> <Conditions> <FileChecksumCondition checksumType="sha256" checksum="6B00EF281C30E6F2004B9C062345DF9ADB3C513710515EDD96F15483CA33D2E0" /> <FileDateCondition type="or" what="is" timestamp="20091010T000000" /> </Conditions> </FileUpdateTask> </Tasks> </Feed> [/code] NAppUpdate has an appropriate feed reader for the NauXml format built-in, obviously, and it is the default FeedReader implementation used. You can use any other format by handing NAU another IUpdateFeedReader implementation.

Preparing updates

So, you were notified of new updates, now what? You could either notify the user of them and start preparing them if the user wishes to proceed (like in the example shown above), or prepare them silently and only notify the user when everything is ready to roll. It's completely up to you, just like the way you would be notifying them about the updates. You can track the progress of the update preparation by subscribing to the UpdateManager.Instance.ReportProgress event. It will notify you of the general progress, and which task is currently preparing itself. There is a code sample showing exactly that in the github repository (available also within the download). The preparation process is defined by doing all the lengthy process required for an update, without changing anything in your system. As such, it is completely safe to abort it, and no rolling back is required.

Applying updates

Once everything is prepared, all you have to do (probably after getting the user's consent) is call UpdateManager.Instance.ApplyUpdates(bool restartApplication). This will apply the updates. Some update tasks might require a cold-update, meaning they cannot complete while the application is running. This is either by request in the feed, or the task tried updating while the application is working, failed, and fell back to requesting a cold update. If no cold-updates are required, the update process will finish here. If there are any cold updates pending, the application will restart itself and apply them when it is off. You can ask NAppUpdate to bring the app back up after performing the update. You also defer the update process to be performed when the user exists the application. This will ensure the process doesn't get in his/her way, and again, it is a matter of simply calling ApplyUpdates(false) on close. Applying updates has to be called from the main UI thread if it may involve shutting down the application, this is to ensure right order, and that everything shuts down correctly.

Rolling back on failure

While preparing updates, or just before applying them, a rollback plan is prepared. In FileUpdateTask for example, the original file if exists is being copied to a backup location. Should anything go wrong, NAppUpdate will call the IUpdateTask Rollback() method of the failed task, and that will restore everything to normal.

Mind the language

Take note of the language I've been using while describing the update process. You are not necessarily "downloading" anything, nor "replacing files". You simply "Prepare" and "Apply", potentially in a cold manner.

This is a fundamental concept of NAppUpdate, and what makes it so strong. All common scenarios for performing application updates are already supported by the built-in funcionality; but it is all done using generic concepts, so any update process will fit.

Error handling

NAppUpdate tries hard to stick to the KISS principle, but being able to provide important indications on what's going on is very important. So when designing the API we went with a very simplistic approach, that allows for easy operation and well-defined behavior, while not compromising the ability to understand what's going on. Each method you call will return true if successful, and false if anything has gone wrong. If you were using the Async version of a method, the callback function will be handed that boolean value. When a NAU method returns false, you should check UpdateManager.Instance.LatestError to see what went wrong. For your convenience, common errors are available in NAppUpdate.Framework.Common.Errors as consts. NAppUpdate has a well-defined state at any given point, and it is available via UpdateManager.State. Since it doesn't make sense to CheckForUpdates() if you already have pending update tasks, NAU will disallow that. You should check the current state yourself before calling any NAU method if the update process is not managed automatically.

Feed generator

A long requested feature is an automatic feed generator, and there have been a few efforts to come up with a working one. Recently NAppUpdate got a contribution of a very nice feed generator, screenshot of it is shown below. It is a very useful too, but doesn't really scratch the surface of what such a generator can look like, thanks to the flexible structure of NAppUpdate. In a future post I'll follow up on this and discuss how I envisioned such a generator tool. Since I hate doing UI, I will leave it as a challenge for whoever is interested...

Links

NAppUpdate on github - https://github.com/synhershko/NAppUpdate Official mailing list - https://groups.google.com/group/nappupdate More NAppUpdate content - http://www.code972.com/blog/tag/nappupdate/

Comments

  • jason

    very excelent post, it really does help to fill in the blanks for me (an interested user of NAppUpdate, and i stumble upon this page after 5 minutes of investigation)

  • Eko SW

    Yep, it definitely help me. Still in reviewing your library. I think it serves it purpose

    Thanks for your work!

  • Javier

    This is an interesting tool.

    Do you have a kind of developer guide or something like that?

Comments are now closed