自动更新

C# 编写自动更新程序

现在但凡是一个程序都有相应的升级程序,如果你的程序没有相应的升级程序,那么你就需要留意了。你的用户很可能丢失!!!网上关于自动升级的例子也有很多,前几天一个朋友很苦恼的跟我说它的客户在逐渐减少(据他所说,他都客户因为他的程序升级很麻烦,所以很多人放弃了使用它的软件),问我说怎么办?其实他也知道该怎么办?所以...朋友嘛!就给他做了一个自动升级程序。恰好今天CSDN上的一位老友也询问这件事情,所以就把代码共享大家了。
先个几个图:
 

 

主要原理(相当简单):
升级程序一定要是一个单独的exe,最好不要和你的程序绑到一起(否则升级程序无法启动)。主程序退出----升级程序启动----升级程序访问你的网站的升级配置文件-----读取配置文件里面信息-----下载-----升级程序关闭----主程序启动
主要代码:
1.读取配置文件:
private void GetUpdateInfo()  
{  
    //获取服务器信息  
    WebClient client = new WebClient();  
    doc = new XmlDocument();  
    try 
    {  
        doc.Load(client.OpenRead(
            "http://192.168.3.43/update/update.xml"));  
 
        client = null;  
    }  
    catch 
    {  
        this.labHtml.Text =
            "无法取得更新文件!程序升级失败!";  
        return;  
    }  
    if (doc == null)  
        return;  
    //分析文件  
    XmlNode node;  
    //获取文件列表  
    string RootPath =
        doc.SelectSingleNode("Product/FileRootPath").InnerText.Trim();  
    node = doc.SelectSingleNode("Product/FileList");  
    if (node != null)  
    {  
        foreach (XmlNode xn in node.ChildNodes)  
        {  
            this.listView1.Items.Add(
                new ListViewItem(new string[]  
                {  
                     xn.Attributes["Name"].Value.ToString(),
                     new WebFileInfo(RootPath+xn.Attributes["Name"]
                        .Value.ToString()).GetFileSize().ToString(),  
                    "---" 
                }));  
        }  
    }  
}
 
private void GetUpdateInfo()
{
    //获取服务器信息
    WebClient client = new WebClient();
    doc = new XmlDocument();
    try
    {
        doc.Load(client.OpenRead(
            "http://192.168.3.43/update/update.xml"));
       
        client = null;
    }
    catch
    {
        this.labHtml.Text =
            "无法取得更新文件!程序升级失败!";
        return;
    }
    if (doc == null)
        return;
    //分析文件
    XmlNode node;
    //获取文件列表
    string RootPath =
        doc.SelectSingleNode("Product/FileRootPath").InnerText.Trim();
    node = doc.SelectSingleNode("Product/FileList");
    if (node != null)
    {
        foreach (XmlNode xn in node.ChildNodes)
        {
            this.listView1.Items.Add(
                new ListViewItem(new string[]
                {
                     xn.Attributes["Name"].Value.ToString(),
                     new WebFileInfo(RootPath+
                        xn.Attributes["Name"].Value.ToString()).
                        GetFileSize().ToString(),
                    "---"
                }));
        }
    }
}
2.文件下载:
/// <summary>  
/// 下载文件  
/// </summary>  
public void Download()  
{  
    FileStream fs =
        new FileStream( this.strFile,FileMode.Create,
        FileAccess.Write,FileShare.ReadWrite ); 
 
    try 
    {  
        this.objWebRequest =
            (HttpWebRequest)WebRequest.Create( this.strUrl );  
        this.objWebRequest.AllowAutoRedirect = true;  
        long nCount = 0;  
        byte[] buffer = new byte[ 4096 ];   //4KB  
        int nRecv = 0;  //接收到的字节数  
 
        this.objWebResponse =
            (HttpWebResponse)this.objWebRequest.GetResponse();  
        Stream recvStream = this.objWebResponse.GetResponseStream();  
        long nMaxLength = (int)this.objWebResponse.ContentLength;
 
        if( this.bCheckFileSize && nMaxLength != this.nFileSize )  
        {  
            throw new Exception(
                string.Format( "文件"{0}"被损坏,无法下载!",
                Path.GetFileName( this.strFile ) ) );  
        }  
        if( this.DownloadFileStart != null )  
            this.DownloadFileStart(
                new DownloadFileStartEventArgs( (int)nMaxLength ) ); 
 
        while( true )  
        {  
            nRecv = recvStream.Read( buffer,0,buffer.Length );  
            if( nRecv == 0 )  
                break;  
            fs.Write( buffer,0,nRecv );  
            nCount += nRecv;  
            //引发下载块完成事件  
            if( this.DownloadFileBlock != null )  
                this.DownloadFileBlock(
                    new DownloadFileEventArgs( (int)nMaxLength,
                    (int)nCount ) );  
        }  
 
        recvStream.Close();   
        //引发下载完成事件  
        if( this.DownloadFileComplete != null )  
            this.DownloadFileComplete( this,EventArgs.Empty );  
    }  
    finally 
    {  
        fs.Close();  
    }  
} 
/// <summary>
/// 下载文件
/// </summary>
public void Download()
{
    FileStream fs =
        new FileStream( this.strFile,FileMode.Create,
        FileAccess.Write,FileShare.ReadWrite );
    try
    {
        this.objWebRequest =
            (HttpWebRequest)WebRequest.Create( this.strUrl );
        this.objWebRequest.AllowAutoRedirect = true;
        //    int nOffset = 0;
        long nCount = 0;
        byte[] buffer = new byte[ 4096 ]; //4KB
        int nRecv = 0; //接收到的字节数
        this.objWebResponse =
            (HttpWebResponse)this.objWebRequest.GetResponse();
        Stream recvStream = this.objWebResponse.GetResponseStream();
        long nMaxLength = (int)this.objWebResponse.ContentLength;
 
        if( this.bCheckFileSize && nMaxLength != this.nFileSize )
        {
            throw new Exception(
                string.Format( "文件"{0}"被损坏,无法下载!",
                Path.GetFileName( this.strFile ) ) );
        }
        if( this.DownloadFileStart != null )
            this.DownloadFileStart(
                new DownloadFileStartEventArgs( (int)nMaxLength ) );
 
        while( true )
        {
            nRecv = recvStream.Read( buffer,0,buffer.Length );
            if( nRecv == 0 )
            break;
            fs.Write( buffer,0,nRecv );
            nCount += nRecv;
            //引发下载块完成事件
            if( this.DownloadFileBlock != null )
                this.DownloadFileBlock(
                    new DownloadFileEventArgs( (int)nMaxLength,
                    (int)nCount ) );
        }
        recvStream.Close();
        //引发下载完成事件
        if( this.DownloadFileComplete != null )
            this.DownloadFileComplete( this,EventArgs.Empty );
    }
    finally
    {
        fs.Close();
    }
}

 
欢迎转载,转载请注明出处:)
 
View Code

C# 使用WebService更新客户端软件

先实现 WEB端的开发,主要考虑使用WEBService技术,提供远程服务的调用函数 。由于项目原因,要实施的客户离作者太远,考虑提供软件的在线升级功能.我们C# WebService来实现。先讲下思路.
思路:
    先实现 WEB端的开发,主要考虑使用C# WebService技术,提供远程服务的调用函数,返回一个文件的字节内容,然后写一个升级程序客户端,分发给客户使用的机器中,(可以随客户的软件一起安装).该客户端程序主要连接webserivce,然后将文件保存到本地机(客户的机器)中.就可以实现!
实现的细节:
   要考虑提供给客户软件版本问题,低版本的升级,最新版本的就不用升级.还要考虑用户名与密码在WEB端的认证!
使用技术:
     ASP.Net WebService开发,客户端的异步调用WebService方法.数据库技术!
开始实现:
1. 1.建立数据库,使用SQLSERVER2000  
2. 1)软件项目表:softlist(softid, softname,   
3. resume, loginname, loginpwd)  
4. softid:编号  
5. softname:软件名称  
6. resume:介绍  
7. loginname:客户登录名  
8. loginpwd:密码  
9.  
10.  
11. 2)各个软件的版本表 SoftListVersion(softid, subid,   
12. version, UpdatePath, olefile)  
13. softid:主表的软件编号  
14. subid:各版本数据编号  
15. version:软件版本  
16. filename:升级文件名  
17. olefile:升级文件的二进制内容,是image类型,  
18. (我主要存放MSI的安装包文件类型,可以使用C#做此类安装包文件)  
19.  
20. 3)建立一个视图,chkVersion,用于检查版本号  
21. SELECT dbo.SoftListVersion.subid, dbo.softlist.  
22. softname, dbo.SoftListVersion.version  
23. FROM dbo.softlist INNER JOIN 
24. dbo.SoftListVersion ON dbo.softlist.softid =   
25. dbo.SoftListVersion.softid  
26.  
27. 4)再建立一个视图,vOleFile,用于下载文件  
28. SELECT dbo.SoftListVersion.subid, dbo.softlist.  
29. softname, dbo.SoftListVersion.filename,  
30. dbo.SoftListVersion.olefile, dbo.SoftListVersion.version  
31. FROM dbo.softlist INNER JOIN 
32. dbo.SoftListVersion ON dbo.softlist.softid =   
33. dbo.SoftListVersion.softid  
34.  
35. 2.写一个WEBSERVICE  
36. 1)启动VS.Net2003,建立一个叫babyWebSvc的项目,  
37. 项目类型为(ASP.Net WEB服务)  
38. 2)添加一个SoftUpdate.asmx的WEB服务  
39.  
40. 3)添加一个方法SearchVersion  
41.  
42. [WebMethod(Description=43. 返回当前软件升级包的最高版本”)]  
44. public string SearchVersion(string softname)  
45. {  
46. string sVersion = ””;  
47. webmod.dbConnStart(); //  
48. (连接)作者自己的连接数据库类,用户自己完成数据库连接  
49. string strSQL = ”select MAX(version) as   
50. MaxVerID from chkVersion where softname = @softname”;  
51. SqlCommand sqlCmd = new SqlCommand(strSQL,webmod.sqlConn);  
52. sqlCmd.CommandTimeout = 0;  
53. sqlCmd.Parameters.Add(”@softname”,SqlDbType.VarChar).  
54. Value = softname;  
55. SqlDataReader sqlRd = sqlCmd.ExecuteReader();  
56. if(sqlRd.HasRows)  
57. {  
58. sqlRd.Read();  
59. sVersion = Convert.ToString(sqlRd[”MaxVerID”]);  
60. }  
61. sqlRd.Close();  
62.  
63. webmod.dbConnEnd(); //(断开连接)作者自己的连接数据库类,  
64. 用户自己完成数据库连接  
65.  
66. return sVersion;  
67. }  
68.  
69. 4)添加下载文件内容的方法DownloadSoft  
70.  
71. [WebMethod(Description=”返回需要下载的文件字节”)]  
72. public byte[] DownloadSoft(string UserName,string PassWord,  
73. string SoftDnldName,string SoftHeightVersion)  
74. {  
75. //(连接)作者自己的连接数据库类,用户自己完成数据库连接  
76. webmod.dbConnStart();  
77.  
78. //检查用户合法性  
79. bool bMember = CheckAuth(UserName,PassWord);  
80. //该WebService内的一个检查用户合法性的函数,用户可以自己完成  
81. if(!bMember)  
82. {  
83. webmod.dbConnEnd();  
84. return null;  
85. }  
86. byte[] b = null;  
87.  
88. //我们取出指定软件名称的最高版本的升级包  
89. string strSQL = ”select olefile from vOleFile where   
90. (filename=@softname) and version=@ver”;  
91. SqlCommand sqlCmd = new SqlCommand(strSQL,webmod.sqlConn);  
92. sqlCmd.CommandTimeout = 0;  
93. sqlCmd.Parameters.Add(”@softname”,SqlDbType.VarChar).  
94. Value = SoftDnldName;  
95. sqlCmd.Parameters.Add(”@ver”, SqlDbType.VarChar).  
96. Value = SoftHeightVersion;  
97. SqlDataReader sqlRd = sqlCmd.ExecuteReader();  
98. if(sqlRd.HasRows)  
99. {  
100. sqlRd.Read();  
101. b = (byte[])sqlRd[”olefile”];//文件的字节内容  
102. }  
103. sqlRd.Close();  
104.  
105. //(断开连接)作者自己的连接数据库类,用户自己完成数据库连接  
106. webmod.dbConnEnd();  
107. return b;  
108. }  
    3.WEB服务的方法完成后,你自己可以启动,测试,我们现在来写客户端的升级程序,假定你在开发时的WEBSERVICE的URL为:http://localhost/babywebsvc/SoftUpdate.asmx,注意这个URL,我们是要在客户端引用的
    4.启动VS.Net2003,建立一个C#的Windows项目,在默认的FORM上添加一个按钮,
    5.添加一个新的文件类型(应用程序配置文件)App.config
    App.Config文件的内容
1. 〈?xml version=”1.0” encoding=”utf-8”?2. 〈configuration〉  
3. 〈appSettings〉  
4. 〈add key=”user” value=”test”/5. 〈add key=”pwd” value=”test”/6. 〈add key=”babyRecordSoftName” value=7. TEST.EXE”/〉〈!--记录在远程的数据库中的软件名称--8. 〈add key=”Version” value=”1.0”/9. 〈/appSettings〉  
10. 〈/configuration〉 
    6.我们在Form启动的LOAD事件中,添加如下代码
1. private void Form1_Load(object sender,   
2. System.EventArgs e)  
3. {  
4. //读出版本号,该版本号是在AssemblyInfo.cs  
5. 中由系统本身设置的,[assembly: AssemblyVersion(”1.0”)]  
6. //以后要更改,可以改此处的AssemblyInfo.cs中的版本号,  
7. 例:[assembly: AssemblyVersion(”1.1”)]  
8. //我们的WEBSERVICE中需要这个数据做为参数  
9. string sVersion = Application.ProductVersion;  
10.  
11. //写到App.Cofing文件中,每次调用WEBSERVICE方法时,  
12. 从App.Cofing中读取版本,你也可以直接使用Application.  
13. ProductVersion,我是为了统一管理,全部从config中读取  
14. this.SaveAppConfig(”Version”,sVersion);  
15. }  
16.  
17. //SaveAppConfig函数的内容  
18. public static void SaveAppConfig  
19. (string AppKey,string AppValue)  
20. {  
21. XmlDocument xDoc = new XmlDocument();  
22. xDoc.Load(Application.ExecutablePath + ”.config”);  
23.  
24. XmlNode xNode;  
25. XmlElement xElem1;  
26. XmlElement xElem2;  
27.  
28. xNode = xDoc.SelectSingleNode(”//appSettings”);  
29.  
30. xElem1 = (XmlElement)xNode.SelectSingleNode(  
31. ”//add[@key=” + AppKey + ”]”);  
32. if ( xElem1 != null ) xElem1.SetAttribute(”  
33. value”,AppValue);  
34. else 
35. {  
36. xElem2 = xDoc.CreateElement(”add”);  
37. xElem2.SetAttribute(”key”,AppKey);  
38. xElem2.SetAttribute(”value”,AppValue);  
39. xNode.AppendChild(xElem2);  
40. }  
41. xDoc.Save(Application.ExecutablePath + ”.config”);  
42. }  
    7.主要部分,开始调用webservice的方法!
    准备工作:1)添加一个WEB引用,(先点菜单”项目”-”添加WEB引用”),
    在弹出中输入url的路径:http://localhost/babywebsvc/SoftUpdate.asmx
    2)假定你在开发时的WEBSERVICE的URL:http://localhost/babywebsvc/SoftUpdate.asmx
    3)填入WEB引用名:AutoUpdateWebSvc
    4)点下按纽完成WEB引用的添加
    8.在你的Button1_click事件中添加如下CODE,主要使用异步调用
1. private string svcUser = ””;  
2. private string svcPwd = ””;  
3. private string svcSoftName = ””;  
4. private string svcCurrVersion = ””;  
5. private string svcDnldFileName = ”Test.MSI”;  
6. //下载下来的文件名,  
7. private byte[] fbyte = null;   
8. //下载后的升级文件的内容  
9. private void Button1_Click(object sender,   
10. System.EventArgs e)  
11. {  
12. //读取App.config文件中的配置信息  
13. svcUser = System.Configuration.  
14. ConfigurationSettings.AppSettings[”user”];   
15. //需要人证的用户名  
16. svcPwd = System.Configuration.  
17. ConfigurationSettings.AppSettings[”pwd”];  
18.  //认证密码  
19. svcSoftName = System.Configuration.  
20. ConfigurationSettings.AppSettings  
21. [”babyRecordSoftName”];//软件名称  
22. svcCurrVersion = System.Configuration.  
23. ConfigurationSettings.AppSettings[”Version”];  
24. //当前版本号  
25.  
26. try  
27. {  
28. AutoUpdateWebSvc.SoftUpdate aSvc =   
29. new AutoUpdateWebSvc.SoftUpdate();  
30.  
31. //此处可以改成自己实际应用时的URL,  
32. 不管WEB引用是动态还是静态,调用都会指向该URL  
33. aSvc.Url =34. http://localhost/babyWebSvc/SoftUpdate.asmx”;  
35.  
36. if(Button1.Text.Trim() == ”检 查”)  
37. {  
38. //检查最新版本  
39. System.AsyncCallback cb = new AsyncCallback  
40. (SearchVersionCallBack);//异步回调方法,  
41. 并检查是否有高版本的升级软件存在  
42. aSvc.BeginSearchVersion(svcSoftName,cb,aSvc);  
43. }  
44. else if(Button1.Text.Trim() == ”升 级”)  
45. {  
46. //开始调用下载服务  
47. InvokeDownload(); //函数体见下面的CODE  
48. }  
49.  
50. }  
51. catch(Exception ex)  
52. {  
53. MessageBox.Show(ex.Message);  
54. }  
55. }  
56.  
57.  
58. //检查最新版本的异步回调方法  
59. private void SearchVersionCallBack(System.  
60. IAsyncResult ar)  
61. {  
62. if(ar==null)return;  
63. if(ar.IsCompleted)  
64. {  
65. try  
66. {  
67. AutoUpdateWebSvc.SoftUpdate aSvc =   
68. (AutoUpdateWebSvc.SoftUpdate)ar.AsyncState;  
69. string sVersion = aSvc.EndSearchVersion(ar);  
70. aSvc.Dispose();  
71.  
72.  
73. if(svcCurrVersion.Trim() == sVersion.Trim())  
74. MessageBox.Show”你的软件当前版本已经是最新的了,  
75. 无需进行升级...”);  
76. else if((string.Compare(svcCurrVersion.Trim(),  
77. sVersion.Trim()))==-1)  
78. {  
79.  
80. MessageBox.Show(”你的软件当前版本比较低,  
81. 可以进行升级...”);  
82. Button1.Text = ”升 级”;  
83. }  
84.  
85. }  
86. catch(Exception ex)  
87. {  
88. MessageBox.Show(ex.Message);  
89. }  
90. }  
91. }  
92.  
93. //调用远程的WEB服务,开始下载  
94. private void InvokeDownload()  
95. {  
96. try  
97. {  
98. AutoUpdateWebSvc.SoftUpdate aSvc =   
99. new AutoUpdateWebSvc.SoftUpdate();  
100. //此处可以改成自己实际应用时的URL,  
101. 不管WEB引用是动态还是静态,调用都会指向该URL  
102. aSvc.Url =103. http://localhost/babyWebSvc/SoftUpdate.asmx”;  
104.  
105. //开始下载  
106. System.AsyncCallback cb =   
107. new AsyncCallback(DownloadSoftCallBack);  
108. //异步回调方法,保存文件  
109. aSvc.BeginDownloadSoft(svcUser,svcPwd,  
110. svcDnldFileName,lblVersion.Text.Trim(),cb,aSvc);  
111.  
112. }  
113. catch(Exception ex)  
114. {  
115. MessageBox.Show(ex.Message);  
116. }  
117. }  
118.  
119. //下载方法执行完成后,异步回调方法  
120. private void DownloadSoftCallBack(System.  
121. IAsyncResult ar)  
122. {  
123. if(ar==null)  
124. {  
125. MessageBox.Show(”升级过程中出现错误,  
126. 不能进行升级,请稍后再试...”);  
127. return;  
128. }  
129. if(ar.IsCompleted)  
130. {  
131. try  
132. {  
133. AutoUpdateWebSvc.SoftUpdate aSvc =   
134. (AutoUpdateWebSvc.SoftUpdate)ar.AsyncState;  
135. fbyte = aSvc.EndDownloadSoft(ar);  
136. aSvc.Dispose();  
137.  
138. //使用线程,保存文件  
139. Thread th = new Thread(new ThreadStart(Save2Disk));  
140. th.Start();  
141.  
142. }  
143. catch(Exception ex)  
144. {  
145. MessageBox.Show(”升级过程中出现错误,”+ex.Message);  
146. }  
147. }  
148. }  
149.  
150.  
151. //将下载下来的字节数组保存成文件  
152. private void Save2Disk()  
153. {  
154. try  
155. {  
156. FileInfo finfo = new FileInfo  
157. (Application.ExecutablePath+svcDnldFileName);  
158. if(finfo.Exists)finfo.Delete();//文件存在就删除它  
159. Stream stream = finfo.OpenWrite();  
160.  
161. prosBar.Maximum = fbyte.Length;//prosBar是一个进度条  
162. prosBar.Minimum = 0;  
163. prosBar.Step = 1;  
164. int i=0;  
165. foreach(byte b in fbyte)  
166. {  
167. stream.WriteByte(b);  
168. prosBar.Value += 1;  
169. }  
170. stream.Flush();  
171. stream.Close();  
172.  
173. DialogResult dr = MessageBox.Show  
174. (”下载完成,是否现在就安装升级程序...”,”  
175. 提示信息”,MessageBoxButtons.OKCancel,  
176. MessageBoxIcon.Information,MessageBoxDefaultButton.  
177. Button1);  
178. if(dr == DialogResult.OK)  
179. {  
180. ExecSetup();//启动下载下来的安装程序,用户可以自己完成  
181. }  
182. }  
183. catch(Exception ex)  
184. {  
185. MessageBox.Show(”升级过程中出现错误,”+ex.Message);  
186. }  
187. uiButton2.Enabled = true;  
188. }  
    9:总结,客户端调用,是从,点击Buttton1开始,搜索版本号,SearchVersion,当找到高版本升级包时,开始执行下载的方法DownloadSoft,然后保存到本地Save2Disk.不管客户端的调用是同步还是异步,WEBService的方法都是一样写的,只不过同步调用,是直接使用WEBService中的方法名称,异步调用则会由系统自动生成BeginXXX()与EndXXX()的方法名称,提供给你使用。
    经过上面的步骤,就基本实现了C# WebService更新客户端软件。
 
View Code

C#实现程序的版本自动升级更新

我们做了程序,不免会有版本升级,这就需要程序有自动版本升级的功能。
那么看看我是如何实现程序自动更新的。
直接上代码:
  1 using System;   

  2 using System.Collections.Generic;   

  3 using System.Text;   

  4 using System.Reflection;   

  5 using System.IO;   

  6 using System.Net;   

  7 using System.Xml;   

  8   

  9 namespace Update   

 10 {   

 11     /// <summary>   

 12     /// 更新完成触发的事件   

 13     /// </summary>   

 14     public delegate void UpdateState();   

 15     /// <summary>   

 16     /// 程序更新   

 17     /// </summary>   

 18     public class SoftUpdate   

 19     {   

 20   

 21         private string download;   

 22         private const string updateUrl = "http://www.csdn.net/update.xml";//升级配置的XML文件地址  

 23  

 24         #region 构造函数   

 25         public SoftUpdate() { }   

 26   

 27         /// <summary>   

 28         /// 程序更新   

 29         /// </summary>   

 30         /// <param name="file">要更新的文件</param>   

 31         public SoftUpdate(string file,string softName) {   

 32             this.LoadFile = file;   

 33             this.SoftName = softName;   

 34         }   

 35         #endregion  

 36  

 37         #region 属性   

 38         private string loadFile;   

 39         private string newVerson;   

 40         private string softName;   

 41         private bool isUpdate;   

 42   

 43         /// <summary>   

 44         /// 或取是否需要更新   

 45         /// </summary>   

 46         public bool IsUpdate   

 47         {   

 48             get    

 49             {   

 50                 checkUpdate();   

 51                 return isUpdate;    

 52             }   

 53         }   

 54   

 55         /// <summary>   

 56         /// 要检查更新的文件   

 57         /// </summary>   

 58         public string LoadFile   

 59         {   

 60             get { return loadFile; }   

 61             set { loadFile = value; }   

 62         }   

 63   

 64         /// <summary>   

 65         /// 程序集新版本   

 66         /// </summary>   

 67         public string NewVerson   

 68         {   

 69             get { return newVerson; }   

 70         }   

 71   

 72         /// <summary>   

 73         /// 升级的名称   

 74         /// </summary>   

 75         public string SoftName   

 76         {   

 77             get { return softName; }   

 78             set { softName = value; }   

 79         }  

 80  

 81         #endregion   

 82   

 83         /// <summary>   

 84         /// 更新完成时触发的事件   

 85         /// </summary>   

 86         public event UpdateState UpdateFinish;   

 87         private void isFinish() {   

 88             if(UpdateFinish != null)   

 89                 UpdateFinish();   

 90         }   

 91   

 92         /// <summary>   

 93         /// 下载更新   

 94         /// </summary>   

 95         public void Update()   

 96         {   

 97             try  

 98             {   

 99                 if (!isUpdate)   

100                     return;   

101                 WebClient wc = new WebClient();   

102                 string filename = "";   

103                 string exten = download.Substring(download.LastIndexOf("."));   

104                 if (loadFile.IndexOf(@"") == -1)   

105                     filename = "Update_" + Path.GetFileNameWithoutExtension(loadFile) + exten;   

106                 else  

107                     filename = Path.GetDirectoryName(loadFile) + "\Update_" + Path.GetFileNameWithoutExtension(loadFile) + exten;   

108                 wc.DownloadFile(download, filename);   

109                 wc.Dispose();   

110                 isFinish();   

111             }   

112             catch  

113             {   

114                 throw new Exception("更新出现错误,网络连接失败!");   

115             }   

116         }   

117   

118         /// <summary>   

119         /// 检查是否需要更新   

120         /// </summary>   

121         public void checkUpdate()    

122         {   

123             try {   

124                 WebClient wc = new WebClient();   

125                 Stream stream = wc.OpenRead(updateUrl);   

126                 XmlDocument xmlDoc = new XmlDocument();   

127                 xmlDoc.Load(stream);   

128                 XmlNode list = xmlDoc.SelectSingleNode("Update");   

129                 foreach(XmlNode node in list) {   

130                     if(node.Name == "Soft" && node.Attributes["Name"].Value.ToLower() == SoftName.ToLower()) {   

131                         foreach(XmlNode xml in node) {   

132                             if(xml.Name == "Verson")   

133                                 newVerson = xml.InnerText;   

134                             else  

135                                 download = xml.InnerText;   

136                         }   

137                     }   

138                 }   

139   

140                 Version ver = new Version(newVerson);   

141                 Version verson = Assembly.LoadFrom(loadFile).GetName().Version;   

142                 int tm = verson.CompareTo(ver);   

143   

144                 if(tm >= 0)   

145                     isUpdate = false;   

146                 else  

147                     isUpdate = true;   

148             }   

149             catch(Exception ex) {   

150                               throw new Exception("更新出现错误,请确认网络连接无误后重试!");   

151             }   

152         }   

153   

154         /// <summary>   

155         /// 获取要更新的文件   

156         /// </summary>   

157         /// <returns></returns>   

158         public override string ToString()   

159         {   

160             return this.loadFile;   

161         }   

162     }   

163 }  
把代码编译为一个类库文件,通过程序引用就OK啦。
传入的参数已经有注释了。
下面是更新的XML文件类容,传到空间上面就可以了,得到XML文件的地址。
1 <?xml version="1.0" encoding="utf-8" ?>    

2 <Update>  

3    <Soft Name="BlogWriter">  

4      <Verson>1.0.1.2</Verson>    

5      <DownLoad>http://www.csdn.net/BlogWrite.rar</DownLoad>    

6   </Soft>  

7 </Update>  
程序更新调用方法:
 1、先引用上面的DLL。
 2、调用方法代码 如下:
 1 using System;   

 2 using System.Collections.Generic;   

 3 using System.ComponentModel;   

 4 using System.Data;   

 5 using System.Drawing;   

 6 using System.Text;   

 7 using System.Windows.Forms;   

 8 using System.IO;   

 9 using System.Threading;   

10 using System.Net;   

11 using System.Xml;   

12 using Update;   

13   

14 namespace UpdateTest   

15 {   

16     public partial class Form1 : Form   

17     {   

18         public Form1()   

19         {   

20             InitializeComponent();   

21             checkUpdate();   

22         }   

23   

24         public void checkUpdate()   

25         {   

26             SoftUpdate app = new SoftUpdate(Application.ExecutablePath, "BlogWriter");   

27             app.UpdateFinish += new UpdateState(app_UpdateFinish);   

28             try  

29             {   

30                 if (app.IsUpdate && MessageBox.Show("检查到新版本,是否更新?", "Update", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)   

31                 {   

32   

33                     Thread update = new Thread(new ThreadStart(app.Update));   

34                     update.Start();   

35                 }   

36             }   

37             catch (Exception ex)   

38             {   

39                 MessageBox.Show(ex.Message);   

40             }   

41         }   

42   

43         void app_UpdateFinish() {   

44                 MessageBox.Show("更新完成,请重新启动程序!", "Update", MessageBoxButtons.OK, MessageBoxIcon.Information);   

45         }   

46   

47     }   

48 }  
 
View Code

C#使用FTP实现客户端程序自动更新

最近做的一个项目中需要用到客户端自动更新功能,最初的想法是利用ClickOnce技术来完成,但在实践中发现根本行不能,原因如下:
1)项目应用到了DevExpress控件包,用ClickOnce发布的自动更新程序,客户在安装时报在GAC中找不到控件dll的错。
2)ClickOnce安装无法实现根据用户安装时录入的参数(比如数据库服务器名、数据库用户名和密码等)来动态修改配置文件的功能。
3)最后一下其实不重要了,就是ClickOnce无法实现用户自定义安装文件夹。
  最后决定放弃使用ClickOnce,使用ftp方式进行,实现思路如下:用户启动程序时,先运行update.exe,该文件会自动比较本地配置文件和ftp服务器上配置文件的异同,会自动下载上次更新后变化的文件以及新加入的文件。(因为都是基本配置文件,所以开发了一个配置文件生成工具,用户只需要选择根目录后,就会自动生成配置文件。)文件下载结束后,再启动实际的客户端程序。
  程序的主要代码如:
   


  1

using System;
  2

using System.Collections.Generic;
  3

using System.Diagnostics;
  4

using System.IO;
  5

using System.Net;
  6

using System.Threading;
  7

using System.Windows.Forms;
  8


  9

namespace Update
 10

{
 11

    /// <summary>
 12

    /// Description: 
 13

    /// Author: ZhangRongHua
 14

    /// Create DateTime: 2009-6-21 12:25
 15

    /// UpdateHistory:      
 16

    /// </summary>
 17

    public partial class frmUpdate : Form
 18

    {
 19

        #region Fields
 20


 21

        private const string CONFIGFILE = "update.xml";
 22

        private const string UPDATEDIR = "PMS";
 23

        private string appPath = Application.StartupPath;
 24

        private List<ErrorInfo> errorList = new List<ErrorInfo>();
 25

        private string locFile = String.Concat(Application.StartupPath, "\", CONFIGFILE);
 26

        private string tmpUpdateFile = "TmpUpdate.xml";
 27

        private List<string> updateList;
 28

        private string updateTmpPath = string.Concat(Path.GetTempPath(), "\", UPDATEDIR);   
 29

        private string url = String.Empty;
 30


 31

        private FTP ftp = null;
 32


 33

        #endregion
 34


 35

        #region Delegates
 36


 37

        public delegate void AsycDownLoadFile(string srcFile, string destFile, int i);
 38


 39

        public delegate void ExecuteUpdateFiles(string srcPath, string destPath);
 40


 41

        public delegate void UpdateComplete();
 42


 43

        public delegate void UpdateUI(int i, string message);
 44


 45

        #endregion
 46


 47

        public event UpdateComplete OnUpdateComplete;
 48


 49

        #region Constructor
 50


 51

        public frmUpdate()
 52

        {
 53

            InitializeComponent();
 54

            OnUpdateComplete += new UpdateComplete(frmUpdate_OnUpdateComplete);
 55

        }
 56


 57

        #endregion
 58


 59

        #region Event Handler
 60


 61

        private void frmUpdate_Load(object sender, EventArgs e)
 62

        {
 63

           if(Directory.Exists(updateTmpPath))
 64

           {
 65

               Directory.Delete(updateTmpPath, true);
 66

           }
 67

         
 68

            // 如果有主程序启动,则关闭
 69

            Process[] ps = Process.GetProcesses();
 70

            foreach (Process p in ps)
 71

            {
 72

                //MessageBox.Show(p.ProcessName);
 73

                if (p.ProcessName.ToLower() == "wat.pms.winform")
 74

                {
 75

                    p.Kill();
 76

                    break;
 77

                }
 78

            }
 79


 80

            GetUpdateFiles();
 81

        }
 82


 83

        private void frmUpdate_OnUpdateComplete()
 84

        {
 85

            ExecuteUpdateFiles dExecuteUpdateFiles = new ExecuteUpdateFiles(ExecuteUpdate);
 86

            Invoke(dExecuteUpdateFiles, new object[] {updateTmpPath, appPath});
 87

        }
 88


 89

        private void frmUpdate_Shown(object sender, EventArgs e)
 90

        {
 91

            Thread updateThread = new Thread(new ThreadStart(DownLoadUpdateFiles));
 92

            updateThread.SetApartmentState(ApartmentState.STA);
 93

            updateThread.IsBackground = true;
 94

            Thread.Sleep(500);
 95

            updateThread.Start();
 96


 97

  
 98

         }
 99


100

        #endregion
101


102

        #region Private Methods
103


104

         /// <summary>
105

         /// 将目标文件替换为本地文件
106

         /// <remark> 
107

         /// Author:ZhangRongHua 
108

         /// Create DateTime: 2009-6-21 10:28
109

         /// Update History:     
110

         ///  </remark>
111

         /// </summary>
112

         /// <param name="srcFile">The SRC file.</param>
113

         /// <param name="destFile">The dest file.</param>
114

        private void DownLoadFile(string srcFile, string destFile)
115

        {
116

            if(ftp == null )
117

            {
118

                ftp = new FTP(Updater.URL, "", Updater.User, Updater.Password);
119

            }
120


121

           
122


123

            if(!ftp.Connected)
124

            {
125

                MessageBox.Show("无法连接远程服务器,无法更新程序", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
126

                Process.Start(Updater.MainProgram);
127

                Close();
128

                
129

            }
130


131

            // 得到服务器端的配置文件
132

             ftp.Get(srcFile, updateTmpPath, destFile);
133


134

          
135

 
136

        }
137


138

        /// <summary>
139

        /// 得到需要更新的文件清单
140

        /// <remark> 
141

        /// Author:ZhangRongHua 
142

        /// Create DateTime: 2009-6-21 10:29
143

        /// Update History:     
144

        ///  </remark>
145

        /// </summary>
146

        private void GetUpdateFiles()
147

        {
148

            Updater.GetBaseInfo(locFile);
149

            url = Updater.URL;
150

            string svrFile = String.Concat(url, CONFIGFILE);
151


152

            if (String.IsNullOrEmpty(svrFile))
153

            {
154

                BroadCastOnUpdateComplete();
155

                return;
156

            }
157


158

            Directory.CreateDirectory(updateTmpPath);
159

            try
160

            {
161

                DownLoadFile(CONFIGFILE, tmpUpdateFile);
162

            }
163

            catch (Exception ex)
164

            {
165

                MessageBox.Show("无法连接远程服务器,无法更新程序", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
166

                Process.Start(Updater.MainProgram);
167

                Close();
168

            }
169


170

            updateList = Updater.GetUpdateFileList(locFile, tmpUpdateFile);
171

            if (updateList == null || updateList.Count < 1)
172

            {
173

                BroadCastOnUpdateComplete();
174

                return;
175

            }
176


177

            pbUpdate.Maximum = updateList.Count;
178

            pbUpdate.Minimum = 0;
179

            lblInfo.Text = String.Concat(updateList.Count, "个文件需要更新!");
180

            lblInfo.BringToFront();
181

            lblState.BringToFront();
182

            lblInfo.Update();
183


184

            pbUpdate.Maximum = updateList.Count;
185

            pbUpdate.Minimum = 0;
186


187

            for (int i = 0; i < updateList.Count; i++)
188

            {
189

                string file = updateList[i];
190

                ListViewItem lvItem = new ListViewItem();
191

                lvItem.Text = file;
192

                lvItem.SubItems.Add(Updater.MainProgramVersion);
193

                lvUpdateList.Items.Add(lvItem);
194

            }
195

        }
196


197

        private void UpdateUIInfo(int i, string message)
198

        {
199

            pbUpdate.Value = i + 1;
200

            lblState.Text = message;
201

            lblState.Update();
202

        }
203


204

        private void BroadCastOnUpdateComplete()
205

        {
206

            if (OnUpdateComplete != null)
207

            {
208

                OnUpdateComplete();
209

            }
210

        }
211


212

        private void DownLoadUpdateFiles()
213

        {
214

            if (updateList == null || updateList.Count < 1)
215

            {
216

                BroadCastOnUpdateComplete();
217

                return;
218

            }
219


220

            try
221

            {
222

                UpdateUI dUpdateUI = new UpdateUI(UpdateUIInfo);
223

                AsycDownLoadFile dAsycDownLoadFile = new AsycDownLoadFile(DownLoadFile);
224

                for (int i = 0; i < updateList.Count; i++)
225

                {
226

                    string file = updateList[i];
227

                    string destFile = String.Concat(updateTmpPath, "\", file);
228

                    string destFileName = destFile.Substring(destFile.LastIndexOf("/") + 1);
229

                    string srcFile = String.Concat(url, file);
230

                    var srcFileName = file.Trim('/');
231

                    destFile = destFile.Replace("/", "\");
232

                    Directory.CreateDirectory(destFile.Substring(0, destFile.LastIndexOf("\")));
233

                    string curentFile = String.Concat("正在更新第", i + 1, "/", updateList.Count, "", file);
234

                   
235

                    Invoke(dAsycDownLoadFile, new object[] { srcFileName, srcFileName, i });
236

                    Thread.Sleep(50);
237

                    Invoke(dUpdateUI, new object[] {i, curentFile});
238

                }
239

            }
240

            catch (Exception ex)
241

            {
242

                Debug.WriteLine(ex.Message);
243

            }
244


245

            BroadCastOnUpdateComplete();
246

        }
247


248

        
249


250

        private void CopyUpdateFiles(string srcPath, string destPath)
251

        {
252

            string[] files = Directory.GetFiles(srcPath);
253

            for (int i = 0; i < files.Length; i++)
254

            {
255

                string srcFile = files[i];
256

                string destFile = string.Concat(destPath, "\", Path.GetFileName(srcFile));
257

                try
258

                {
259

                    File.Copy(srcFile, destFile, true);
260

                }
261

                catch (System.IO.IOException  ex)
262

                {
263

                    
264

                    
265

                }
266

                 
267

            }
268


269

            string[] dirs = Directory.GetDirectories(srcPath);
270

            for (int i = 0; i < dirs.Length; i++)
271

            {
272

                srcPath = dirs[i];
273

                string tmpDestPath = String.Concat(destPath, "\", Path.GetFileName(srcPath));
274

                Directory.CreateDirectory(tmpDestPath);
275

                CopyUpdateFiles(srcPath, tmpDestPath);
276

            }
277

        }
278


279


280


281

        /// <summary>
282

        /// 更新完成后,要执行的动作(将下载的文件从临时目录复制到主目录,重启主程序)
283

        /// <remark> 
284

        /// Author:ZhangRongHua 
285

        /// Create DateTime: 2009-6-21 12:25
286

        /// Update History:     
287

        ///  </remark>
288

        /// </summary>
289

        /// <param name="srcPath">The SRC path.</param>
290

        /// <param name="destPath">The dest path.</param>
291

        private void ExecuteUpdate(string srcPath, string destPath)
292

        {
293

            if (errorList != null && errorList.Count < 1)
294

            {
295

                lblInfo.Text = "正在执行更新";
296

                lblInfo.Update();
297

                CopyUpdateFiles(srcPath, destPath);
298

                File.Copy(tmpUpdateFile, locFile, true);
299

            }
300

            Process.Start(Updater.MainProgram);
301


302

            Close();
303

        }
304


305

        private void DownLoadFile(string srcFile, string destFile, ListViewItem lvItem)
306

        {
307

            try
308

            {
309

                DownLoadFile(srcFile, destFile);
310

                lvItem.SubItems.Add("Ok");
311

             }
312

            catch (Exception ex)
313

            {
314

                Debug.WriteLine(ex.Message);
315

                lvItem.SubItems.Add("fail");
316

                ErrorInfo errorInfo = new ErrorInfo();
317
View Code

C#中三层架构的实现介绍

这篇文章讨论如何在C#中实现三层架构,使用MS Access数据库存储数据。在此,我在3层架构中实现一个小型的可复用的组件保存客户数据。并提供添加,更新,查找客户数据的功能。
背景
首先,我介绍一些3层架构的理论知识。简单说明:什么是3层架构?3层架构的优点是什么?
什么是三层架构?
3层架构是一种“客户端-服务器”架构,在此架构中用户接口,商业逻辑,数据保存以及数据访问被设计为独立的模块。主要有3个层面,第一层(表现层,GUI层),第二层(商业对象,商业逻辑层),第三层(数据访问层)。这些层可以单独开发,单独测试。
为什么要把程序代码分为3层。把用户接口层,商业逻辑层,数据访问层分离有许多的优点。
在快速开发中重用商业逻辑组件,我们已经在系统中实现添加,更新,删除,查找客户数据的组件。这个组件已经开发并且测试通过,我们可以在其他要保存客户数据的项目中使用这个组件。
系统比较容易迁移,商业逻辑层与数据访问层是分离的,修改数据访问层不会影响到商业逻辑层。系统如果从用SQL Server存储数据迁移到用Oracle存储数据,并不需要修改商业逻辑层组件和GUI组件
系统容易修改,假如在商业层有一个小小的修改,我们不需要在用户的机器上重装整个系统。我们只需要更新商业逻辑组件就可以了。
应用程序开发人员可以并行,独立的开发单独的层。
代码
这个组件有3层,第一个层或者称为GUI层用form实现,叫做FrmGUI。第二层或者称为商业逻辑层,叫做BOCustomer,是Bussniess Object Customer的缩写。最后是第三层或者称为数据层,叫做DACustomer,是Data Access Customer的缩写。为了方便,我把三个层编译到一个项目中。
用户接口层
下面是用户接口成的一段代码,我只选取了调用商业逻辑层的一部分代码。
 
//This function get the details from the user via GUI
 
//tier and calls the Add method of business logic layer.
 
private void cmdAdd_Click(object sender, System.EventArgs e)
 
{
 
try
 
{
 
cus = new BOCustomer();
 
cus.cusID=txtID.Text.ToString();
 
cus.LName = txtLName.Text.ToString();
 
cus.FName = txtFName.Text.ToString();
 
cus.Tel= txtTel.Text.ToString();
 
cus.Address = txtAddress.Text.ToString();
 
cus.Add();
 
}
 
catch(Exception err)
 
{
 
MessageBox.Show(err.Message.ToString());
 
}
 
}
 
//This function gets the ID from the user and finds the
 
//customer details and return the details in the form of
 
//a dataset via busniss object layer. Then it loops through
 
//the content of the dataset and fills the controls.
 
private void cmdFind_Click(object sender, System.EventArgs e)
 
{
 
try
 
{
 
String cusID = txtID.Text.ToString();
 
BOCustomer thisCus = new BOCustomer();
 
DataSet ds = thisCus.Find(cusID);
 
DataRow row;
 
row = ds.Tables[0].Rows[0];
 
//via looping
 
foreach(DataRow rows in ds.Tables[0].Rows )
 
{
 
txtFName.Text = rows["CUS_F_NAME"].ToString();
 
txtLName.Text = rows["CUS_L_NAME"].ToString();
 
txtAddress.Text = rows["CUS_ADDRESS"].ToString();
 
txtTel.Text = rows["CUS_TEL"].ToString();
 
}
 
}
 
catch (Exception err)
 
{
 
MessageBox.Show(err.Message.ToString());
 
}
 
}
 
//this function used to update the customer details.
 
private void cmdUpdate_Click(object sender, System.EventArgs e)
 
{
 
try
 
{
 
cus = new BOCustomer();
 
cus.cusID=txtID.Text.ToString();
 
cus.LName = txtLName.Text.ToString();
 
cus.FName = txtFName.Text.ToString();
 
cus.Tel= txtTel.Text.ToString();
 
cus.Address = txtAddress.Text.ToString();
 
cus.Update();
 
}
 
catch(Exception err)
 
{
 
MessageBox.Show(err.Message.ToString());
 
}
 
}
 
商业逻辑层
下面是商业逻辑层的所有代码,主要包括定义customer对象的属性。但这仅仅是个虚构的customer对象,如果需要可以加入其他的属性。商业逻辑层还包括添加,更新,查找,等方法。
商业逻辑层是一个中间层,处于GUI层和数据访问层中间。他有一个指向数据访问层的引用cusData = new DACustomer().而且还引用了System.Data名字空间。商业逻辑层使用DataSet返回数据给GUI层。
using System;
using System.Data;
namespace _3tierarchitecture
{
///
/// Summary description for BOCustomer.
///
public class BOCustomer
{
//Customer properties
private String fName;
private String lName;
private String cusId;
private String address;
private String tel;
private DACustomer cusData;
 
public BOCustomer()
{
//An instance of the Data access layer!
cusData = new DACustomer();
}
///
/// Property FirstName (String)
///
public String FName
{
get
{
return this.fName;
}
set
{
try
{
this.fName = value;
if (this.fName == "")
{
throw new Exception(
"Please provide first name ...");
}
}
catch(Exception e)
{
throw new Exception(e.Message.ToString());
}
}
}
///
/// Property LastName (String)
///
public String LName
{
get
{
return this.lName;
}
set
{
//could be more checkings here eg revmove ' chars
//change to proper case
//blah blah
this.lName = value;
if (this.LName == "")
{
throw new Exception("Please provide name ...");
}
}
}
///
/// Property Customer ID (String)
///
public String cusID
{
get
{
return this.cusId;
}
set
{
this.cusId = value;
if (this.cusID == "")
{
throw new Exception("Please provide ID ...");
}
}
}
///
/// Property Address (String)
///
public String Address
{
get
{
return this.address;
}
set
{
this.address = value;

if (this.Address == "")
{
throw new Exception("Please provide address ...");
}
}
}
///
/// Property Telephone (String)
///
public String Tel
{
get
{
return this.tel;
}
set
{
this.tel = value;
if (this.Tel == "")
{
throw new Exception("Please provide Tel ...");
}
}
}
 
///
/// Function Add new customer. Calls
/// the function in Data layer.
///
public void Add()
{
cusData.Add(this);
}
///
/// Function Update customer details.
/// Calls the function in Data layer.
///
public void Update()
{
cusData.Update(this);
}
///
/// Function Find customer. Calls the
/// function in Data layer.
/// It returns the details of the customer using
/// customer ID via a Dataset to GUI tier.
///
public DataSet Find(String str)
{
if (str == "")
throw new Exception("Please provide ID to search");
DataSet data = null;
data = cusData.Find(str);
return data;
}
}
}
 
数据访问层
数据层包括处理MS Access数据库的细节。所有这些细节都是透明的,不会影响到商业逻辑层。数据访问层有个指向商业逻辑层的引用BOCustomer cus。为了应用方便并且支持其他数据库。
 
View Code

Filter与updatebatch混合使用实现批量更新

/*******部分代码*******/
/*******idArr()和codeArr()为两个数组,其中,idArr()为filter数据过滤条件,codeArr()作为更新数据**********/
.......
 
dim CRs,sql,FStr
sql=""

conn.begintrans
set CRs=server.CreateObject("adodb.recordset")
CRs.ActiveConnection=conn
CRs.CursorType=1
CRs.LockType=3
CRs.Source="select * from MB_DCManage"
CRs.Open
for i=0 to ubound(codeArr)-1
FStr="DCM_ID="&idArr(i) '定义数据过滤
CRs.Filter=FStr 
CRs("DCM_Code")=codeArr(i)
next
CRs.UpdateBatch(3) '批量更新
........
/***********避免重复的进行open操作,但是filter本身也有效率问题;应该结合事务处理;filter可进行多条件选择;updateBatch各参数-----adAffectCurrent(1):当前位置;
adAffectGroup(2) :符合filter的数据
adAffterAll(3) :当前所有数据***************/
 
View Code

WinForm App自动更新(Live Update)架构

WinForm App自动更新(Live Update)架构
2010-06-22  来自:博客园  字体大小:【大 中 小】
· 摘要:做了一个小系统,在发布了第一个可用版本之后,顺便实现了自动更新功能。之前没有这方面的经验,也没有翻阅相关资料,自己想了一个简单的思路,如有笑话之处,恳请批评指正。
· 
一. 基本思路
     一直做Web Form开发,最近开始尝试了一下Win Form,做了一个小系统,在发布了第一个可用版本之后,顺便实现了自动更新功能。之前没有这方面的经验,也没有翻阅相关资料,自己想了一个简单的思路,如有笑话之处,恳请批评指正。
    基本上就是这样的:
    客户端有两个子程序,简单的讲就是两个EXE,一个主的应用程序,一个自动Live Update程序,而在服务端,是一个WCF,提供程序版本更新信息和更新文件。
     每当程序启动(或手动点“检测更新”),主程序会调用服务端的WCF检测更新,若检测到新版本,则启动Live Update程序,同时关闭自身。
     Live Update启动后,调用服务端WCF,获取文件列表,然后直接下载更新文件并覆盖本地文件。完毕后启动主程序,同时关闭自身,这样,一次自动更新就完了。
二. 系统架构
    


三. 序列图
    



四. 其它
1. 检测新版本
     在WCF中会有一个XML配置文件,用于客户检测版本和更新文件。
    
2. 下载文件以及覆盖旧文件
     Live Update下载文件后先保存在临时文件夹,下载完毕后再从临时文件夹覆盖主应用程序的旧文件。防止自动更新中途失败导致主应用程序不可用。

3. WCF Contract(仅供参考)

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;



namespace WendyFinance.UpdateCenter.Contract {



     [ServiceContract]

    public interface IAutoUpdate {



         [OperationContract]

        bool CheckUpdate(string clientVersion);



         [OperationContract]

        string GetCurrentVersion();



         [OperationContract]

        string GetUpdateDescription();



         [OperationContract]

         List<string> GetFileList();



         [OperationContract]

        string GetFile(string fileName);

     }

}
 
View Code

版本的自动更新

除了在软件架构设计中需要规划好各功能项以备分别可以实现局部升级外,重要的是配置好自动升级组件。然而该自动升级组件并非直接引用即可,它需要遵守一定的规则部署才能完成自动升级。以下给出它的配置手册。 必备的文件清单:
 
 
 
文件名 功能描述
AppStart.exe 类似中介代理的程序,由它来启动真正的应用程序。不直接启动主应用程序是为了防止主应用程序升级的过程中被锁死而造成升级失败。
AppStart.config AppStart.exe的配置文件,它指定要启动的应用程序所在目录,以及要启动应用程序的名称
appupdater.dll 最重要的部件,自动升级的主要组件
system.Resources.dll 资源文件
mscorlib.Resources.dll 核心资源文件
UpdateVersion.xml 服务器端版本升级配置文件
自动升级配置:
 
 
 
注意事项:
 
 
 
1.  因为自动升级组件appupdater代码里写死的原因,AppStart.config和AppStart.exe是不能改名的!
 
 
 
2.  服务器端最好使用Windows 2000 Server .  使用Windows 2003 Server 会因为网络安全策略造成访问不了服务器目录资源,升级失败。
 
 
 
3.  服务器端需要把提供升级的该站点配置为允许目录浏览。
 
 
 
4.  文件的层次结构必须如下配置: 
客户端执行程序目录结构如下
 
 
 
 AppStart.exe (不能改名) 
 AppStart.config (不能改名) 
 ClientExe (这个目录名称可以自定,但必须同时修改AppStart.config的AppFolderName项内容) 
  TrySmartClient.exe(这个是自定的主应用程序)
  appupdater.dll (自动升级组件)
其中AppStart.config内容如下:
 
 
 
<Config>
 
 
 
  <AppFolderName>ClientExe</AppFolderName>
 
 
 
  <AppExeName>TrySmartClient.exe</AppExeName> 
 
 
 
</Config> 
AppStart.config解释:
 
 
 
<AppFolderName>ClientExe</AppFolderName>
 
 
 
告诉AppStart.exe要寻找的主应用程序目录名称。 
<AppExeName>TrySmartClient.exe</AppExeName>
 
 
 
告诉AppStart.exe要寻找的主应用程序名。
 
 
 
服务器端目录结构(该目录需要设置为允许目录浏览):
 
 
 
 UpdateVersion.xml (必须,不能改名) 
 Ver (可自定,但必须同时修改UpdateVersion.xml的ApplicationUrl项内容) 
  TrySmartClient.exe (新版本主应用程序)
  mscorlib.Resources.dll (资源文件)
  system.Resources.dll(资源文件)
其中UpdateVersion.xml内容如下:
 
 
 
<VersionConfig>
 
 
 
    <AvailableVersion>6.0.0.0</AvailableVersion>
 
 
 
    <ApplicationUrl>http://YourServerDomainName/SmartServer/Ver/</ApplicationUrl>
 
 
 
</VersionConfig>
 
 
 
UpdateVersion.xml解释:
 
 
 
<AvailableVersion>6.0.0.0</AvailableVersion>
 
 
 
告诉客户端目前可用的版本,客户端appupdater组件会比较本地主应用程序版本号和该项配置的版本号,如果比本地的版本号更高,则进行下载更新。
 
 
 
<ApplicationUrl>http://YourServerDomainName/SmartServer/Ver/</ApplicationUrl>
 
 
 
告诉客户端到哪个网址进行下载更新
 
 
 
按上述目录结构和配置文件内容,appupdater组件的关键属性配置应该如下:
 
 
 
AutoFileLoad :True
ChangeDetectionMode ServerManifestCheck
UpdateUrl http://YourServerDomainName/SmartServer/UpdateVersion.xml
appupdater组件关键属性配置解释:
 
 
 
AutoFileLoad = true  //允许自动文件装载
 
 
 
ChangeDetectionMode :ServerManifestCheck  //检测模式:根据配置文件进行检测,也就是UpdateUrl属性指定的文件。
 
 
 
UpdateUrl :指定自动升级组件检测服务器端升级配置文件统一资源定位位置和文件名。(也就是网址)
 
 
 
上述配置在.Net Framework 1.1 和Windows2000下测试通过. appupdater自动升级组件下载地址:http://c2c.6688.com/updateDown/appupdater.rar
 
 
 
现在我们来将它应用在实例中。
 
 
 
第一步:建立应用程序来进行更新
 
 
 
在这一步我们将建立应用程序来演示如何实现自动更新。
 
 
 
1.        使用VS.NET生成一个新的Windows应用项目,命名为“SampleApp”。
 
 
 
2.        给窗体一个你选择的有趣的背景色。我们将使用背景色来与后面更新的版本区别。
 
 
 
3.        现在让我们给这个应用程序增加一个细微的功能,首先给你的窗体增加一个按钮。压缩文件中包含一个拥有简单Windows窗体的程序集。给压缩文件中SamplesSampleAppSimpleForm程序集增加一个引用。然后在你的按钮事件句柄中添加两行代码:
 
 
 
    SimpleForm.Form1 F = new SimpleForm.Form1();
 
 
 
    F.Show();
 
 
 
4.        将你的build标志从debug转换为RELEASE。这将允许我们避免稍后当我们生成一个应用程序的新版本而同时原始拷贝正在运行产生的pdb文件锁定问题。
 
 
 
生成并测试你的应用程序。
 
 
 
第二步:添加.NET应用程序更新组件
 
 
 
在这一步我们将给SampleApp添加.NET应用程序更新组件。
 
 
 
1.        在VS.NET工具栏的组件标签上,右击选择“自定义工具栏”。选择‘.NET框架组件’标签。点“浏览”并选择位于压缩文件中AppUpdater项目下的AppUpdater.dll,单击OK。
 
 
 
2.        一个AppUpdater图标现在应该出现在工具栏的组件列表的底部。将AppUpdater组件拖放到SampleApp窗体上。一个名为appUpdater1的.NET应用程序更新组件的实例会出现在窗体的底部。
 
 
 
第三步:设置.NET应用程序更新组件  
 
 
 
在这一步我们将设置.NET应用程序更新组件。注意这个示例你只需改变最开始的四个属性,其它的,默认值就够了。
 
 
 
AppUpdater  属性 ――这是.NET Application应用程序更新的核心,对于本程序需要做以下设置:
 
属性名称 描述
AutoFileLoad 这个控制后面要描述的命令下载特征,现在将它设置为true。
ChangeDetectionMode 该枚举决定如何为更新进行检查。在该例中,我们将使用一个服务器显式检查,因此将这个值设置为“ServerManifestCheck”。
ShowDefaultUI .NET应用程序更新组件具有一系列用户界面来通知用户一些事件,对于该例我们将使用默认的用户界面,因此将这个值设置为true。
UpdateUrl UpdateUrl是决定更新程序到何处去寻找更新的。在该例中设置为服务器显式文件的URL:   http://yourWebserver/SampleApp_ServerSetup/UpdateVersion.xml.    用你的Web服务器名称来代替”yourWebserver”
 
 
 
Downloader 属性――AppUpdater组件有两个子组件。第一个称之为Downloader,它控制组件的下载和安装。下面是该属性的描述,对于我的示例来说默认的属性值就能工作的很好。
 
 
 
属性名称 描述
DownloadRetryAttempts 在下载期间如果有错误发生(比如Web服务器宕机)downloader会稍后重试。这个属性控制downloader认为是彻底的应用程序更新错误之前重试网络请求的次数。
SecondsBeteweenDownloadRety 重试网络请求之前等待的秒数。
UpdateRetryAttempts 这个属性控制试图更新的次数。
ValidateAssemblies 这个属性控制下载程序集有效完成的级别。更多信息参见这篇文章的安全一节。
Poller 属性――AppUpdater的第二个子组件是Poller。Poller控制更新检查。下面是该属性的描述,对于我们的示例而言,所有的默认属性值就工作的很好。
 
 
 
属性名称 描述
AutoStart 布尔值,在应用程序启动时控制Poller是否应当开始轮询或它是否应当等待直到有计划的显式开始。
DownloadOnDetection 布尔值,控制Poller在一个新的更新发现时是否立即开始下载更新,或者是否通过调用DownloadUdpate()方法必须开始显式下载。
InitialPollInterval 应用程序启动后在第一次执行更新检查前等待的秒数。
PollInterval 第一次更新检查之后,PollInterval控制后续每次更新检查之间间隔的秒数,注意:默认为每30秒进行一次检查。
所有这一切完成之后,你的属性表格看起来应当是下面这个样子:SamplesSampleAppSampleApp_Complete目录包含应用程序正确安装的一个版本。
 
 
 
第四步:生成并在客户端部署应用程序V1版本。
 
 
 
在这一步我们将生成应用程序V1版本并将它部署在客户端。
 
 
 
在SampleApp项目中,打开AssemblyInfo.cs文件。将AssemblyVersion的值从“1.0”修改为“1.0.0.0”.这会引起在生成程序集时获得值为“1.0.0.0”的标记,该标记代替VS.NET通常指定为递增的值。
 
 
 
1.        生成应用程序。
 
 
 
2.        从压缩文件中将SamplesSampleAppSampleApp_ClientSetup目录拷贝到你的本地机器上。要注意SampleApp_ClientSetup目录已经包含了AppStart.exe。AppStart.config已经设置为指向1.0.0.0目录并且启动SampleApp.exe。
 
 
 
从SampleApp的release生成目录下拷贝SampleApp(Appupdater.dll,SimpleForm.dll和SampleApp.exe)到你客户端的SampleApp_ClientSetup1.0.0.0目录下。
 
 
 
在这个时候,一个功能完整的应用程序版本应当被“安装”到了客户端,可以通过运行AppStart.exe来执行。
 
 
 
第五步:安装Web服务器
 
 
 
在这一步我们将安装Web服务器以在轮询应用程序更新时使用。.NET应用程序更新组件使用HTTP-DAV来下载应用程序更新因此需要一个支持HTTP-DAV的Web服务器。Windows 2000上的IIS5.0和更新的操作系统都支持HTTP-DAV。
 
 
 
1.        从压缩文件中将Samples/SampleApp_ServerSetup目录拷贝到你的Web服务器上的wwwroot目录下。
 
 
 
2.        为了完整,将SampleApp的V1版本拷贝到Web服务器的1.0.0.0文件夹。
 
 
 
3.        在你的Web服务器上为SampleApp_ServerSetup目录启用IIS的“目录浏览”。
 
 
 
第六步:自动更新应用程序
 
 
 
OK,现在是时间来通过自动安装一个新版本来看看以上这些艰苦工作的结果了。
 
 
 
1.        如果你部署在客户端的SampleApp版本没有运行,加载它让它运行。记得使用AppStart.exe。
 
 
 
2.        回到VS.NET并在SampleApp窗体中做一些可以被注意到的修改(比如修改背景色)。
 
 
 
3.        将AssemblyInfo.cs的版本信息修改为2.0.0.04.        重新生成。
 
 
 
5.        回到Web服务器并生成一个和1.0.0.0目录同等的目录2.0.0.0。从release生成目录下将新版本应用程序拷贝到Web服务器上新建的2.0.0.0目录下。
 
 
 
6.        打开UpdateVersion.xml并修改AvailableVersion为2.0.0.0。修改ApplicationURL为指向新的2.0.0.0路径。
 
 
 
7.        保存对UpdateVersion.xml所做的修改。
 
 
 
一旦你保存了新的UpdateVersion.xml,在30秒之内,运行中的SampleApp拷贝将会探测到新的可用的版本。SampleApp将下载新版本,实现更新,并弹出默认的用户界面询问用户是否希望重启并立即开始使用新版本。单击“Yes”回应该对话框。SampleApp将会重启并运行新版本。如果你查看客户端SampleApp的部署,你会注意到现在在原始的1.0.0.0的目录后有一个2.0.0.0的目录。1.0.0.0目录将会在下一次更新发生时被清空。
 
 
 
 
 
 
如果由于某种原因,你无法使得.NET应用程序更新器工作。在你深入调试之前确定以下几点,你遇到的问题很可能就是如下之一:
 
 
 
• 你是否将IIS目录浏览给打开了?如果没有,更新器将不会下载安装任何文件。
 
 
 
• 你是否正确的部署了一切并正确设置了URL?
 
 
 
• 如果你的应用程序安装在program files目录下,确定你是该机的超级管理员或超级用户吗?如果不是,你将不会有写权限来更新应用程序。
 
 
 
• 你是在应用程序的主用户界面线程中生成AppUpdater对象的吗?如果不是,更新器将不能显示用户界面并且在激发事件回到用户界面时失败。
 
 
 
• 是否更新成功,但应用程序使用新的更新自动重启时失败?.NET应用程序更新组件试图通过调用Application.Exit方法来重启应用程序。然而,该方法并不能保证关闭一个应用程序。如果你生成并遗留了单独的线程在运行,该方法就无法关闭进程。保证所有线程终止的解决的方案是通过调用Application.OnExit事件,或者挂钩.NET应用程序更新器的OnUpdateComplete事件并自己处理关闭。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
View Code

编写组件,使用JavaScript更新UpdatePanel

众所周知,UpdatePanel是通过Trigger来更新的。被设定为Trigger的控件在PostBack之后会被客户端所截获,并且使用 XMLHttpRequest对象发送内容,然后服务器端由ScriptManager配合,改变Page对象的输出,最后得到部分刷新的效果。但是有时 候我们可能需要使用JavaScript来刷新UpdatePanel,这时候就不太方便了。
  当然,我们又一个属于 Workaround的方法,那就是使用JavaScript来模拟按钮的点击。我们往往会将一个按钮设为某个UpdatePanel的Trigger, 然后在客户端模拟它的点击(我后面会提到,其实这是一个比较糟糕的做法,没有必要),使UpdatePanel进行更新。但是这样的做法实在太麻烦了些, 也相当的不优雅。
  现在我们就来编写一个组件解决这个问题。这个组件的名字叫做JavaScriptUpdater,似乎取得不怎么样——我一直不擅长取名。
 
首先来定一个需求吧
  我们的目的,其实就是为了在客户端生成一个 JavaScript代理,提供一个方法,调用它之后能够刷新页面。如果一个UpdatePanel的UpdateMode为Always,那么它一定会 更新。如果需要更新UpdateMode为Conditional的UpdatePanel,就需要通过在页面中编写tag来设定哪些 UpdatePanel也会被更新。我们需要尽可能的把编程工作变得最小。
  不如我们先考虑使用方式,我们编写的这个JavaScriptUpdater在页面中可以这样使用:
JavaScriptUpdater使用方式
<helper:JavaScriptUpdater runat="server" ID="Updater" MethodName="Refresh"
ResolveUpdatePanel="OnResolveUpdatePanel" Enabled="True">
<UpdatePanels>
<helper:UpdatePanel UpdatePanelID="UpdatePanel1" />
...
</UpdatePanels>
</helper:JavaScriptUpdater>
 
  JavaScriptUpdater有一个 简单属性MethodName,表明了在客户端生成代理方法的名字。再上例中该属性为Refresh,表明我们会调用 UpdatePanels.Refresh()方法进行UpdatePanel更新。UpdatePanels是一个集合属性,可以指定哪些 UpdateMode为Conditional的UpdatePanel一同进行更新。如果某个UpdatePanelID没有找到的话,就会调用 ResolveUpdatePanel事件,让用户来指定一个UpdatePanel。还有一个Enabled属性,用于控制该 JavaScriptUpdater是否生效。
  一个页面里能够放置多个JavaScriptUpdater,这样可以生成多个JavaScript代理方法。这样的设定,应该已经足够用了。
 
实现JavaScriptUpdater
  自然,我们先定义最简单的两个类,UpdatePanelHelper.UpdatePanel类,和ResolveUpdatePanelEventArgs类。由于实在简单,就直接贴一下代码了:
UpdatePanel类
namespace UpdatePanelHelper
{
public class UpdatePanel
{
private string _UpdatePanelID;
public string UpdatePanelID
{
get { return _UpdatePanelID; }
set { _UpdatePanelID = value; }
}
}
}
ResolveUpdatePanelEventArgs类
namespace UpdatePanelHelper
{
public class ResolveUpdatePanelEventArgs : EventArgs
{
private string _ID = null;
public string ID
{
get { return _ID; }
}
private System.Web.UI.UpdatePanel _UpdatePanel = null;
public System.Web.UI.UpdatePanel UpdatePanel
{
get { return _UpdatePanel; }
set { _UpdatePanel = value; }
}
public ResolveUpdatePanelEventArgs(string id)
{
this._ID = id;
}
}
}
 
  然后开始考虑编写最关键的JavaScriptUpdater类。首先定义它的简单框架,如下:
JavaScriptUpdater控件的总体框架与属性定义
namespace UpdatePanelHelper
{
[PersistChildren(false)]
[ParseChildren(true)]
[NonVisualControl]
public class JavaScriptUpdater : Control
{
private bool initialized = false;
private bool _Enabled = true;
public bool Enabled
{
get
{
return this._Enabled;
}
set
{
if (this.initialized)
{
throw new InvalidOperationException(
                        "Cannot set the property after initialized.");
}
this._Enabled = value;
}
}
private string _MethodName;
public string MethodName
{
get
{
return this._MethodName;
}
set
{
if (this.initialized)
{
throw new InvalidOperationException(
                        "Cannot set the property after initialized.");
}
this._MethodName = value;
}
}
public event EventHandler<ResolveUpdatePanelEventArgs> ResolveUpdatePanel;
private List<UpdatePanel> _UpdatePanels = new List<UpdatePanel>();
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<UpdatePanel> UpdatePanels
{
get
{
return _UpdatePanels;
}
}
...
}
}
 
  我们使用一个initialized变量来 确保Enabled或MethodName属性只能在页面Init时进行修改。由于控件会在多个生命周期中进行操作,如果不做这样的限制,会让控制变得繁 琐,容易出错。从下面的代码中会发现,我们会在响应页面的InitComplete事件时将initialized变量设为true。
  我们在这里实现JavaScript更新UpdatePanel的做法,和传统的方法异曲同工,只是我们这里将动态添加按钮,并且在这里我们使用LinkButton。我们将响应Page对象的Load事件,添加那个作为Trigger的LinkButton。如下:
动态添加LinkButton
private string clientButtonId = null;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Page.InitComplete += new EventHandler(Page_InitComplete);
}
private void Page_InitComplete(object sender, EventArgs e)
{
    this.initialized = true;

if (this.Enabled)
{
this.Page.Load += new EventHandler(Page_Load);
this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete);
}
}
private void Page_Load(object sender, EventArgs e)
{
LinkButton button = new LinkButton();
button.Text = "Update";
button.ID = this.ID + "Button";
button.Style[HtmlTextWriterStyle.Display] = "none";
button.Click += new EventHandler(OnTrigger);
this.Page.Form.Controls.Add(button);
this.clientButtonId = button.ClientID;
ScriptManager.GetCurrent(this.Page).RegisterAsyncPostBackControl(button);
}
 
  我们在页面的 Page_InitComplete事件中判断Enabled属性是否为true(这时Enabled属性已经不能修改了),如果Enabled为 ture,则响应页面的Load事件,用于动态添加一个LinkButton。请注意,我们并不会将它的Visible属性设为False,否则它的 HTML将不会出现在页面上。我们应该将它Style的display设为none,这样它既能在页面结构中出现,也不会显示出来。在添加了这个 LinkButton之后,将会保留它的ClientID,并且找出当前页面的ScriptManager,调用 RegisterAsyncPostBackControl方法,将这个LinkButton注册为异步刷新页面的控件。
  以前,我写给别人范例都是使用了一个 Button作为Trigger,然后在客户端使用JavaScript对它进行点击。这其实不是很合理,比较好的做法其实使用LinkButton。要 说明这个问题的原因,我们需要看一下LinkButton在页面中生成的HTML元素。很显然,这是个<a />,如下:
LinkButton生成的HTML
<a id="UpdaterButton" href="javascript:__doPostBack('UpdaterButton','')">Update</a>
 
  请注意它的href,它表明了点击该元素会 执行这个JavaScript代码。发现了不?我们何必模拟元素的点击,我们直接调用__doPostBack函数不就行了?Cool!于是我们现在也可 以轻易得出,在响应页面的PreRenderComplete事件时需要注册的Script脚本了。如下:
在PreRenderComplete事件中注册脚本
private const string BasicScripts =
@"if (!window.UpdatePanels) window.UpdatePanels = {};
UpdatePanels._createUpdateMethod = function(btnId)
{
return function()
{
__doPostBack(btnId, '');
}
}";
private const string RegisterMethodTemplate =
"
UpdatePanels['{0}'] = UpdatePanels._createUpdateMethod('{1}');";
private void Page_PreRenderComplete(object sender, EventArgs e)
{
this.Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"BasicScripts",
JavaScriptUpdater.BasicScripts,
true);
this.Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
this.clientButtonId,
String.Format(JavaScriptUpdater.RegisterMethodTemplate,
            this.MethodName, this.clientButtonId),
true);
}
 
  首先会注册一些基础脚本,我们会使用相同的 Type和Key参数,这样保证了这段代码只会被注册一次。在注册每个代理方法的脚本时,就会使用该脚本的clientButtonId作为key,保证 了每段脚本都会被注册成功。顺便一提,我们在这里直接使用了Page的ClientScriptManager来注册脚本,保证了在异步更新 UpdatePanel时,不会将脚本发送到客户端去。
  可能有朋友会出现疑惑,为什么我们需要在页 面的PreRenderComplete事件中注册脚本呢?在页面的Load事件中一并注册了不可以吗?答案是,因为ScriptManager也是在这 时候注册ASP.NET AJAX的基础脚本,我们现在这么做是为了保证了我们注册的脚本出现在ASP.NET AJAX的脚本之后。
  哦,原来如此……等一下,是否发觉我们现在 的脚本与ASP.NET AJAX的基础脚本无关?没错,事实上我们这里的确可以方法页面的Load事件中注册,我现在这么做似乎只是一个习惯——或者说是为ASP.NET AJAX编写组件的一个模式——响应页面的PreRenderComplete事件,用于注册所需的脚本。
  大部分的工作已经完成了,我们只需要再响应那个LinkButton的Click事件即可。我们需要强制更新所有指定的UpdatePanel。代码如下,非常简单,就不多作解释了:
触发更新
private void OnTrigger(object sender, EventArgs e)
{
if (this.Enabled)
{
foreach (UpdatePanel panel in this.UpdatePanels)
{
System.Web.UI.UpdatePanel updatePanel =
this.FindUpdatePanel(panel.UpdatePanelID);
if (updatePanel != null)
{
updatePanel.Update();
}
}
}
}
private System.Web.UI.UpdatePanel FindUpdatePanel(string id)
{
System.Web.UI.UpdatePanel result = null;
if (id != null)
{
result = this.NamingContainer.FindControl(id)
as System.Web.UI.UpdatePanel;
}
if (result == null)
{
ResolveUpdatePanelEventArgs e = new ResolveUpdatePanelEventArgs(id);
this.OnResolveUpdatePanel(e);
result = e.UpdatePanel;
}
return result;
}
private void OnResolveUpdatePanel(ResolveUpdatePanelEventArgs e)
{
if (this.ResolveUpdatePanel != null)
{
this.ResolveUpdatePanel(this, e);
}
}
 
使用JavaScriptUpdater
  JavaScriptUpdater非常简单,只需一个最简单的例子,大家就可以明白它的使用方式:
JavaScriptUpdater使用示例
<%@ Register Assembly="UpdatePanelHelper" Namespace="UpdatePanelHelper"
 TagPrefix="helper" %>
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<%= DateTime.Now.ToString() %>
</ContentTemplate>
</asp:UpdatePanel>
<helper:JavaScriptUpdater runat="server" ID="Updater" MethodName="Refresh">
<UpdatePanels>
<helper:UpdatePanel UpdatePanelID="UpdatePanel1" />
</UpdatePanels>
</helper:JavaScriptUpdater>
<input type="button" onclick="UpdatePanels.Refresh()" value="Refresh" />
 
  点击最下方定义的按钮时,会调用 UpdatePanels.Refresh()方法,于是则会更新UpdatePanel1。请注意,UpdatePanel1的UpdateMode为 Conditional,我们是通过在JavaScriptUpdater中指定它,用于强制对它进行更新。我们再看一下它生成的JavaScript代 码和HTML就会更加清楚它的实现方式了。如下:
在客户端生成的内容
<script type="text/javascript">
if (!window.UpdatePanels) window.UpdatePanels = {};
UpdatePanels._createUpdateMethod = function(btnId)
{
return function()
{
__doPostBack(btnId, '');
}
}
UpdatePanels['Refresh'] = UpdatePanels._createUpdateMethod('UpdaterButton');
</script>
...
<a id="UpdaterButton"
href="javascript:__doPostBack('UpdaterButton','')"
style="display:none;">Update</a>
 
点击这里下载源文件。
 
View Code

利用XMLHTTP实时更新数据

传统上,我们浏览网页,如果加入最新的数据.只能是等我们重新向服务器端请求时才能显示出来.但是,对于一些时效性很强的网站.传统的这种做法是不能满足的. 我们可以让程序自动刷新.定时向服务器请求数据.5秒取一次数据,10秒取一次数据.利用XMLHTTP发出请求并取得数据.传到客户端,客户端重新组织并显示数据.
demo.htm 前台显示.
以下是代码片段:
<script language="javascript">
function GetResult()
{
/*
*--------------- GetResult() -----------------
* GetResult()
* 功能:通过XMLHTTP发送请求,返回结果.
* 参数:str,字符串,发送条件.
* 实例:GetResult();
*--------------- GetResult() -----------------
*/
var oBao = new ActiveXObject("Microsoft.XMLHTTP");
//特殊字符:+,%,&,=,?等的传输解决办法.字符串先用escape编码的.
//Update:2004-6-1 12:22
oBao.open("POST","Server.asp",false);
oBao.send();
//服务器端处理返回的是经过escape编码的字符串.
var strResult = unescape(oBao.responseText);
//将字符串分开.
var arrResult = strResult.split("###");
RemoveRow(); //删除以前的数据.
//将取得的字符串分开,并写入表格中.
for(var i=0;i<arrResult.length;i++)
{
arrTmp = arrResult[i].split("@@@");
num1 = arrTmp[0]; //字段num1的值
num2 = arrTmp[1]; //字段num2的值
row1 = tb.insertRow();
cell1 = row1.insertCell();
cell1.innerText = num1;
cell2 = row1.insertCell();
cell2.innerText = num2;
}
}
function RemoveRow()
{
//保留第一行表头,其余数据均删除.
var iRows = tb.rows.length;
for(var i=0;i<iRows-1;i++)
{
tb.deleteRow(1);
}
}
function MyShow()
{
//2秒自动刷新一次,2秒取得一次数据.
timer = window.setInterval("GetResult()",2000);
}
</script>
<body onload="MyShow()">
<p>
</p>
<table width="47%" height="23"border="0" cellpadding="1" cellspacing="0" id="tb">
<tr>
<td>num1</td>
<td>num2</td>
</tr>
</table>
Server.asp 后台读取数据
以下是代码片段:
<% @Language="javascript" %>
<%
function OpenDB(sdbname)
{
/*
*--------------- OpenDB(sdbname) -----------------
* OpenDB(sdbname)
* 功能:打开数据库sdbname,返回conn对象.
* 参数:sdbname,字符串,数据库名称.
* 实例:var conn = OpenDB("database.mdb");
*--------------- OpenDB(sdbname) -----------------
*/
var connstr = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source="+Server.MapPath(sdbname);
var conn = Server.CreateObject("ADODB.Connection");
conn.Open(connstr);
return conn;
}
var sResult = new Array();
var oConn = OpenDB("data.mdb");
//特殊字符:+,%,&,=,?等的传输解决办法.客户端字符是经过escape编码的
//所以服务器端先要经过unescape解码.
//Update:2004-6-1 12:22
var sql = "select num1,num2 from nums order by id";
var rs = oConn.Execute(sql);
while(!rs.EOF)
{
//一条记录用"###"隔开.每列数据用"@@@"隔开. 这是以只有两个列数据的情况.
sResult[sResult.length] = rs("num1").Value + "@@@" + rs("num2").Value
rs.MoveNext();
}
//escape解决了XMLHTTP。中文处理的问题.
Response.Write(escape(sResult.join("###")));
%>
数据库data.mdb
表 nums
id,自动编号
num1,文本
num2,文本
测试数据
id num1 num2
1 20.70 20.810
2 10.5 20.5
3 12.3 300
4 132 323
5 563 56
6 20 10
 
View Code

利用反射检查程序集实现自动更新

  在.Net下要让程序实现自动维护程序集的版本并且实现自动更新到最新版本的功能,可以使用反射机制。它提供了检查程序集的方法,通过 System.Reflection 中的 Assembly 类我们可以加载程序集,然后检查它的版本号,以此判断是否需要下载或更新。这里我写了一个示例来实现这个功能。但最后发现一旦加载了程序集就占用了需要更新的程序集文件,导致文件替换失败。为了解决这个问题,我参考了Flier's Sky的Assembly.Unload和Wayfarer's Prattle的通过应用程序域AppDomain加载和卸载程序集。下面就是我的代码,由于时间仓促,估计有些异常还没有处理到。请大家指教。

using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
 
namespace Update
{
    // 序列化这个用来传递参数的类
    public class AssembliyInf : MarshalByRefObject
    {
        public string AssemblyName = "";
        public string AssemblyFileFullPath = "";
        public string Version = "";
        public string Revision = "";
        public string Major = "";
        public string Minor = "";
    }
 
    // 由于是远程调用的方式,所以这个类也需要序列化
    public class AssemblyLoader : MarshalByRefObject, IDisposable
    {
        public AssembliyInf GetAssemblyInf(string fileFullName)
        {
            AssembliyInf assemblyInf = new AssembliyInf();
 
            try
            {
                Assembly assembly = Assembly.ReflectionOnlyLoadFrom(fileFullName);
 
                assemblyInf.AssemblyName = ((AssemblyName)assembly.GetName()).Name;
                assemblyInf.AssemblyFileFullPath = assembly.Location;
                assemblyInf.Version = ((AssemblyName)assembly.GetName()).Version.ToString();
                assemblyInf.Revision = ((AssemblyName)assembly.GetName()).Version.Revision.ToString();
                assemblyInf.Major = ((AssemblyName)assembly.GetName()).Version.Major.ToString();
                assemblyInf.Minor = ((AssemblyName)assembly.GetName()).Version.Minor.ToString();
 
                assembly = null;     // 释放引用
 
                // 手工调用框架的垃圾收集器
                System.GC.Collect();
                System.GC.WaitForPendingFinalizers();
                System.GC.Collect(0);
            }
            catch (Exception)
            {
            }
 
            return assemblyInf;
        }
 
        public void Dispose()
        {
 
        }
    }
 
 
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine();
 
            string sourceFile, distinationFile;
            sourceFile = @"D:MyAppUpdatemyApp.exe";          // 假定准备更新的程序集已经下载到本地
            distinationFile = @"D:MyAppmyApp.exe";            // 这是要维护的目标程序集,发现新版本后就需要替换
 
            // 显示准备更新的程序集信息
            AssembliyInf assemblyNew = GetAssemblyInf(sourceFile);
            ShowAssembly(assemblyNew);
 
            // 显示当前使用的程序集信息
            AssembliyInf assemblyCurrent = GetAssemblyInf(distinationFile);
            ShowAssembly(assemblyCurrent);
 
            // 比较两个程序集
            if (Compare(assemblyNew, assemblyCurrent))
            {
                Console.WriteLine("需要更新当前程序集!");
                // 开始更新
                Update(assemblyNew, assemblyCurrent);
            }
            else
                Console.WriteLine("不需要更新当前程序集!");
 
            Console.ReadKey();
        }
 
        // 用新的程序集替换现有的
        static void Update(AssembliyInf assemblyNew, AssembliyInf assemblyCurrent)
        {
            string sourceFile, distinationFile;
 
            sourceFile = assemblyNew.AssemblyFileFullPath;
            distinationFile = assemblyCurrent.AssemblyFileFullPath;
 
            // 替换文件
            File.Copy(sourceFile, distinationFile, true);
        }
 
        // 显示程序集相关信息
        static void ShowAssembly(AssembliyInf assembly)
        {
            Console.WriteLine("Assembly Name:             " + assembly.AssemblyName );
            Console.WriteLine("Assembly Version.Current:  " + assembly.Version);
            Console.WriteLine("Assembly Version.Revision: " + assembly.Revision);
            Console.WriteLine("Assembly Version.Major:    " + assembly.Major);
            Console.WriteLine("Assembly Version.Minor:    " + assembly.Minor);
            Console.WriteLine("Assembly FullName:         " + assembly.AssemblyFileFullPath);
            Console.WriteLine();
        }
 
        // 比较两个程序集判断是否需要更新
        static bool Compare(AssembliyInf assemblyNew, AssembliyInf assemblyCurrent)
        {
            if ((assemblyNew.AssemblyName == assemblyCurrent.AssemblyName)
                && (int.Parse(assemblyNew.Revision) > int.Parse(assemblyCurrent.Revision)))
                return true;
            else
                return false;
        }
 
        // 获取程序集的信息
        static AssembliyInf GetAssemblyInf(string fileFullName)
        {
            AssembliyInf assemblyInf = new AssembliyInf();
 
            string dllName = typeof(Program).Assembly.Location;
            AppDomain domain = null;
            AppDomainSetup setup = new AppDomainSetup();
            setup.ShadowCopyFiles = "true";
 
            domain = AppDomain.CreateDomain(dllName, null, setup);
 
            AssemblyLoader al = (AssemblyLoader)domain.CreateInstanceFromAndUnwrap(dllName, "Update.AssemblyLoader");
            AssembliyInf tmpAssemblyInf = al.GetAssemblyInf(fileFullName);
 
            // 由于使用了序列化导致传回的对象不能传出这个方法,所以要转换一下
            assemblyInf.AssemblyName = tmpAssemblyInf.AssemblyName;
            // 又因为是使用了子程序域的方法,实际执行加载的子程序域是一个临时文件。返回值是一个临时文件。
            //assemblyInf.AssemblyFileFullPath = tmpAssemblyInf.AssemblyFileFullPath;
            assemblyInf.AssemblyFileFullPath = fileFullName;
            assemblyInf.Version = tmpAssemblyInf.Version;
            assemblyInf.Major = tmpAssemblyInf.Major;
            assemblyInf.Minor = tmpAssemblyInf.Minor;
            assemblyInf.Revision = tmpAssemblyInf.Revision;
 
            AppDomain.Unload(domain);
 
            return assemblyInf;
        }
    }
 
}
 
View Code

软件更新程序(使用WinRar压缩包)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Diagnostics;

namespace UpDate
{
public partial class Form1 : Form
{
private WebClient update = new WebClient();

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
string URL = @"http://files.cnblogs.com/mossan/Zhongwen.rar";
int n = URL.LastIndexOf('/');
string fileName = URL.Substring(n + 1, URL.Length - n - 1);

if (!(Directory.Exists(Application.StartupPath + "\update")))
{
Directory.CreateDirectory(Application.StartupPath + "\update");
}

try { update.DownloadFile(URL, Application.StartupPath + "\update\" + fileName); }
catch (WebException ex) { MessageBox.Show(ex.Message, "Error"); }

try 
{
Process UnWinrar = new Process();
UnWinrar.StartInfo.FileName = "WinRAR.exe";
UnWinrar.StartInfo.Arguments = "e -o+ "" + Application.StartupPath + "\update\" + fileName + """ + " "" + Application.StartupPath + "\update" + """;
UnWinrar.Start();
MessageBox.Show("解压缩完成!");
File.Copy(Application.StartupPath + "\update\" + "Zhongwen.exe", Application.StartupPath + "\Zhongwen.exe", true);

Process ProZhongwen = new Process();
ProZhongwen.StartInfo.FileName = Application.StartupPath + "\Zhongwen.exe";
ProZhongwen.Start();
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
this.Close();
}
}
}
View Code

网站文件更新

在进行网站更新时,往往更新的文件不太多,但文件处于不同的目录中。这样更新时,既要备份原始文件,又要进行更新,很是麻烦。如果是7个前端机,真是烦不胜烦。所以便有了此工具。
    其实,Xcopy命令已经实现了,我们只要好好的利用它就可以。即:先把要更新的复制到备份目录,然后从目的目录中把备份目录中存在的文件复制过来。再把新文件复制过去就可以了。


@echo off
echo ****************************************************   
echo *** 版本:V1.1                                   ***   
echo *** 功能:把指定目录中的文件,全部复制到指定目录 ***   
echo ***       并备份原来已经存在的文件到备份目录     ***   
echo *** 编写:河北全通软件中心 牛昆亮 2009-2.19      ***   
echo ****************************************************   
if "%1"=="" goto input 
if "%2"=="" goto input 
set src=%1
set des=%2
:begin
if "%src%"=="" goto usage 
if "%des%"=="" goto usage 
echo ===============================================
echo             将要执行升级
echo            新文件路径名  :%src%
echo            原始文件路径名:%des%
set /p gotoEnd="            按Y继续,其它退出:"
if not "%gotoEnd%" == "y" goto end
set bakpath=bak_%date:~0,10%%time:~0,2%%time:~3,2%%time:~6,2%
xcopy "%src%*.*" %bakpath% /S 
xcopy "%des%*.*" %bakpath% /S /U /R /Y 
xcopy "%src%*.*" "%des%" /S /R

goto end

:input
set /P src=输入新文件路径名(如:SrcPath):
set /p des=输入原始文件路径名(如:DesPath):
goto begin
:usage
echo usage:update srcpath despath
:end
echo press any key to exit
pause
 
  update-tool.rar
技术交流,欢迎转载。转载请注明出处 http://evlon.cnblogs.com QQ:evlion@qq.com
 
View Code

一段简单软件更新程序代码(WinRar压缩包)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Diagnostics;

namespace UpDate
{
public partial class Form1 : Form
{
private WebClient update = new WebClient();

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
string URL = @"http://files.cnblogs.com/mossan/Zhongwen.rar";
int n = URL.LastIndexOf('/');
string fileName = URL.Substring(n + 1, URL.Length - n - 1);

if (!(Directory.Exists(Application.StartupPath + "\update")))
{
Directory.CreateDirectory(Application.StartupPath + "\update");
}

try 
{
update.DownloadFile(URL, Application.StartupPath +
"\update\" + fileName);
}
catch (WebException ex) { MessageBox.Show(ex.Message, "Error"); }

try 
{
Process UnWinrar = new Process();
UnWinrar.StartInfo.FileName = "WinRAR.exe";
UnWinrar.StartInfo.Arguments = "e -o+ "" + Application.StartupPath +
"\update\" + fileName + """ + " "" + Application.StartupPath +
"\update" + """;
UnWinrar.Start();
MessageBox.Show("解压缩完成!");
File.Copy(Application.StartupPath + "\update\" +
"Zhongwen.exe", Application.StartupPath + "\Zhongwen.exe", true);

Process ProZhongwen = new Process();
ProZhongwen.StartInfo.FileName = Application.StartupPath + "\Zhongwen.exe";
ProZhongwen.Start();
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
this.Close();
}
}
}
View Code

自动更新程序的设计框架

概要说明:
自动更新程序主要负责从服务器中获取相应的更新文件,并且把这些文件下载到本地,替换现有的文件。达到修复Bug,更新功能的目的。
本文作为更新程序的一个框架性设计,主要侧重介绍功能和流程。也许在若干年后,我再回顾当初自己的设计,会有另外一份所获。
 
系统组成:
FTP服务器 主要存放要更新的所有文件。提供客户端下载
更新文件生成功能 根据FTP文件夹中的内容,生成更新列表(xml文件)
自动更新程序 判断版本号,下载最新的文件,替换现有程序。
 
服务端的流程图:
 
 

 
客户端的流程图:
 
 



更新列表文件介绍:
 
 

 
 
补充:
1:更新程序没有做成全自动。用户必须手工点击更新按钮,才启动更新程序。
因为目前的系统平时更新并不频繁。在系统刚上线的时候,可能更新会勤一些,但在系统运行稳定后,也许一年半载都不会去更新。
 
2:更新程序如果执行到一半,发生意外导致更新被迫终止,目前的解决方案是让用户再重新更新一次。没有断电续传。没有中间过程恢复。
如果更新程序使用并不频繁,就没必要加入过多的逻辑。偶尔出一些错误,只要不影响业务程序运行,绝大数用户还是可以忍受的。
 
 3:虽然此更新功能和windows的update,瑞星的update相比,简直就是一个小儿科程序,但是这个程序的却也满足了我参与的所有项目的要求。
不求最好,够用就行。呵呵
 
View Code
原文地址:https://www.cnblogs.com/blogpro/p/11458373.html