实现C/S程序的自动更新2
2015-11-20 15:02
459 查看
转自:http://blog.csdn.net/danjiewu/article/details/3176001?P_AVPASS=PHDGBITAVPASSP
最近在做一个.Net C/S的系统,需要实现自动更新。MS已经提供了ClickOnce,很方便,但是用起来不太习惯,还是决定自己写一个简单的。
自动更新无非文件比较、下载、启动程序几个步骤,其中文件比较可以通过手动在配置文件中维护版本号,也可以比较文件的MD5值,或者在.Net里还可以用Assembly或文件的版本号。因为怕麻烦,手动维护不考虑,剩下两者各有所长,都提供了以供选择。下载就比较简单了,http、ftp、WebService都可以选择。启动程序一般用System.Diagnostics.Process.Start就可以,我用的是AppDomain.ExecuteAssembly。
要自动生成文件版本信息,需要有个服务端,可以是WebService等等,我采用的是自己实现IHttpHandler,提供文件的版本信息和下载。
下面是UpdateHelper的类图和定义:
GetUpdateInfos、CheckIfNeedUpdate、DownloadFile分别是获取文件版本信息、比较文件是否是最新的、下载文件的作用,很简单。
public static class UpdateHelper
{
public static UpdateInfo[] GetUpdateInfos()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Properties.Settings.Default.Server);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
using (Stream output = request.GetResponse().GetResponseStream())
{
XmlSerializer xml = new XmlSerializer(typeof(UpdateInfo[]));
return (UpdateInfo[])xml.Deserialize(output);
}
}
else
{
throw new Exception("Exception occurs on the server.");
}
}
public static bool CheckIfNeedUpdate(UpdateInfo update)
{
if (!File.Exists(update.FileName))
{
return true;
}
else
{
switch (update.VersionType)
{
case VersionType.FileVersion:
return FileVersionInfo.GetVersionInfo(update.FileName).FileVersion != update.Version;
case VersionType.MD5:
using (FileStream file = File.OpenRead(update.FileName))
{
return Convert.ToBase64String(MD5.Create().ComputeHash(file)) != update.Version;
}
default: return false;
}
}
}
public static void Update()
{
UpdateInfo[] updates = UpdateHelper.GetUpdateInfos();
List<Thread> downloadsThreads = new List<Thread>();
foreach (UpdateInfo update in updates)
{
if (UpdateHelper.CheckIfNeedUpdate(update))
{
Thread thread = new Thread(delegate(object param) { UpdateHelper.DownloadFile((string)param); });
thread.Start(update.FileName);
downloadsThreads.Add(thread);
}
}
foreach (Thread thread in downloadsThreads) thread.Join();
}
public static void DownloadFile(string fileName)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Properties.Settings.Default.Server + "?download=" + fileName);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
byte[] buffer = new byte[8096];
using (Stream output = request.GetResponse().GetResponseStream())
{
using (FileStream file = File.Open(fileName, FileMode.OpenOrCreate))
{
int length = output.Read(buffer, 0, buffer.Length);
while (length != 0)
{
file.Write(buffer, 0, length);
length = output.Read(buffer, 0, buffer.Length);
}
}
}
}
else
{
throw new Exception("Exception occurs on the server.");
}
}
}
public class UpdateInfo
{
public UpdateInfo() { }
public UpdateInfo(string filename, VersionType versionType, string version)
{
FileName = filename;
VersionType = versionType;
Version = version;
}
public string FileName;
public VersionType VersionType;
public string Version;
}
public enum VersionType
{
FileVersion,
MD5
}
UpdateServiceHandler定义:
public class UpdateServiceHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (String.Equals(context.Request.RequestType, "GET", StringComparison.OrdinalIgnoreCase))
{
string filename = context.Request.QueryString["download"];
UpdateInfoSection updateInfoSection = GetUpdateConfig();
if (String.IsNullOrEmpty(filename))
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "text/xml";
foreach (UpdateInfo update in updateInfoSection.UpdateFiles.Values)
{
switch (update.VersionType)
{
case VersionType.FileVersion:
update.Version = FileVersionInfo.GetVersionInfo(update.Path).FileVersion;
break;
case VersionType.MD5:
using (FileStream file = File.OpenRead(update.Path)) update.Version = Convert.ToBase64String(MD5.Create().ComputeHash(file));
break;
}
}
XmlSerializer xml = new XmlSerializer(typeof(List<UpdateInfo>));
xml.Serialize(context.Response.Output, new List<UpdateInfo>(updateInfoSection.UpdateFiles.Values));
}
else
{
UpdateInfo update;
if (updateInfoSection.UpdateFiles.TryGetValue(filename, out update))
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "application/x-msdownload";
context.Response.AppendHeader("Content-Disposition", "attachment;filename=" + filename);
context.Response.WriteFile(update.Path);
}
else
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "text/html";
context.Response.Output.WriteLine("File {0} not fount.", filename);
}
}
}
}
private UpdateInfoSection GetUpdateConfig()
{
UpdateInfoSection updateInfoSection = (UpdateInfoSection)ConfigurationManager.GetSection("ClientUpdateInfo");
if (!updateInfoSection.Initialize)
{
foreach (UpdateInfo update in updateInfoSection.UpdateFiles.Values)
{
if (String.IsNullOrEmpty(update.Path)) update.Path = Path.Combine(updateInfoSection.DefaultForlder, update.FileName);
update.Path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, update.Path);
}
updateInfoSection.Initialize = true;
}
return updateInfoSection;
}
#endregion
}
public class UpdateInfoSection : ConfigurationSection
{
public string DefaultForlder;
public Dictionary<string, UpdateInfo> UpdateFiles = new Dictionary<string, UpdateInfo>();
public bool Initialize = false;
protected override void DeserializeSection(System.Xml.XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == System.Xml.XmlNodeType.Element)
{
switch (reader.Name)
{
case "File":
string versionType = reader.GetAttribute("VersionType");
UpdateFiles.Add(reader.GetAttribute("FileName"),
new UpdateInfo(
reader.GetAttribute("FileName"),
String.IsNullOrEmpty(versionType) ? VersionType.FileVersion : (VersionType)Enum.Parse(typeof(VersionType), versionType, true),
reader.GetAttribute("Version"),
reader.GetAttribute("Path")));
break;
case "DefaultFolder":
DefaultForlder = reader.ReadString();
break;
}
}
}
}
}
public class UpdateInfo
{
public UpdateInfo() { }
public UpdateInfo(string filename, VersionType versionType, string version, string path)
{
FileName = filename;
VersionType = versionType;
Version = version;
Path = path;
}
public string FileName;
[XmlIgnore]
public string Path;
public VersionType VersionType;
public string Version;
}
public enum VersionType
{
FileVersion,
MD5
}
注意下其中UpdateInfo多了一个Path属性,表示文件的实际路径,不需要显示给客户端,因此加了XmlIgnore标记。
在服务端需要配置UpdateInfoSection项和UpdateServiceHandler的地址映射。
在<system.web>节<httpHandlers>下添加一项:
<httpHandlers>
<add verb="*" path="update.aspx" type="MyService.UpdateServiceHandler"/>
</httpHandlers>
其中MyService.UpdateServiceHandler需要替换为UpdateServiceHandler的完整名称。
然后添加UpdateInfoSection项:
<configSections>
<section name="ClientUpdateInfo" type="MyService.UpdateInfoSection"/>
</configSections>
<ClientUpdateInfo>
<DefaultFolder>bin</DefaultFolder>
<File FileName ="log4net.dll"/>
<File FileName ="Castle.Core.dll"/>
<File FileName ="Castle.DynamicProxy2.dll"/>
<File FileName ="Client.dll" Path ="bin/Client.exe" />
<File FileName ="client.config" VersionType="MD5"/>
</ClientUpdateInfo>
DefaultFolder是在没有为文件提供Path时默认的包含文件的目录,配置比较简单。
好了,现在可以打开update.aspx页面看到文件的版本信息
在update.aspx后加上参数的话,例如?download=log4net.dll,就可以下载文件了。
最后,客户端只需要更新并运行程序就可以了,当然还需要设置一下服务端的地址(Properties.Settings.Default.Server):
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
UpdateHelper.Update();
AppDomain domain = AppDomain.CreateDomain("client");
domain.SetData("APP_CONFIG_FILE", "client.config");
domain.ExecuteAssembly("Client.dll", AppDomain.CurrentDomain.Evidence, Environment.GetCommandLineArgs());
}
}
最近在做一个.Net C/S的系统,需要实现自动更新。MS已经提供了ClickOnce,很方便,但是用起来不太习惯,还是决定自己写一个简单的。
自动更新无非文件比较、下载、启动程序几个步骤,其中文件比较可以通过手动在配置文件中维护版本号,也可以比较文件的MD5值,或者在.Net里还可以用Assembly或文件的版本号。因为怕麻烦,手动维护不考虑,剩下两者各有所长,都提供了以供选择。下载就比较简单了,http、ftp、WebService都可以选择。启动程序一般用System.Diagnostics.Process.Start就可以,我用的是AppDomain.ExecuteAssembly。
要自动生成文件版本信息,需要有个服务端,可以是WebService等等,我采用的是自己实现IHttpHandler,提供文件的版本信息和下载。
下面是UpdateHelper的类图和定义:
GetUpdateInfos、CheckIfNeedUpdate、DownloadFile分别是获取文件版本信息、比较文件是否是最新的、下载文件的作用,很简单。
public static class UpdateHelper
{
public static UpdateInfo[] GetUpdateInfos()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Properties.Settings.Default.Server);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
using (Stream output = request.GetResponse().GetResponseStream())
{
XmlSerializer xml = new XmlSerializer(typeof(UpdateInfo[]));
return (UpdateInfo[])xml.Deserialize(output);
}
}
else
{
throw new Exception("Exception occurs on the server.");
}
}
public static bool CheckIfNeedUpdate(UpdateInfo update)
{
if (!File.Exists(update.FileName))
{
return true;
}
else
{
switch (update.VersionType)
{
case VersionType.FileVersion:
return FileVersionInfo.GetVersionInfo(update.FileName).FileVersion != update.Version;
case VersionType.MD5:
using (FileStream file = File.OpenRead(update.FileName))
{
return Convert.ToBase64String(MD5.Create().ComputeHash(file)) != update.Version;
}
default: return false;
}
}
}
public static void Update()
{
UpdateInfo[] updates = UpdateHelper.GetUpdateInfos();
List<Thread> downloadsThreads = new List<Thread>();
foreach (UpdateInfo update in updates)
{
if (UpdateHelper.CheckIfNeedUpdate(update))
{
Thread thread = new Thread(delegate(object param) { UpdateHelper.DownloadFile((string)param); });
thread.Start(update.FileName);
downloadsThreads.Add(thread);
}
}
foreach (Thread thread in downloadsThreads) thread.Join();
}
public static void DownloadFile(string fileName)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Properties.Settings.Default.Server + "?download=" + fileName);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
byte[] buffer = new byte[8096];
using (Stream output = request.GetResponse().GetResponseStream())
{
using (FileStream file = File.Open(fileName, FileMode.OpenOrCreate))
{
int length = output.Read(buffer, 0, buffer.Length);
while (length != 0)
{
file.Write(buffer, 0, length);
length = output.Read(buffer, 0, buffer.Length);
}
}
}
}
else
{
throw new Exception("Exception occurs on the server.");
}
}
}
public class UpdateInfo
{
public UpdateInfo() { }
public UpdateInfo(string filename, VersionType versionType, string version)
{
FileName = filename;
VersionType = versionType;
Version = version;
}
public string FileName;
public VersionType VersionType;
public string Version;
}
public enum VersionType
{
FileVersion,
MD5
}
UpdateServiceHandler定义:
public class UpdateServiceHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (String.Equals(context.Request.RequestType, "GET", StringComparison.OrdinalIgnoreCase))
{
string filename = context.Request.QueryString["download"];
UpdateInfoSection updateInfoSection = GetUpdateConfig();
if (String.IsNullOrEmpty(filename))
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "text/xml";
foreach (UpdateInfo update in updateInfoSection.UpdateFiles.Values)
{
switch (update.VersionType)
{
case VersionType.FileVersion:
update.Version = FileVersionInfo.GetVersionInfo(update.Path).FileVersion;
break;
case VersionType.MD5:
using (FileStream file = File.OpenRead(update.Path)) update.Version = Convert.ToBase64String(MD5.Create().ComputeHash(file));
break;
}
}
XmlSerializer xml = new XmlSerializer(typeof(List<UpdateInfo>));
xml.Serialize(context.Response.Output, new List<UpdateInfo>(updateInfoSection.UpdateFiles.Values));
}
else
{
UpdateInfo update;
if (updateInfoSection.UpdateFiles.TryGetValue(filename, out update))
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "application/x-msdownload";
context.Response.AppendHeader("Content-Disposition", "attachment;filename=" + filename);
context.Response.WriteFile(update.Path);
}
else
{
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentType = "text/html";
context.Response.Output.WriteLine("File {0} not fount.", filename);
}
}
}
}
private UpdateInfoSection GetUpdateConfig()
{
UpdateInfoSection updateInfoSection = (UpdateInfoSection)ConfigurationManager.GetSection("ClientUpdateInfo");
if (!updateInfoSection.Initialize)
{
foreach (UpdateInfo update in updateInfoSection.UpdateFiles.Values)
{
if (String.IsNullOrEmpty(update.Path)) update.Path = Path.Combine(updateInfoSection.DefaultForlder, update.FileName);
update.Path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, update.Path);
}
updateInfoSection.Initialize = true;
}
return updateInfoSection;
}
#endregion
}
public class UpdateInfoSection : ConfigurationSection
{
public string DefaultForlder;
public Dictionary<string, UpdateInfo> UpdateFiles = new Dictionary<string, UpdateInfo>();
public bool Initialize = false;
protected override void DeserializeSection(System.Xml.XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == System.Xml.XmlNodeType.Element)
{
switch (reader.Name)
{
case "File":
string versionType = reader.GetAttribute("VersionType");
UpdateFiles.Add(reader.GetAttribute("FileName"),
new UpdateInfo(
reader.GetAttribute("FileName"),
String.IsNullOrEmpty(versionType) ? VersionType.FileVersion : (VersionType)Enum.Parse(typeof(VersionType), versionType, true),
reader.GetAttribute("Version"),
reader.GetAttribute("Path")));
break;
case "DefaultFolder":
DefaultForlder = reader.ReadString();
break;
}
}
}
}
}
public class UpdateInfo
{
public UpdateInfo() { }
public UpdateInfo(string filename, VersionType versionType, string version, string path)
{
FileName = filename;
VersionType = versionType;
Version = version;
Path = path;
}
public string FileName;
[XmlIgnore]
public string Path;
public VersionType VersionType;
public string Version;
}
public enum VersionType
{
FileVersion,
MD5
}
注意下其中UpdateInfo多了一个Path属性,表示文件的实际路径,不需要显示给客户端,因此加了XmlIgnore标记。
在服务端需要配置UpdateInfoSection项和UpdateServiceHandler的地址映射。
在<system.web>节<httpHandlers>下添加一项:
<httpHandlers>
<add verb="*" path="update.aspx" type="MyService.UpdateServiceHandler"/>
</httpHandlers>
其中MyService.UpdateServiceHandler需要替换为UpdateServiceHandler的完整名称。
然后添加UpdateInfoSection项:
<configSections>
<section name="ClientUpdateInfo" type="MyService.UpdateInfoSection"/>
</configSections>
<ClientUpdateInfo>
<DefaultFolder>bin</DefaultFolder>
<File FileName ="log4net.dll"/>
<File FileName ="Castle.Core.dll"/>
<File FileName ="Castle.DynamicProxy2.dll"/>
<File FileName ="Client.dll" Path ="bin/Client.exe" />
<File FileName ="client.config" VersionType="MD5"/>
</ClientUpdateInfo>
DefaultFolder是在没有为文件提供Path时默认的包含文件的目录,配置比较简单。
好了,现在可以打开update.aspx页面看到文件的版本信息
在update.aspx后加上参数的话,例如?download=log4net.dll,就可以下载文件了。
最后,客户端只需要更新并运行程序就可以了,当然还需要设置一下服务端的地址(Properties.Settings.Default.Server):
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
UpdateHelper.Update();
AppDomain domain = AppDomain.CreateDomain("client");
domain.SetData("APP_CONFIG_FILE", "client.config");
domain.ExecuteAssembly("Client.dll", AppDomain.CurrentDomain.Evidence, Environment.GetCommandLineArgs());
}
}
相关文章推荐
- my configurations with vim,vim plug-ins and tmux
- 使用java实现高中数学中自由组合
- IM类应用架构所需了解
- 算法笔记4
- 【转】Jmeter JDBC请求的问题
- 【转】Jmeter和LR上传文件和下载
- SQL servcer 时间日期函数、数据类型转换
- 【五】注入框架RoboGuice使用:(Your First POJO Injection)
- JQuery.Ajax()的data参数类型实例详解
- hive分区表增加字段新增字段值为空的bug
- ORACLE 10.2.5垮版本升级11.2.2 for windows 详细文档
- js 和css 加版本号问题
- 工厂模式
- 1009. 说反话 (20)
- weblogic 服务开机自动启动
- 【转】Jmeter的正则表达式未正确提取数据
- Android 触摸手势基础 官方文档概览
- GitHub上整理的一些工具 (转载)
- curl 异步执行操作
- dubbo使用logback输出日志