在WinForm應用程序中實現自動升級

這是本人第一次寫比較複雜的文章,表達不清之處,請各位見諒。好,閒話少說,入正題。

最近單位開發一個項目,其中需要用到自動升級功能。因爲自動升級是一個比較常用的功能,可能會在很多程序中用到,於是,我就想寫一個自動升級的組件,在應用程序中,只需要引用這個自動升級組件,並添加少量代碼,即可實現自動升級功能。因爲我們的程序中可能包含多個exe或者dll文件,所以要支持多文件的更新。

首先,要確定程序應該去哪裏下載需要升級的文件。我選擇了到指定的網站上去下載,這樣比較簡單,也通用一些。在這個網站上,需要放置一個當前描述最新文件列表的文件,我們估且叫它服務器配置文件。這個文件保存了當前最新文件的版本號(lastver),大小(size),下載地址(url),本地文件的保存路徑(path),還有當更新了這個文件後,程序是否需要重新啓動(needRestart)。這個文件大致如下:
updateservice.xml

<?xml version="1.0" encoding="utf-8"?>
<updateFiles>
  
<file path="AutoUpdater.dll"  url="http://update.iyond.com/CompanyClientApplication/AutoUpdater.zip" lastver="1.0.0.0" size="28672" needRestart="true" />
  
<file path="CompanyClient.exe"  url="http://update.iyond.com/CompanyClientApplication/CompanyClient.zip" lastver="1.1.0.0" size="888832 " needRestart="true" />
  
<file path="HappyFenClient.dll"  url="http://update.iyond.com/CompanyClientApplication/HappyFenClient.zip" lastver="1.0.0.0" size="24576" needRestart="true" />
  
<file path="NetworkProvider.dll"  url="http://update.iyond.com/CompanyClientApplication/NetworkProvider.zip" lastver="1.0.0.0" size="32768" needRestart="true" />
  
<file path="Utility.dll"  url="http://update.iyond.com/CompanyClientApplication/Utility.zip" lastver="1.0.0.0" size="20480" needRestart="true" />
  
<file path="Wizard.dll"  url="http://update.iyond.com/CompanyClientApplication/Wizard.zip" lastver="1.0.0.0" size="24576"  needRestart="true" />
</updateFiles>

同時,客戶端也保存了一個需要升級的本地文件的列表,形式和服務器配置文件差不多,我們叫它本地配置文件。其中,<Enable>節點表示是否啓用自動升級功能,<ServerUrl>表示服務器配置文件的地址。
update.config

<?xml version="1.0" encoding="utf-8"?>
<Config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  
<Enabled>true</Enabled>
  
<ServerUrl>http://update.iyond.com/updateservice.xml</ServerUrl>
  
<UpdateFileList>
      
<LocalFile path="AutoUpdater.dll" lastver="1.0.0.0" size="28672" />
      
<LocalFile path="CompanyClient.exe" lastver="1.1.0.0" size="888832 " />
      
<LocalFile path="HappyFenClient.dll" lastver="1.0.0.0" size="24576" />
      
<LocalFile path="NetworkProvider.dll" lastver="1.0.0.0" size="32768" />
      
<LocalFile path="Utility.dll" lastver="1.0.0.0" size="20480" />
      
<LocalFile path="Wizard.dll" lastver="1.0.0.0" size="24576"  />
  
</UpdateFileList>  
</Config>

使用自動各級組件的程序在啓動時,會去檢查這個配置文件。如果發現有配置文件中的文件版本和本地配置文件中描述的文件版本不一致,則提示用戶下載。同時,如果本地配置文件中某些文件在服務器配置文件的文件列表中不存在,則說明這個文件已經不需要了,需要刪除。最後,當升級完成後,會更新本地配置文件。

我們先來看一下如何使用這個組件。
在程序的Program.cs的Main函數中:

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(
false);

    AutoUpdater au 
= new AutoUpdater();
    
try
    {
        au.Update();
    }
    
catch (WebException exp)
    {
        MessageBox.Show(String.Format(
"無法找到指定資源/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
catch (XmlException exp)
    {
        MessageBox.Show(String.Format(
"下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
catch (NotSupportedException exp)
    {
        MessageBox.Show(String.Format(
"升級地址配置錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
catch (ArgumentException exp)
    {
        MessageBox.Show(String.Format(
"下載的升級文件有錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    
catch (Exception exp)
    {
        MessageBox.Show(String.Format(
"升級過程中發生錯誤/n/n{0}", exp.Message), "自動升級", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    Application.Run(
new MainUI());
}


如上所示,只需要簡單的幾行代碼,就可以實現自動升級功能了。

軟件運行截圖:






下面,我們來詳細說一下這個自動升級組件的實現。
先看一下類圖:

AutoUpdater:自動升級的管理類,負責整體的自動升級功能的實現。
Config:配置類,負責管理本地配置文件。
DownloadConfirm:一個對話框,向用戶顯示需要升級的文件的列表,並允許用戶選擇是否馬上升級。
DownloadFileInfo:要下載的文件的信息
DownloadProgress:一個對話框,顯示下載進度。
DownloadProgress.ExitCallBack,
DownloadProgress.SetProcessBarCallBack,
DownloadProgress.ShowCurrentDownloadFileNameCallBack:由於.NET2.0不允許在一個線程中訪問另一個線程的對象,所以需要通過委託來實現。
LocalFile:表示本地配置文件中的一個文件
RemoteFile:表示服務器配置文件中的一個文件。
UpdateFileList:一個集合,從List<LocalFile>繼承

我們先整體看一下AutoUpdater.cs:

AutoUpdater.cs


在構造函數中,我們先要加載配置文件:

public AutoUpdater()
{
    config 
= Config.LoadConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME));
}


最主要的就是Update()這個函數了。當程序調用au.Update時,首先檢查當前是否開戶了自動更新:

        if (!config.Enabled)
            
return;


如果啓用了自動更新,就需要去下載服務器配置文件了:

        WebClient client = new WebClient();
        
string strXml = client.DownloadString(config.ServerUrl);


然後,解析服務器配置文件到一個Dictionary中:

        Dictionary<string, RemoteFile> listRemotFile = ParseRemoteXml(strXml);


接下來比較服務器配置文件和本地配置文件,找出需要下載的文件和本地需要刪除的文件:

        List<DownloadFileInfo> downloadList = new List<DownloadFileInfo>();
        
//某些文件不再需要了,刪除
        List<LocalFile> preDeleteFile = new List<LocalFile>();

        
foreach (LocalFile file in config.UpdateFileList)
        
{
            
if (listRemotFile.ContainsKey(file.Path))
            
{
                RemoteFile rf 
= listRemotFile[file.Path];
                
if (rf.LastVer != file.LastVer)
                
{
                    downloadList.Add(
new DownloadFileInfo(rf.Url, file.Path, rf.LastVer, rf.Size));
                    file.LastVer 
= rf.LastVer;
                    file.Size 
= rf.Size;

                    
if (rf.NeedRestart)
                        bNeedRestart 
= true;
                }


                listRemotFile.Remove(file.Path);
            }

            
else
            
{
                preDeleteFile.Add(file);
            }

        }


        
foreach (RemoteFile file in listRemotFile.Values)
        
{
            downloadList.Add(
new DownloadFileInfo(file.Url, file.Path, file.LastVer, file.Size));
            config.UpdateFileList.Add(
new LocalFile(file.Path, file.LastVer, file.Size));

            
if (file.NeedRestart)
                bNeedRestart 
= true;
        }


如果發現有需要下載的文件,則向用戶顯示這些文件,並提示其是否馬上更新。如果用戶選擇了馬上更新,則先刪除本地不再需要的文件,然後開始下載更新文件。

        if (downloadList.Count > 0)
        
{
            DownloadConfirm dc 
= new DownloadConfirm(downloadList);

            
if (this.OnShow != null)
                
this.OnShow();

            
if (DialogResult.OK == dc.ShowDialog())
            
{
                
foreach (LocalFile file in preDeleteFile)
                
{
                    
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.Path);
                    
if (File.Exists(filePath))
                        File.Delete(filePath);

                    config.UpdateFileList.Remove(file);
                }


                StartDownload(downloadList);
            }

        }


我們再來看一下StartDownload函數

    private void StartDownload(List<DownloadFileInfo> downloadList)
    
{
        DownloadProgress dp 
= new DownloadProgress(downloadList);
        
if (dp.ShowDialog() == DialogResult.OK)
        
{
            
//更新成功
            config.SaveConfig(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FILENAME));

            
if (bNeedRestart)
            
{
                MessageBox.Show(
"程序需要重新啓動才能應用更新,請點擊確定重新啓動程序。""自動更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
                Process.Start(Application.ExecutablePath);
                Environment.Exit(
0);
            }

        }

    }


在這個函數中,先調用DownloadProgress下載所有需要下載的文件,然後更新本地配置文件,最後,如果發現某些更新文件需要重新啓動應用程序的話,會提示用戶重新啓動程序。

至此,AutoUpdater這個類的使命就完成了,其實,整個的升級過程也就完成了。(廢話)。

最後,我們來看一下這個組件是如何下載更新文件的

DownloadProgress.cs


在構造函數中,將要下載的文件列表傳進來

        public DownloadProgress(List<DownloadFileInfo> downloadFileList)
        
{
            InitializeComponent();

            
this.downloadFileList = downloadFileList;
        }


在Form的Load事件中,啓動下載線程,開始下載。

        private void OnFormLoad(object sender, EventArgs e)
        
{
            evtDownload 
= new ManualResetEvent(true);
            evtDownload.Reset();
            Thread t 
= new Thread(new ThreadStart(ProcDownload));
            t.Name 
= "download";
            t.Start();
        }


下載線程沒什麼特殊的,使用了WebClient的異步下載文件函數DownloadFileAsync,並且註冊了兩個事件,分別負責下載進度顯示和下載完成後的處理:

                clientDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged);
                clientDownload.DownloadFileCompleted 
+= new AsyncCompletedEventHandler(OnDownloadFileCompleted);


大家看一下就明白了。

        private void ProcDownload()
        
{
            evtPerDonwload 
= new ManualResetEvent(false);

            
foreach (DownloadFileInfo file in this.downloadFileList)
            
{
                total 
+= file.Size;
            }


            
while (!evtDownload.WaitOne(0false))
            
{
                
if (this.downloadFileList.Count == 0)
                    
break;

                DownloadFileInfo file 
= this.downloadFileList[0];


                
//Debug.WriteLine(String.Format("Start Download:{0}", file.FileName));

                
this.ShowCurrentDownloadFileName(file.FileName);

                
//下載
                clientDownload = new WebClient();

                clientDownload.DownloadProgressChanged 
+= new DownloadProgressChangedEventHandler(OnDownloadProgressChanged);
                clientDownload.DownloadFileCompleted 
+= new AsyncCompletedEventHandler(OnDownloadFileCompleted);

                evtPerDonwload.Reset();

                clientDownload.DownloadFileAsync(
new Uri(file.DownloadUrl), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName + ".tmp"), file);
                
                
//等待下載完成
                evtPerDonwload.WaitOne();

                clientDownload.Dispose();
                clientDownload 
= null;

                
//移除已下載的文件
                this.downloadFileList.Remove(file);
            }


            
//Debug.WriteLine("All Downloaded");

            
if (this.downloadFileList.Count == 0)
                Exit(
true);
            
else
                Exit(
false);

            evtDownload.Set();
        }


最後,在OnDownloadFileCompleted函數中進行最後的處理。包括備份原文件,替換現有文件等。

        void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        
{
            DownloadFileInfo file 
= e.UserState as DownloadFileInfo;
            nDownloadedTotal 
+= file.Size;
            
this.SetProcessBar(0, (int)(nDownloadedTotal * 100 / total));
            
//Debug.WriteLine(String.Format("Finish Download:{0}", file.FileName));
            
//替換現有文件
            string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FileFullName);
            
if (File.Exists(filePath))
            
{
                
if (File.Exists(filePath + ".old"))
                    File.Delete(filePath 
+ ".old");

                File.Move(filePath, filePath 
+ ".old");
            }


            File.Move(filePath 
+ ".tmp", filePath);
            
//繼續下載其它文件
            evtPerDonwload.Set();
        }


其它的函數只是一些顯示進度條和下載信息的,這裏就不再詳細介紹了。大家可以下載源碼看一下。

最後說一下,由於個人能力和時間的問題,這個組件還有一些不完善的地方,比如配置文件比較複雜等,如果您對這個組件有什麼改進的話,請務必發給我一份,我的郵件是:[email protected].謝謝。


再做個廣告:VS2005專業教程站是小弟最近做的一個網站,主要是提供VS2005、ASP.NET相關教程,希望大家有什麼問題可以去看一下,同時給我提些建議。謝謝大家啦。

源碼下載

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章