⬆️ ⬇️

We write our auto-update service

Most developers of stand-alone applications sooner or later face the problem of delivering updates for their application. In this article I will try to solve this problem in the best, in my opinion, way - to write my own universal auto-update service, which will hang in processes in a single copy and deliver updates for all subscribing applications.



There are several ready-made solutions for .NET, but the most relevant is ClickOnce. This technology can no longer be called new, but, in my opinion, it received serious development not so long ago, and does not have an exhaustive functionality.

If you do not want to reinvent the wheel, then I advise you to carefully explore the possibilities of ClickOnce, and if the functionality offered is sufficient for you, then this is definitely your choice. However, ClickOnce is not a panacea and it is not always possible to do with it.



Now I want to talk about my vision of the mechanism of auto-updates. I do not pretend to be the last resort, so constructive criticism and suggestions in the comments are welcome.

')

UPD: The essence of the implementation is to reduce the number of processes and services that are engaged in updating. If you have several applications, then all of them will be able to “receive” updates from a single Windows service. It will not be necessary for each application to launch a launcher, keep a connection with the update server. Theoretically, one process can deal with all updates in the system, and it is possible that ClickOnce will soon become this process if developers stop making their “bikes”. And developers will stop making their bikes when they have enough ClickOnce functionality. Now, unfortunately, this is not always the case.



So the task



Suppose we have several different applications installed on the user's computer. I would like to write a universal service of auto-updates, so that later I could use it in other applications. And all applications were updated using only one service, which would save resources with a large amount of software. It is also desirable that in existing applications I need to make minimal changes to connect and configure auto-updates. The update process must be customizable for each application.



Implementation



In order to concretely define the problem, I will solve it with reference to Windows, write code in C # .NET, although in this article I will mainly operate on abstractions and give only small snippets of code.



My auto-update service consists of 3 modules:

1) Web service with updates themselves, versioning, a database of all supported applications and everything else that can be brought here (for example, some “remote” settings for updating a specific application).

2) A Windows service that connects to a web service and checks for updates for all signed applications, by timer and on demand.

3) A client library that will know how to connect to the Updater's Windows service, as well as provide the callback application with an interface.



I blew it all into 3 assemblies and named accordingly.



Updater.Online - web service

Updater.Service - windows-service

Updater.Client - client-module



I also highlighted another build for general abstractions - Updater.Domain .



Updater Online



Let's start with the web service. Everything is simple, everything can be crammed into one CheckForUpdates method, which accepts ApplicationID and CurrentVersion, looks in the database if there are any actual updates for this application, and if there is, returns the path to the .zip file with the update or null if there are no updates. This is the simplest case, in general, as well as the requested parameters and the query results may be more.

The service may return additional information that may come in handy during the update process. For example, on the service, you can indicate whether the update is possible in silent-mode for this application, additional data on how to download the update, in what format it is, what size it has, etc.



Updater service



This is probably the most voluminous module. Here is the WCF service UpdaterService, below is the interface that it implements and the callback interface.



[ServiceContract(CallbackContract = typeof(IUpdateServiceCallback))] public interface IUpdaterService { #region Callback subsctibe/unsibscribe methods [OperationContract(IsOneWay = true)] void Subscribe(SubscribeRequest request); [OperationContract(IsOneWay = true)] void Unsubscribe(UnsubscribeRequest request); #endregion [OperationContract(IsOneWay = true)] void InstallAvalibleUpdates(InstallAvalibleUpdatesRequst request); [OperationContract(IsOneWay = true)] void DownloadUpdate(Guid applicationId); [OperationContract(IsOneWay = true)] void CheckForUpdates(Guid applicationId); } [ServiceContract] public interface IUpdateServiceCallback { [OperationContract(IsOneWay = true)] void OnUpdateDetected(UpdateDetectedEventArgs eventArgs); [OperationContract(IsOneWay = true)] void OnUpdateDownloaded(UpdateDownloadedEventArgs updateDetectedEventArgs); [OperationContract(IsOneWay = true)] void OnUpdateInstalled(UpdateInstalledEventArgs eventArgs); } 




In the Subscribe method, I add the callback that came from the client to the static Dictionary <TAppID, CallbackList>, where TAppID is the application ID (I have a Guid), for each application there is a separate list of callbacks.

Here is the implementation of the Subscribe method.



 public void Subscribe(SubscribeRequest request) { //    calback   IUpdateServiceCallback callback = OperationContext.Current.GetCallbackChannel<IUpdateServiceCallback>(); // lock,       lock (sync) { //      Application   ID.   , //   . /     var app = applicationService.Get(request.ApplicationId); if (app == null) { app = new Application() { Id = request.ApplicationId }; applicationService.Add(app); } app.CurrentVersion = request.Version ?? app.CurrentVersion; app.RootFolderPath = request.RootFolder ?? app.RootFolderPath; app.Name = request.ApplicationName ?? app.Name; } //   callback'      , //     var list = GetEventList(request.ApplicationId) ?? new CallbacksList(); list.OnUpdateDetected += callback.OnUpdateDetected; list.OnUpdateDownloaded += callback.OnUpdateDownloaded; if (registredCallbacks.ContainsKey(request.ApplicationId)) { registredCallbacks[request.ApplicationId] = list; } else { registredCallbacks.Add(request.ApplicationId, list); } //     ,   //       ( //    ) ICommunicationObject obj = (ICommunicationObject)callback; obj.Closing += ClientClosing; obj.Closed += ClientClosed; applicationService.SaveChanges(); } 




Also in the implementation of UpdaterService, I added static methods to call Callback to hide the implementation of their call a little. These methods are invoked on the side of the Updater Service when it is necessary to initiate the corresponding events on the client side.



 private static Dictionary<Guid, CallbacksList> registredCallbacks; //      callback'   null     private static CallbacksList GetEventList(Guid appId) { CallbacksList result; return registredCallbacks.TryGetValue(appId, out result) ? result : null; } //   callback'    private static void PerformCallback(Guid applicationId, Action<CallbacksList> func) { try { var list = GetEventList(applicationId); if (list != null) { func(list); } } catch { } } //    OnUpdateDetected     //   ApplicatioId public static void OnUpdateDetected(UpdateDetectedEventArgs args) { PerformCallback(args.ApplicationId, callbacks => callbacks.OnUpdateDetected(args)); } 




I also launch a “timer” on the service, which checks for updates for all signed applications after a specified period of time.

I have ApplicationService in the code, although I called it a service, it looks more like a repository of information about signed applications and updates for them.

Here are the app classes and updates.



 public class Application { public Guid Id { get; set; } public String Name { get; set; } public Version CurrentVersion { get; set; } public String RootFolderPath { get; set; } public List<Update> Updates { get; set; } } public class Update { public String UpdateUrl { get; set; } public Version Version { get; set; } public bool IsInstalled { get; set; } public bool IsDownloaded { get; set; } public string UpdateLocalPath { get; set; } } 




Updater client





This assembly is connected in the application and monitors messages from the Service Updater, informing the application about them through an object implementing IUpdateServiceCallback. You can use the same object that was transferred from the application to the Updater Client as a callback to the service, but filter information sent to the client application, rather than pushing it all in a row, which will be returned to the Updater service. In the implementation given by me, the wrapper is not used. The client application also transmits data about itself as an object that implements the IUpdatble interface.



 public interface IUpdatble { Guid ApplicationId { get; } String ApplicationName { get; } String RootFolder { get; } } public class UpdaterClient { private IUpdaterService client; private IUpdateble settings; private DuplexChannelFactory<IUpdaterService> factory; public UpdaterClient(IUpdateServiceCallback callback, IUpdateble settings) { this.settings = settings; var context = new InstanceContext(callback); var binding = new NetTcpBinding(); //     ,    //      , //    IUpdateServiceCallback factory = new DuplexChannelFactory<IUpdaterService>(context, binding, new EndpointAddress(UpdaterSettings.Default.UpdaterServiceUrl)); client = factory.CreateChannel(); } //    ,   //     Iupdateble , //      public void Subscribe() { client.Subscribe(new SubscribeRequest() { ApplicationId = settings.ApplicationId, RootFolder = settings.RootFolder, Version = settings.Version, ApplicationName = settings.ApplicationName }); } public void Unsubscribe() { client.Unsubscribe(new UnsubscribeRequest() { ApplicationId = settings.ApplicationId }); } public void InstallUpdates(bool reopenOnComplete) { InstallUpdates(reopenOnComplete); } public void DownloadUpdate() { client.DownloadUpdate(new DownloadUpdateRequest() { ApplicationId = settings.ApplicationId }); } public void CheckForUpdates() { client.CheckForUpdates(settings.ApplicationId); } public void InstallUpdates(bool reopenOnComplete) { client.InstallAvalibleUpdates(new InstallAvalibleUpdatesRequst() { ApplicationId = settings.ApplicationId, RestartOnComplete = reopenOnComplete }); } } 




Well, that's all. It is used on the client side as follows: we connect the Updater.Client assembly, create an UpdaterClient object, call the Subscribe method, and our application starts receiving messages from the service about new updates.

Source: https://habr.com/ru/post/131649/



All Articles