应用程序的版本升级
我们写了一个应用程序发布后,以后该应用程序有新版本后如何将升级后的版本部署到客户的机器上去呢?
我目前的做法是:
如果这个升级版本是一个不重要的升级版本,比如说仅针对特定客户增加一些新的功能,可以通知这部分客户按以下方法进行升级:
如上图所示,程序当前版本是 1.225 版,而该程序的更新版本 1.226 版已经发布了。这时可以在程序的“关于”对话框中点击“更新”按钮来升级应用程序。
升级完成后弹出一个对话框,点击“确定”按钮退出本程序后,重新启动该应用程序,发现版本已经更新到 1.226 版了:
此时,由于该程序已经是最新版,无法再升级,所以“更新”按钮不可用。
如果要发布一个关键的升级版本,可以将程序最低版本设置为刚发布的版本。
在这个例子中,程序最低版本被设置为 1.225,已经发布的程序更新版本是 1.226,而客户机器上的程序当前版本是 1.206。因此,客户在登录系统时,会得到一个“ 本程序已经过时”的提示,要求升级程序的版本。点击“是”按钮后:
同样更新了程序,版本升级到 1.226,如下图所示:
上面讲述的是客户如何升级版本。而我们作为程序开发者又如何发布程序的新版本呢?
这非常简单,开发完成一个新版本,并编译成功后,运行这个应用程序,点击“系统 -> 发布应用程序”菜单项,在弹出的“应用程序发布”对话框中点击“发布”按钮就行了。
如果这个版本是关键版本,还可以设置客户端程序所需的最低版本为此版本。
这些功能又是如何实现的呢?
首先,我们的客户端应用程序在运行时需要连接一个 Microsoft SQL Server 数据库服务器。在这个 SQL Server 数据库中有以下数据表:
这个 Adm_Configuration 数据表中的一些记录保存了程序最低版本和程序更新版本的值。
这个 Adm_ClientRelease 数据表保存了程序更新版本的具体信息,其中 ReleaseData 字段的数据类型是 varbinary(max),这个字段中保存着要发布的程序更新版本的二进制代码本身。
下面是用于读 Adm_Configuration 数据表的存储过程:
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'读数据库配置表错, 无此键: ' + @TheKey; RAISERROR (@ErrorMessage, 11, 1); END
下面是用于更新 Adm_Configuration 数据表的存储过程:
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'更新数据库配置表错, 无此键: ' + @TheKey; RAISERROR (@ErrorMessage, 11, 1); END
下面是读取和更新 Adm_Configuration 数据表的 C# 源程序(数据层)的部分代码:
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 数据表中要发布的应用程序更新版本的二进制代码的长度的存储过程:
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 数据表中要发布的应用程序更新版本的二进制代码的存储过程:
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 数据表的存储过程:
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 数据表的 C# 源程序(数据层)的部分代码:
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; } } }
下面是更新应用程序的关键的 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("客户机系统时钟与服务器不一致"); if (!Online) throw new AppException("数据库维护中, 暂停使用. 请联系管理员"); if (DbVersion < Pub.MinDbVersion) throw new AppException("数据库当前版本太低, 请联系管理员"); } /// <summary> /// 判断是否满足进行应用程序更新的条件 /// </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(); } } }
下面是“关于”对话框中涉及“更新”按钮的 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 = "用户已经登录"; return; } Configuration.CheckLogin(); if (Pub.Version < Configuration.ClientVersion) { if (!Configuration.CanUpdate()) throw new Exception("本程序已经过时, 而且没有新版本可用, 请联系管理员"); WaitYesNo(Configuration.UpdateApplicatoin, "本程序已经过时, 有新版本可用, 是否进行更新?"); 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}用户已经登录 或者{0}用户已经被禁用", Environment.NewLine); else { tbxMessage.Text = "用户登录成功"; ((MdiWindow)MdiParent).UpdateStatus(); ((MdiWindow)MdiParent).AfterLogin(); } } catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); } btnSubmit.Enabled = true; } } }
下面是“应用程序发布”对话框的 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, "上次已经发布的版本", Configuration.UpdateClientVersion.ToString()); Pub.AddRow(dt, "要发布的程序的信息", ""); 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("要发布的程序集名称不正确"); tbxMessage.Text = "该程序满足发布条件,请按“发布”按钮进行发布"; 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 = "客户端程序发布成功, 版本: " + 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 = "客户端程序所需的最低版本已经设置为: " + version.ToString(); } catch (Exception ex) { tbxMessage.Text = Pub.GetMessage(ex); } btnMinClientVersion.Enabled = true; } } }
这就是我目前用于更新客户端应用程序的方法。希望对大家有所启发,起到抛砖引玉的作用。