您的位置:首页 > 其它

实现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());

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: