C# 實現軟件自動更新升級程序
原理
服務器端的結構是這樣的:
其工作原理如下:
Update.asmx僅提供一個功能,就是檢測是否需要更新,在需要更新的時候就返回一個更新地址,通常情況下返回的地址就是Download.ashx,而在某些特殊情況下,也可以修改服務端使之從其他Url提供更新下載。檢測是否需要更新的的具體做法是:首先獲取Updata目錄中的主程序版本號,再獲取數據庫中的最新版本號,兩者對比。如果相同則直接與客戶端提供的版本號相對比并返回結果;如果不同則將主程序版本號寫入數據庫,然后生成新的更新文件包,直接向客戶端返回更新地址。
Download.ashx的功能僅僅是將最新版本更新文件包輸出。
而客戶端部分包含主程序、Update.exe以及其他附屬文件,更新時由主程序檢測并下載更新,在主程序退出時,如有更新并已成功下載,則調用Update.exe完成解包及更新覆蓋工作。需注意的是:Update.exe永遠不能被更新,因為它無法更新其自身,所以服務端更新時也不要將Update.exe納入更新包。
下面就是來實際編寫一個自動更新解決方案:
服務器端
首先建立一個Web服務項目,項目名為“自動更新服務”:
添加一數據庫,名為Database.mdf:
在數據庫中創建新的數據庫關系圖,并如下設計數據庫表:
創建一個Ado.Net Entity Data Model,名為Model.edmx:
從剛才的建立的數據庫中生成模型:
在Web.Config的appSettings節點中新增兩個節點,用以設置更新程序的主文件名及更新包下載地址:
- C# code復制代碼
<appSettings> <add key="主程序文件名" value="MyApp.exe"/> <add key="更新包下載地址" value="Download.ashx"/> </appSettings>
引入一個GZip類用以打包(該類的源碼將在文章末尾隨本文示例源代碼一并提供):
添加一個新的Web服務,名為Update.asmx:
書寫如下代碼:
- C# code復制代碼
[WebMethod] public string GetUpdate(string ClientVerison) { if (獲取最新版本() != ClientVerison) { return System.Web.Configuration.WebConfigurationManager.AppSettings["更新包下載地址"]; } return null; } static string 獲取最新版本() { string v = 獲取文件版本(HttpContext.Current.Server.MapPath(string.Format("~/App_Data/Update/{0}", System.Web.Configuration.WebConfigurationManager.AppSettings["主程序文件名"]))); using (var c = new DatabaseEntities()) { //從數據庫取得最新版本信息 var q = c.UpdateVersion.OrderByDescending(f => f.PublicTime).FirstOrDefault(); if (q == null || v != q.Version) { //數據庫中的版本與當前主程序版本不一致時,以主程序版本為準,寫入數據庫,并生成新的更新文件包 var d = new UpdateVersion() { Version = v, PublicTime = DateTime.Now }; c.AddToUpdateVersion(d); c.SaveChanges(); 打包更新文件(HttpContext.Current.Server.MapPath("~/App_Data/Update/"), HttpContext.Current.Server.MapPath("~/App_Data/Update.gzip")); } } return v; } public static void 打包更新文件(string 打包目錄, string 輸出文件) { GZip.壓縮(輸出文件, Directory.GetFiles(打包目錄).Concat(Directory.GetDirectories(打包目錄)).ToArray()); } public static string 獲取文件版本(string 文件路徑) { FileVersionInfo f = FileVersionInfo.GetVersionInfo(文件路徑); return f.FileVersion; }
代碼:
- C# code復制代碼
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "application/zip"; context.Response.WriteFile(context.Server.MapPath("~/App_Data/Update.gzip")); }
服務端至此就編寫完畢了。
客戶端
新建一個WinForm應用程序項目,名為Update:
建好之后直接刪掉Form1.cs吧,此程序不需要界面,在Program.cs中寫代碼就可以了。
同樣需要引入GZip類用于解包:
然后編寫代碼:
- C# code復制代碼
[STAThread] static void Main() { try { var d = DateTime.Now; while (DateTime.Now.Subtract(d).TotalSeconds < 10) Application.DoEvents(); GZip.解壓縮(Path.Combine(Application.StartupPath, "update.data"), Application.StartupPath); } catch { } }
這里的作用就是等待10秒,然后解包update.data文件,覆蓋到當前目錄中。
現在來建立主程序,主程序是WinForm、命令行、WPF都可以,我們新建一個WPF應用程序,命名為MyAPP:
為程序添加服務引用:
這里的地址使用的是本地的調試地址。
為了檢測主程序自身的版本號,還需要添加對System.Windows.Forms的引用。
然后開始設計界面,這里僅為演示更新操作,所以界面上只是簡單的設計了更新相關的提示、操作控件:
代碼為:
<Window x:Class="MyApp.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="377" Loaded="Window_Loaded" Closed="Window_Closed"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> </Grid.RowDefinitions> <Label Margin="0" Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">檢測到新版本,是否下載?</Label> <Button Grid.Row="1" Height="23" Name="button1" VerticalAlignment="Center" Visibility="Hidden" Click="button1_Click">開始下載</Button> <Label Grid.Row="2" Margin="0" Name="label2" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Hidden">更新包已下載完畢,在程序關閉后將自動執行更新操作。</Label> </Grid> </Window>
需注意的是,這里控件都被設置為Visibility="Hidden",我們將會在需要時再將其顯示出來。
編寫后臺代碼:
- C# code復制代碼
public Uri DownloadUri { get { return _DownloadUri; } set { _DownloadUri = value; } } private Uri _DownloadUri; public bool UpdateReady { get { return _UpdateReady; } set { _UpdateReady = value; } } private bool _UpdateReady; private void Window_Loaded(object sender, RoutedEventArgs e) { var u = new MyApp.ServiceReference.UpdateSoapClient(); var s=u.GetUpdate(System.Windows.Forms.Application.ProductVersion); if (!string.IsNullOrEmpty(s)) { //獲取相對于Web服務所在Uri的Uri DownloadUri = new Uri(u.Endpoint.ListenUri, s); label1.Visibility = button1.Visibility = Visibility.Visible; } } private void button1_Click(object sender, RoutedEventArgs e) { var c = new WebClient(); c.DownloadFile(DownloadUri, System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.data")); UpdateReady = true; label2.Visibility = Visibility.Visible; } private void Window_Closed(object sender, EventArgs e) { if (UpdateReady) { Process.Start(System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.exe")); } }
測試
現在將主程序、附屬文件和Update.exe放在一起,并將主程序及附屬文件復制一份放到服務器端的App_data/Update/目錄中,再添加一個“更新說明.txt”:
然后啟動客戶端程序進行測試,應該看到程序界面里什么都沒有,因為客戶端和服務器端程序版本是一致的。
現在我們修改客戶端版本號為1.0.0.1:
然后重新編譯程序。
因為服務器僅僅是判斷版本號是否不同,而不是哪個更高,所以不僅僅是升級,降級更新也是可以的,我們來測試一下:
找到所謂的新版本了^^,點開始下載:
下載完成,這時目錄里就有update.data這個文件了。
現在關閉程序,等待10秒,讓Update.exe完成更新:
可以看到,程序被降級為1.0.0.0了,而且那個“更新說明.txt”也被更新出來了。
評論內容只代表網友觀點,與本站立場無關!
[回復] 13 樓 游客 打分:100 分 發表時間:2016-10-26
· 回復 9 樓(游客):傻逼!你行你上,不行別BB!
[回復] 12 樓 游客 打分:100 分 發表時間:2016-01-18
· 回復 9 樓(游客):不管怎么樣,人家是分享給大家,是認真寫的。也費了不少功夫。你丫挺費心思說別人。真高潔。
[回復] 11 樓 qinchun2046 打分:100 分 發表時間:2015-12-03
· 代碼有錯誤啊
[回復] 10 樓 游客 打分:100 分 發表時間:2015-08-26
· 回復 9 樓(游客):誰也沒求你看!你牛X你寫!
[回復] 9 樓 游客 打分:100 分 發表時間:2015-07-30
· 看了你的文章,只能嘆國人可憐又可悲!洋洋灑灑一個大篇,要用的沒必要看你這么多文字及圖示;不用的,只想看你的代碼,也沒必要看你的文章。但你自說自話,自己都沒編碼去測試,出文章干什么?想賺什么?你去國外編
[回復] 8 樓 游客 打分:1 分 發表時間:2014-01-18
· 沒有源碼,也沒有gzip,不知道行不行