淺析如何實現(xiàn)應(yīng)用程序版本升級
我們寫了一個應(yīng)用程序發(fā)布后,以后該應(yīng)用程序有新版本后如何將升級后的版本部署到客戶的機器上去呢?
我目前的做法是:
如果這個升級版本是一個不重要的升級版本,比如說僅針對特定客戶增加一些新的功能,可以通知這部分客戶按以下方法進行升級:
如上圖所示,程序當(dāng)前版本是 1.225 版,而該程序的更新版本 1.226 版已經(jīng)發(fā)布了。這時可以在程序的“關(guān)于”對話框中點擊“更新”按鈕來升級應(yīng)用程序。
升級完成后彈出一個對話框,點擊“確定”按鈕退出本程序后,重新啟動該應(yīng)用程序,發(fā)現(xiàn)版本已經(jīng)更新到 1.226 版了:
此時,由于該程序已經(jīng)是最新版,無法再升級,所以“更新”按鈕不可用。
如果要發(fā)布一個關(guān)鍵的升級版本,可以將程序最低版本設(shè)置為剛發(fā)布的版本。
在這個例子中,程序最低版本被設(shè)置為 1.225,已經(jīng)發(fā)布的程序更新版本是 1.226,而客戶機器上的程序當(dāng)前版本是 1.206。因此,客戶在登錄系統(tǒng)時,會得到一個“ 本程序已經(jīng)過時”的提示,要求升級程序的版本。點擊“是”按鈕后:
同樣更新了程序,版本升級到 1.226,如下圖所示:
上面講述的是客戶如何升級版本。而我們作為程序開發(fā)者又如何發(fā)布程序的新版本呢?
這非常簡單,開發(fā)完成一個新版本,并編譯成功后,運行這個應(yīng)用程序,點擊“系統(tǒng) -> 發(fā)布應(yīng)用程序”菜單項,在彈出的“應(yīng)用程序發(fā)布”對話框中點擊“發(fā)布”按鈕就行了。
如果這個版本是關(guān)鍵版本,還可以設(shè)置客戶端程序所需的最低版本為此版本。
這些功能又是如何實現(xiàn)的呢?
首先,我們的客戶端應(yīng)用程序在運行時需要連接一個 Microsoft SQL Server 數(shù)據(jù)庫服務(wù)器。在這個 SQL Server 數(shù)據(jù)庫中有以下數(shù)據(jù)表:
這個 Adm_Configuration 數(shù)據(jù)表中的一些記錄保存了程序最低版本和程序更新版本的值。
這個 Adm_ClientRelease 數(shù)據(jù)表保存了程序更新版本的具體信息,其中 ReleaseData 字段的數(shù)據(jù)類型是 varbinary(max),這個字段中保存著要發(fā)布的程序更新版本的二進制代碼本身。
下面是用于讀 Adm_Configuration 數(shù)據(jù)表的存儲過程:
- CREATE PROCEDURE [dbo].[prAdm_GetConfiguration]
- @TheKey nvarchar(50),
- @Value nvarchar(128) output
- AS
- SELECT @Value=Value FROM Adm_Configuration WHERE TheKey = @TheKey;
- IF @@Error = 0 AND @@Rowcount <> 1
- BEGIN
- DECLARE @ErrorMessage nvarchar(4000);
- SET @ErrorMessage = N'讀數(shù)據(jù)庫配置表錯, 無此鍵: ' + @TheKey;
- RAISERROR (@ErrorMessage, 11, 1);
- END
下面是用于更新 Adm_Configuration 數(shù)據(jù)表的存儲過程:
- CREATE PROCEDURE [dbo].[prAdm_UpdateConfiguration]
- @TheKey nvarchar(50),
- @Value nvarchar(128)
- AS
- UPDATE Adm_Configuration
- SET Value = @Value
- WHERE TheKey = @TheKey;
- IF @@Error = 0 AND @@Rowcount <> 1
- BEGIN
- DECLARE @ErrorMessage nvarchar(4000);
- SET @ErrorMessage = N'更新數(shù)據(jù)庫配置表錯, 無此鍵: ' + @TheKey;
- RAISERROR (@ErrorMessage, 11, 1);
- END
下面是讀取和更新 Adm_Configuration 數(shù)據(jù)表的 C# 源程序(數(shù)據(jù)層)的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Adm.Data
- {
- sealed class Configuration : DbObject
- {
- public Configuration(string newConnectionString) : base(newConnectionString) { }
- public string GetValue(string key)
- {
- SqlParameter[] parameters = {
- new SqlParameter("@TheKey", SqlDbType.NVarChar, 50),
- new SqlParameter("@Value" , SqlDbType.NVarChar, 128)
- };
- parameters[0].Value = key;
- parameters[1].Direction = ParameterDirection.Output;
- RunNonQuery("prAdm_GetConfiguration", parameters);
- return (string)parameters[1].Value;
- }
- public void Update(string key, string value)
- {
- SqlParameter[] parameters = {
- new SqlParameter("@TheKey", SqlDbType.NVarChar, 50),
- new SqlParameter("@Value" , SqlDbType.NVarChar, 128)
- };
- parameters[0].Value = key;
- parameters[1].Value = value;
- RunNonQuery("prAdm_UpdateConfiguration", parameters);
- }
- }
- }
下面是讀取 Adm_ClientRelease 數(shù)據(jù)表中要發(fā)布的應(yīng)用程序更新版本的二進制代碼的長度的存儲過程:
- CREATE PROCEDURE [dbo].[prAdm_GetClientReleaseLength]
- @ReleaseName nvarchar(128),
- @ReleaseVersion varchar(128),
- @ReleaseLength int output
- AS
- SELECT @ReleaseLength = DataLength(ReleaseData)
- FROM Adm_ClientRelease
- WHERE ReleaseName = @ReleaseName
- AND ReleaseVersion = @ReleaseVersion
下面是讀取 Adm_ClientRelease 數(shù)據(jù)表中要發(fā)布的應(yīng)用程序更新版本的二進制代碼的存儲過程:
- CREATE PROCEDURE [dbo].[prAdm_GetClientReleaseData]
- @ReleaseName nvarchar(128),
- @ReleaseVersion varchar(128),
- @UserID int output,
- @ReleaseTime datetime output,
- @ReleaseData varbinary(max) output
- AS
- SELECT @UserID = UserID,
- @ReleaseTime = ReleaseTime,
- @ReleaseData = ReleaseData
- FROM Adm_ClientRelease
- WHERE ReleaseName = @ReleaseName
- AND ReleaseVersion = @ReleaseVersion
下面是更新 Adm_ClientRelease 數(shù)據(jù)表的存儲過程:
- CREATE PROCEDURE [dbo].[prAdm_UpdateClientRelease]
- @ReleaseName nvarchar(128),
- @ReleaseVersion varchar(128),
- @UserID int,
- @ReleaseData varbinary(max)
- AS
- DELETE FROM Adm_ClientRelease
- WHERE ReleaseName = @ReleaseName;
- INSERT INTO Adm_ClientRelease
- (ReleaseName, ReleaseVersion, UserID, ReleaseData) VALUES
- (@ReleaseName, @ReleaseVersion, @UserID, @ReleaseData);
下面是讀取和更新 Adm_ClientRelease 數(shù)據(jù)表的 C# 源程序(數(shù)據(jù)層)的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Adm.Data
- {
- sealed class ClientRelease : DbObject
- {
- public ClientRelease(string newConnectionString) : base(newConnectionString) { }
- public void Update(string name, string version, int userID, byte[] data)
- {
- SqlParameter[] parameters = {
- new SqlParameter("@ReleaseName" , SqlDbType.NVarChar , 128),
- new SqlParameter("@ReleaseVersion", SqlDbType.VarChar , 128),
- new SqlParameter("@UserID" , SqlDbType.Int , 4),
- new SqlParameter("@ReleaseData" , SqlDbType.VarBinary, data.Length)
- };
- parameters[0].Value = name;
- parameters[1].Value = version;
- parameters[2].Value = userID;
- parameters[3].Value = data;
- RunNonQuery("prAdm_UpdateClientRelease", parameters);
- }
- public byte[] GetData(string name, string version)
- {
- return GetData(name, version, GetLength(name, version));
- }
- public int GetLength(string name, string version)
- {
- SqlParameter[] parameters = {
- new SqlParameter("@ReleaseName" , SqlDbType.NVarChar, 128),
- new SqlParameter("@ReleaseVersion", SqlDbType.VarChar , 128),
- new SqlParameter("@ReleaseLength" , SqlDbType.Int , 4)
- };
- parameters[0].Value = name;
- parameters[1].Value = version;
- parameters[2].Direction = ParameterDirection.Output;
- RunNonQuery("prAdm_GetClientReleaseLength", parameters);
- return (int)parameters[2].Value;
- }
- byte[] GetData(string name, string version, int length)
- {
- SqlParameter[] parameters = {
- new SqlParameter("@ReleaseName" , SqlDbType.NVarChar , 128),
- new SqlParameter("@ReleaseVersion", SqlDbType.VarChar , 128),
- new SqlParameter("@UserID" , SqlDbType.Int , 4),
- new SqlParameter("ReleaseTime" , SqlDbType.DateTime , 8),
- new SqlParameter("@ReleaseData" , SqlDbType.VarBinary, length)
- };
- parameters[0].Value = name;
- parameters[1].Value = version;
- parameters[2].Direction = ParameterDirection.Output;
- parameters[3].Direction = ParameterDirection.Output;
- parameters[4].Direction = ParameterDirection.Output;
- RunNonQuery("prAdm_GetClientReleaseData", parameters);
- return (byte[])parameters[4].Value;
- }
- }
- }
下面是更新應(yīng)用程序的關(guān)鍵的 C# 源程序的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Adm.Business
- {
- sealed class Configuration : BizObject
- {
- public static Version UpdateClientVersion
- {
- get { return new Version(Db.GetValue("Sys_UpdateClientVersion")); }
- set { Db.Update("Sys_UpdateClientVersion", value.ToString()); }
- }
- /// <summary>
- /// 檢查是否能夠進行登錄
- /// </summary>
- public static void CheckLogin()
- {
- if (MaxMinutesForTimeDiffServerAndClient > 0
- && Math.Abs((DbCommon.GetDbTime() - DateTime.Now).TotalMinutes) > MaxMinutesForTimeDiffServerAndClient)
- throw new AppException("客戶機系統(tǒng)時鐘與服務(wù)器不一致");
- if (!Online) throw new AppException("數(shù)據(jù)庫維護中, 暫停使用. 請聯(lián)系管理員");
- if (DbVersion < Pub.MinDbVersion) throw new AppException("數(shù)據(jù)庫當(dāng)前版本太低, 請聯(lián)系管理員");
- }
- /// <summary>
- /// 判斷是否滿足進行應(yīng)用程序更新的條件
- /// </summary>
- /// <returns>是否能夠進行更新</returns>
- public static bool CanUpdate()
- {
- if (Pub.ThePrincipal != null) return false;
- Version updateVersion = null;
- try { updateVersion = Configuration.UpdateClientVersion; }
- catch { }
- if (updateVersion == null) return false;
- return updateVersion > Pub.Version;
- }
- public static void UpdateApplicatoin()
- {
- if (!CanUpdate()) throw new AppException("錯誤: 不滿足進行更新的條件");
- string version = UpdateClientVersion.ToString();
- byte[] bs = ClientRelease.GetData(Pub.Assembly.GetName().Name, version);
- string assemblyLocation = Pub.Assembly.Location;
- string oldFileName = assemblyLocation + ".OLD";
- File.Delete(oldFileName);
- File.Move(assemblyLocation, oldFileName);
- using (BinaryWriter bw = new BinaryWriter(new FileStream(assemblyLocation, FileMode.CreateNew)))
- {
- bw.Write(bs);
- }
- MessageBox.Show("更新完成,請按“確定”按鈕退出本程序", "更新", MessageBoxButtons.OK, MessageBoxIcon.Information);
- Application.Exit();
- }
- }
- }
下面是“關(guān)于”對話框中涉及“更新”按鈕的 C# 源程序的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Window
- public partial class AboutForm : Skyiv.Ben.Icbc.Mis.Window.Base2Form
- {
- private void btnUpdate_Click(object sender, EventArgs e)
- {
- btnUpdate.Enabled = false;
- try
- {
- Configuration.UpdateApplicatoin();
- }
- catch (Exception ex)
- {
- MessageBox.Show(Pub.GetMessage(ex), "更新", MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- }
- }
- }
下面是“用戶登錄”對話框的 C# 源程序的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Window
- {
- public partial class LoginForm : Skyiv.Ben.Icbc.Mis.Window.Base2Form
- {
- private void btnSumbit_Click(object sender, EventArgs e)
- {
- btnSubmit.Enabled = false;
- try
- {
- tbxMessage.Text = "";
- // ((MdiWindow)MdiParent).SetTimerInterval();
- if (Pub.ThePrincipal != null)
- {
- spcMain.Enabled = false;
- tbxMessage.Text = "用戶已經(jīng)登錄";
- return;
- }
- Configuration.CheckLogin();
- if (Pub.Version < Configuration.ClientVersion)
- {
- if (!Configuration.CanUpdate()) throw new Exception("本程序已經(jīng)過時, 而且沒有新版本可用, 請聯(lián)系管理員");
- WaitYesNo(Configuration.UpdateApplicatoin, "本程序已經(jīng)過時, 有新版本可用, 是否進行更新?");
- btnSubmit.Enabled = true;
- return;
- }
- Pub.ThePrincipal = User.ValidateLoginAndSetLogined(tbxUserid.Text.Trim(),
- isUseKeepedPassword ? loginInfo.DefaultPassword : tbxPassword.Text);
- KeepUserInfo();
- if (Pub.ThePrincipal == null) tbxMessage.Text = string.Format("{0}用戶不存在 "
- + "或者{0}密碼不正確 或者{0}用戶已經(jīng)登錄 或者{0}用戶已經(jīng)被禁用", Environment.NewLine);
- else
- {
- tbxMessage.Text = "用戶登錄成功";
- ((MdiWindow)MdiParent).UpdateStatus();
- ((MdiWindow)MdiParent).AfterLogin();
- }
- }
- catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); }
- btnSubmit.Enabled = true;
- }
- }
- }
下面是“應(yīng)用程序發(fā)布”對話框的 C# 源程序的部分代碼:
- namespace Skyiv.Ben.Icbc.Mis.Window
- {
- public partial class ReleaseApplicationForm : Skyiv.Ben.Icbc.Mis.Window.Base2Form
- {
- private void ReleaseApplicationForm_Load(object sender, EventArgs e)
- {
- releaseFileName = Pub.Assembly.Location;
- UpdateStatus();
- }
- private void btnFileName_Click(object sender, EventArgs e)
- {
- OpenFileDialog dlg = new OpenFileDialog();
- dlg.Filter = "程序(*.exe;*.dll)|*.exe;*.dll|所有文件(*.*)|*.*";
- if (dlg.ShowDialog() != DialogResult.OK) return;
- releaseFileName = dlg.FileName;
- UpdateStatus();
- }
- void UpdateStatus()
- {
- tbxMessage.Text = "";
- DataTable dt = new DataTable();
- dt.Columns.Add("屬性", typeof(string));
- dt.Columns.Add("值", typeof(string));
- try
- {
- btnSubmit.Enabled = false;
- AssemblyName myself = Pub.Assembly.GetName();
- Pub.AddRow(dt, "本程序集的名稱", myself.Name);
- Pub.AddRow(dt, "本程序集的版本", myself.Version.ToString());
- Pub.AddRow(dt, "所需的最低版本", Configuration.ClientVersion.ToString());
- Pub.AddRow(dt, "上次已經(jīng)發(fā)布的版本", Configuration.UpdateClientVersion.ToString());
- Pub.AddRow(dt, "要發(fā)布的程序的信息", "");
- Pub.AddRow(dt, "文件名稱", releaseFileName);
- Pub.AddRow(dt, "文件大小", (new FileInfo(releaseFileName)).Length.ToString("N0") + " bytes");
- toRelease = Assembly.LoadFile(releaseFileName).GetName();
- Pub.AddRow(dt, "程序集名稱", toRelease.Name);
- Pub.AddRow(dt, "程序集版本", toRelease.Version.ToString());
- if (myself.Name != toRelease.Name) throw new AppException("要發(fā)布的程序集名稱不正確");
- tbxMessage.Text = "該程序滿足發(fā)布條件,請按“發(fā)布”按鈕進行發(fā)布";
- btnSubmit.Enabled = true;
- }
- catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); }
- finally { dgvMain.DataSource = dt; }
- }
- private void btnSubmit_Click(object sender, EventArgs e)
- {
- tbxMessage.Text = "";
- btnSubmit.Enabled = false;
- try
- {
- using (BinaryReader br = new BinaryReader(new FileStream(releaseFileName, FileMode.Open, FileAccess.Read)))
- {
- byte[] bs = br.ReadBytes((int)br.BaseStream.Length);
- ClientRelease.Update(toRelease.Name, toRelease.Version.ToString(), bs);
- Configuration.UpdateClientVersion = toRelease.Version;
- dgvMain.DataSource = null;
- tbxMessage.Text = "客戶端程序發(fā)布成功, 版本: " + toRelease.Version.ToString();
- }
- }
- catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); }
- }
- private void btnMinClientVersion_Click(object sender, EventArgs e)
- {
- tbxMessage.Text = "";
- btnMinClientVersion.Enabled = false;
- try
- {
- Version version = new Version(tbxMinClientVersion.Text);
- Configuration.ClientVersion = version;
- tbxMinClientVersion.Text = "";
- dgvMain.DataSource = null;
- btnSubmit.Enabled = false;
- tbxMessage.Text = "客戶端程序所需的最低版本已經(jīng)設(shè)置為: " + version.ToString();
- }
- catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); }
- btnMinClientVersion.Enabled = true;
- }
- }
- }
這就是我目前用于更新客戶端應(yīng)用程序的方法。希望對大家有所啟發(fā),起到拋磚引玉的作用。
原文鏈接:http://www.cnblogs.com/skyivben/archive/2010/12/24/1916034.html
【編輯推薦】
- 詳解ASP.NET MVC 3 beta新特性
- ASP.NET MVC 3讓依賴注入實現(xiàn)得更簡單
- 詳解ASP.NET MVC 3 beta新特性
- ASP.NET MVC 3新特性與NuPack功能詳解
- .NET開發(fā)人員應(yīng)該關(guān)注的七個開源項目