说到C#.NET的更动更新 大家都想到了ClickOnce,但很多时候它的功能并没有我们需要的足够的强大。其实它的原理很简单,为什么我们不自己开发一套呢?下面以我的开发实例与大家交流一下。
原理:
1.服务器有一虚拟目录Update,里面放置客户端的所有程序(由于IIS限制,不能升级.config文件,如需要则改IIS相应配置)
2.Update目录里再放置一Default.aspx文件,用来取出当前文件夹下的文件列表和文件修改时间,并形成一个XML返回出来。
3.客户端升级程序使用HttpWebRequest对象访问这个http://server/Update/Default.asp,并获得返回的XML。将XML解析后与本地文件时间比较,如果不同则下载。
代码:
(Update/Default.aspx文件)
<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%
SortedList FileList = new SortedList();
string UpdatePath = AppDomain.CurrentDomain.BaseDirectory + "//update//";
DirectoryInfo di = new DirectoryInfo(UpdatePath);
foreach (FileInfo fi in di.GetFiles())
{
// 判断如果是Default.aspx则不下载到客户端
if (fi.Name == "Default.aspx") continue;
FileList.Add(fi.Name, fi.LastWriteTime.ToString());
}
string retValue = "<xml>";
for (int i = 0; i < FileList.Count; i++)
{
retValue += "<row File='" + FileList.GetKey(i).ToString() + "' Date='" + FileList.GetValue(i).ToString() + "' />";
}
retValue += "</xml>";
Response.Write(retValue);
%>
好,上面的代码很简单,就是获得服务器更新目录里的所有文件列表与文件的修改时间返回出来。
客户端检查更新的代码:
引用:
using System.Reflection;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Xml;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Threading;
全局变量:
private SortedList DirectoryList = new SortedList();
private int m_FileNum = 1;
// 函数fnDoUpdate
private void fnDoUpdate()
{
try
{
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create("http://server/update/Default.aspx");
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
Stream ReceiveStream = myResponse.GetResponseStream();
Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
StreamReader sr = new StreamReader(ReceiveStream, encode);
Char[] read = new Char[256];
string strResult = "";
int count = sr.Read(read, 0, 256);
while (count > 0)
{
String str = new String(read, 0, count);
strResult += str;
count = sr.Read(read, 0, 256);
}
XmlDocument doc = new XmlDocument();
doc.LoadXml(strResult);
// 这个Resource是自己定义的一个类 基本具有下面几个属性:Name,Url,LastModified 我就不定义了
Resource tempResource;
foreach (XmlNode xn in doc.SelectNodes("//row"))
{
tempResource = new Resource();
tempResource.Name = xn.Attributes["File"].Value;
tempResource.Url = ClientConfiguration.AppServerUrl + "update/" + tempResource.Name;
tempResource.LastModified = Convert.ToDateTime(xn.Attributes["Date"].Value);
DirectoryList.Add(tempResource.Url, tempResource);
}
// 好了 DirectoryList就是服务器的文件列表了 下面开始与本地文件进行比较了
for (int i = 1; i <= DirectoryList.Count; i++)
{
Resource currentResource = (Resource)DirectoryList.GetByIndex(i - 1);
string newFilePath = Application.StartupPath + "//" + currentResource.Name;
// 删除掉时间相同的 因为不需要下载 这里用到了CheckDateTime函数是我们自己定义的 后面符带了
if (File.Exists(newFilePath) && CheckDateTime(currentResource.LastModified, LastModFromDisk(newFilePath)))
DownloadList.Remove(currentResource.Url);
}
// 好了 到这里就得到了 我们需要更新的文件列表 DownloadList 开始下载了
// 下载过程的处理比较麻烦 使用多线程要出问题 我研究了很久想出这个办法
m_FileNum = 1; // 这个用来定位当前下载第几个文件
progressBar1.EditValue = 0; //进度条规0
progressBar1.Properties.Maximum = DownloadList.Count;
StartDownload();
}
catch (Exception ex)
{
// 异常处理
}
}
// 函数StartDownload 用于下载单个文件
private void StartDownload()
{
try
{
Resource currentResource = (Resource)DownloadList.GetByIndex(m_FileNum - 1);
string newFilePath = Application.StartupPath +"//"+ currentResource.Name;
if (File.Exists(newFilePath)) File.Delete(newFilePath);
progressBar1.EditValue = m_FileNum ;
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
Uri dUri = new Uri(currentResource.Url);
client.DownloadFileAsync(dUri, newFilePath);
}
catch (Exception ex)
{
// 异常处理
}
}
// 下面二个事件 是WebClient对象的下载事件
private void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs args)
{
// 要做得漂亮点 这里放些计算速度的 或时时显示下载进度的处理
// 常用的有:args.ProgressPercentage 下载进度百分比 args.BytesReceived 已下载字节数
// ts.TotalMilliseconds 总字节数
}
private void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs args)
{
Resource currentResource = (Resource)DownloadList.GetByIndex(m_FileNum - 1);
string newFilePath = Application.StartupPath +"//"+ currentResource.Name;
// 将下载回来的文件修改时间更正 WebClient不会做这事只有我们来做了
FileInfo f = new FileInfo(newFilePath);
f.LastWriteTime = currentResource.LastModified;
m_FileNum++;
if (m_FileNum > DownloadList.Count)
{
// 文件更新完成 这里写完成后的处理
}
else
{
// 没下完则继续下载下一个
StartDownload();
}
}
// 函数 LastModFromDisk 获取本地文件修改时间
private DateTime LastModFromDisk(string filePath)
{
FileInfo f = new FileInfo(filePath);
return (f.LastWriteTime);
}
// 好了 到这里需要做的都写完了 下面是调用了 一定要用多线程来调用
Thread t = new Thread(new ThreadStart(fnDoUpdate));
t.Start();
好了,简单的自动更新功能就这么简单的完成了,你可以根据需要修改其中任意环节为自己想要的。
上面的代码是我在复杂的代码中把复杂的部分去掉了,也许你直接COPY过去用会编译不过,但我想
应该容易修改正确,希望大家多多指正。
大家不要置疑这思路的正确性,因为我开发的这段代码正在我公司的产品中使用,它的最大好处就是
我们可以随心所欲的修改下载的判断,或增加不同的需求。