Windows程序通用自动更新模块(C#,.NET4.5以上)

本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf。

一、工作流程:
1. 主程序A调起升级程序B
2. B从服务器获取更新程序列表,打印更新信息。
3. B杀死A进程(此步骤可以放在步骤2~5任意位置)
4. B根据更新信息中指示的地址,下载更新程序包(.zip文件)
5. 解压缩.zip文件到一个新创建的文件夹
6. 将解压后的文件拷贝到原始文件目录,做替换。
7. 删除下载的.zip文件以及解压后创建的文件夹
8. B打开A

二、源码介绍:

升级程序B的实现:

更新信息列表用于存储版本信息,以及更新说明信息。通常为json或xml文件。本文为json文件。

存储列表信息的类

public class UpdateItem
{
    public string Version { get; set; }  //版本号
    public string UpdateContent { get; set; }  //更新信息
    public string DownloadUri { get; set; }  //更新包的下载地址
    public string Time { get; set; }  //更新时间
    public string Size { get; set; }  //更新包大小
}

 获取更新信息使用WebClient.DownloadData(Uri),其中使用Newtonsoft.Json进行json序列化及反序列化。

WebClient client = new WebClient();
byte[] data = client.DownloadData(uri);
//json转为UpdateItem类对象
UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data));

 获取更新信息以后在界面上进行输出。
下面介绍一下生成更新信息的json文件并使用FTP上传到服务器的代码。json文件也可以手动写,手动上传。不是重点,不想看可以跳到下一部分。

UpdateItem UpdateInfo = new UpdateItem();
...  //赋值
string json = JsonConvert.SerializeObject(UpdateInfo);
 //连接服务器
FtpWebRequest request = (FtpWebRequest)WebRequest.Create("ftp://...");
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential("账号", "密码");
// 复制字符  
byte[] fileContents = Encoding.UTF8.GetBytes(json);
request.ContentLength = fileContents.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(fileContents, 0, fileContents.Length);
requestStream.Close();
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
response.Close();

下载更新包zip文件

下载使用WebClient.DownloadFile(Uri, string)

string dir = System.IO.Directory.GetCurrentDirectory();  //程序所在文件夹路径
string zipfile = System.IO.Path.Combine(dir, "file.zip");  //下载后zip文件的完整路径

WebClient client = new WebClient();
client.DownloadFile(UpdateInfo.DownloadUri, zipfile);
return true;

解压缩zip文件

解压缩需要用到System.IO.Compression.ZipFile,需要.NET4.5及以上。如果用不了(找不到ZipFile),请检查是否已添加引用System.IO.Compression.FileSystem程序集。

代码只有一行,两个参数分别为zip文件完整路径,解压后的文件夹完整路径。

ZipFile.ExtractToDirectory(zipfile, extractPath);

使用第三方压缩软件生成的.zip文件有可能会解压失败,建议使用下方代码生成。两个参数分别为需要压缩的文件夹完整路径,生成的zip文件完整路径。

ZipFile.CreateFromDirectory(startPath, zipfile);

拷贝解压后的文件到原始目录

foreach (string item in Directory.GetFiles(extractPath))
{
    File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true);
}

删除临时文件

File.Delete(zipfile);
DirectoryInfo di = new DirectoryInfo(extractPath);
di.Delete(true);

B进程杀死A进程

string appname = "file"; //A名字,不要路径,不要.exe
Process[] processes = Process.GetProcessesByName(appname);
foreach (var p in processes)
p.Kill();

B进程调起A进程

System.Diagnostics.Process.Start(@"path");  //完整路径

 

主要流程就是这些,建议用异步操作实现。
现在上号比较少,私信很多要代码的都没回…以后都会把完整代码附上的。
下面是完整代码。

private Uri uri = new Uri(@"http://...");  //更新信息列表文件路径
private UpdateItem UpdateInfo;  //UpdateItem类的定义在前面第一部分说明

public MainWindow()
{
    InitializeComponent();
    LoadingData();
}

//用户按下更新按钮
private async void Button_Click(object sender, RoutedEventArgs e)
{
    //下载
    Tb_State.Text = "正在下载新版本文件,请耐心等待";
    string dir = System.IO.Directory.GetCurrentDirectory();
    string zipfile = System.IO.Path.Combine(dir, "File.zip");
    bool success = await Task.Run(() =>
    {
        try
        {
            WebClient client = new WebClient();
            client.DownloadFile(UpdateInfo.DownloadUri, zipfile);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    });

    if (success)
        Tb_State.Text = "文件已下载,正在复制文件";
    else
    {
        Tb_State.Text = "下载新版本文件失败,请重试";
        return;
    }

    //杀死主程序进程
    string appname = "File";
    Process[] processes = Process.GetProcessesByName(appname);
    foreach (var p in processes)
        p.Kill();


    //解压缩+拷贝+删除
    bool success2 = await Task.Run(() =>
    {
        try
        {
            string extractPath = System.IO.Path.Combine(dir, "NewVersion");
            ZipFile.ExtractToDirectory(zipfile, extractPath);
            foreach (string item in Directory.GetFiles(extractPath))
                File.Copy(item, System.IO.Path.Combine(dir, System.IO.Path.GetFileName(item)), true);
            File.Delete(zipfile);
            DirectoryInfo di = new DirectoryInfo(extractPath);
            di.Delete(true);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    });

    if (success2)
        Tb_State.Text = "更新完成,您可以点击下方按钮启动应用";
    else
        Tb_State.Text = "复制更新文件出错,请重试";
}

//读取更新列表文件
private async Task<bool> LoadingData()
{
    Tb_State.Text = "正在下载更新文件信息";
    bool success = await Task.Run(() =>
    {
        try
        {
            WebClient client = new WebClient();
            byte[] data = client.DownloadData(uri);
            UpdateInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateItem>(Encoding.UTF8.GetString(data));
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    });

    if (success)
        Tb_State.Text = "已获取新版本信息,可进行更新";
    else
        Tb_State.Text = "无法获取更新信息";
    return success;
}

//更新完成,更新程序B调起主程序A
private void Btn_Open_Click(object sender, RoutedEventArgs e)
{
    System.Diagnostics.Process.Start(@"D:\...");   //A程序完整路径
}

再贴一个用于下载过程中,能实时显示已下载文件大小的代码
此部分需写在下载部分之前。

System.Windows.Threading.DispatcherTimer dt = new System.Windows.Threading.DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(100);  //100毫秒
dt.Tick += (x, y) => {
    if (File.Exists(zipfile) == false)
        return;
    string size = ((new FileInfo(zipfile).Length) / 1024.0 / 1024).ToString("f2");
    if (download == false)  //是否下载完毕
        Tb_State.Text = size + "MB / " + UpdateInfo.Size;  //输出:已下载/总大小
    else
        dt.Stop();
};
dt.Start();

代码部分执行时,再次执行代码可能会报错。可能原因包括下载zip文件后未删除再次下载报错等。如要做项目还有很多地方需要进行判断,如检测路径的合法性等。为了代码便于阅读,没有加入这些错误检测部分,需自行补充。几乎所有操作前都应判断路径合法性,以及目标位置的文件是否存在(是否需要先删除)等。只能说是在输入合法的情况下,完整执行此代码是没有问题的。

代码通过Visual Studio 2019测试,.NET4.5。

 

posted @ 2020-04-14 18:22  小y  阅读(415)  评论(0编辑  收藏