您的当前位置:首页>全部文章>文章详情

【C#】C# Winform自动更新

CrazyPanda发表于:2024-02-01 23:23:51浏览:348次TAG:

目录

一、需求

二、更新文件列表生成器

三、软件启动器

1.判断是否需要更新

2.文件下载

3.执行 下载,覆盖,删除任务

4.执行结果

四、搭建更新服务器

1.启动服务器

2.新建项目本体

3.给启动软件加密

4.修改版本号

五、整体测试

1.生成更新文件

2.软件更新

3.下载最新的版本

4.打开软件本体

5.总结

结束



当前项目已停止维护,推荐使用 FTP 版自动更新

C# 自动更新(基于FTP)_c# 程序自动升级-CSDN博客


一、需求

在Unity里面,有XLua,ILRuntime 这样的热更新框架,有Unity经验的人都知道,在Unity里面发布的各个平台,哪怕是Windows平台,根本不必关闭程序才能进行更新,但是 Winform 项目必须关闭程序才进行下载替换,我也在网上找了一下,目前 Winform 还没看到什么比较好的开源框架,于是我自己动手写了一个

效果如下:

01174706_63da351a96fff24847.gif

在 Windows 平台,客户端自动更新方法一般如下面几个步骤,最少需要做三个软件

1.更新文件列表生成器

2.软件启动器

3.软件本体(只能由软件启动器 打开)

软件启动入口可以加密码,防止用户随意打开


二、更新文件列表生成器

我们知道,在原有基础上更新文件必要的三个基础:新增,覆盖,删除,那么问题来了,新增和删除好理解,在覆盖文件这块,假如:最新文件和旧版文件有一个同名的文件都叫 Test.dll,这时候要怎么判断是否进行下载和替换?

这个问题,可以用文件的哈希值去解决,如果客户端匹配不上,那么就需要从服务器下载和替换了,代码如下:

using System;
using System.IO;
using System.Security.Cryptography;
 
namespace Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string path1 = "D:\\单据备份\\53.png";
            string saveHash = "97-9F-FC-54-BF-C0-95-B8-15-64-C5-AC-81-03-DE-90-13-CD-03-D5";
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(path1, FileMode.Open))
                {
                    byte[] hashByte1 = hash.ComputeHash(file1);//哈希算法根据文本得到哈希码的字节数组
                    string str1 = BitConverter.ToString(hashByte1);//将字节数组装换为字符串
                    Console.WriteLine(str1);
 
                    if (saveHash.Equals(str1))
                        Console.WriteLine("两个文件相同");
                    else
                        Console.WriteLine("两个文件不相同");
                }
            }
 
            Console.ReadKey();
        }
    }
}

更新文件生成器界面如下,将最新的软件文件放到服务器一个固定的文件夹,然后用更新文件生成器就可以生成文件列表等数据了

1.png


新建一个类 DES加密类,这个是给json加密用的,不过源码中,我将这功能注释了

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
 
namespace Utils
{
 
    public class DESKey
    {
        /// <summary>
        /// Des默认密钥向量
        /// </summary>
        public static byte[] IV = { 0x13, 0x35, 0x16, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
        /// <summary>
        /// Des加解密钥必须8位
        /// </summary>
        public const string Key = "4hghhgg";
    }
 
    public class DES
    {
        /// <summary>
        /// Des加密
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="key">des密钥,长度必须8位</param>
        /// <param name="iv">密钥向量</param>
        /// <returns>加密后的字符串</returns>
        public static string EncryptDes(string source, string key, byte[] iv)
        {
            using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
            {
                byte[] rgbKeys = GetDesKey(key),
                    rgbIvs = iv,
                    inputByteArray = Encoding.UTF8.GetBytes(source);
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateEncryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
                        cryptoStream.FlushFinalBlock();
                        return Convert.ToBase64String(memoryStream.ToArray());
                    }
                }
            }
        }
 
        /// <summary>
        /// Des解密
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="key">des密钥,长度必须8位</param>
        /// <param name="iv">密钥向量</param>
        /// <returns>解密后的字符串</returns>
        public static string DecryptDes(string source, string key, byte[] iv)
        {
            using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
            {
                byte[] rgbKeys = GetDesKey(key), rgbIvs = iv, inputByteArray = Convert.FromBase64String(source);
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, desProvider.CreateDecryptor(rgbKeys, rgbIvs), CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(inputByteArray, 0, inputByteArray.Length);
                        cryptoStream.FlushFinalBlock();
                        return Encoding.UTF8.GetString(memoryStream.ToArray());
                    }
                }
            }
        }
 
        /// <summary>
        /// 获取Des8位密钥
        /// </summary>
        /// <param name="key">Des密钥字符串</param>
        /// <returns>Des8位密钥</returns>
        private static byte[] GetDesKey(string key)
        {
            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("key", "Des密钥不能为空");
            }
            if (key.Length > 8)
            {
                key = key.Substring(0, 8);
            }
            if (key.Length < 8)
            {
                // 不足8补全
                key = key.PadRight(8, '0');
            }
            return Encoding.UTF8.GetBytes(key);
        }
    }
}

Form1.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace UpdateFileCreater
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private class FileType
        {
            /// <summary>
            /// 路径
            /// </summary>
            public string Path { get; set; }
            /// <summary>
            /// 哈希值
            /// </summary>
            public string Hashs { get; set; }
        }
 
        private class UpdateFile
        {
            /// <summary>
            /// 文件夹列表
            /// </summary>
            public List<string> DirectoryList { get; set; } = new List<string>();
            /// <summary>
            /// 文件列表
            /// </summary>
            public List<FileType> FilesinfoList { get; set; } = new List<FileType>();
            /// <summary>
            /// 服务器版本号
            /// </summary>
            public string Version { get; set; }
        }
 
 
        //需要生成列表的目录
        private string Path = "D:\\H\\本地服务器工具\\Software\\News\\";
        //存放json的位置
        private string FileConfigJsonPath = "D:\\H\\本地服务器工具\\Software\\FileList.json";
 
        //本地文件的黑名单(不参与到更新)
        private static List<string> LocalFileBlacklist = new List<string>();
 
        //文件夹列表
        private static List<string> DirectorysList = new List<string>();
        //文件列表
        private static List<FileType> FilesinfoList = new List<FileType>();
 
 
        private void Form1_Load(object sender, EventArgs e)
        {
            this.TextBox_Path.Text = Path;
 
            //添加黑名单
            LocalFileBlacklist.Add("log4net.dll");
            LocalFileBlacklist.Add("log4net.xml");
            LocalFileBlacklist.Add("Newtonsoft.Json.dll");
            LocalFileBlacklist.Add("Newtonsoft.Json.xml");
 
 
            string localVersionPath = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
            Console.WriteLine("软件的版本号" + localVersionPath);
        }
 
        /// <summary>
        /// 选择路径
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_SelectionPath_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            dialog.Description = "请选择文件夹";
            dialog.SelectedPath = Path;
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                if (string.IsNullOrEmpty(dialog.SelectedPath))
                {
                    MessageBox.Show(this, "文件夹路径不能为空", "提示");
                    return;
                }
                Path = dialog.SelectedPath;
                this.TextBox_Path.Text = Path;
            }
        }
 
        /// <summary>
        /// 生成文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_CreaterFile_Click(object sender, EventArgs e)
        {
            DirectorysList.Clear();
            FilesinfoList.Clear();
 
            if (System.IO.Directory.Exists(Path))
            {
                Task.Run(() =>
                {
                    GetDirectoryFileList(Path);
 
                    if (FilesinfoList.Count > 0)
                    {
                        UpdateFile updateFile = new UpdateFile();
                        updateFile.DirectoryList = DirectorysList;
                        updateFile.FilesinfoList = FilesinfoList;
                        updateFile.Version = FileVersionInfo.GetVersionInfo(Path + "\\Test1.exe").FileVersion;
 
                        //生成json文件
                        string json = JsonConvert.SerializeObject(updateFile);
                        //加密json文件
                        //json = Utils.DES.EncryptDes(json, Utils.DESKey.Key, Utils.DESKey.IV);
                        WriteJsonFile(FileConfigJsonPath, json);
                        MessageBox.Show("生成文件成功!");
                    }
                });
            }
        }
 
 
        /// <summary>
        /// 获取一个文件夹下的所有文件和文件夹集合
        /// </summary>
        /// <param name="path"></param>
        private void GetDirectoryFileList(string path)
        {
            DirectoryInfo directory = new DirectoryInfo(path);
            FileSystemInfo[] filesArray = directory.GetFileSystemInfos();
            foreach (var item in filesArray)
            {
                if (item.Attributes == FileAttributes.Directory)
                {
                    //添加文件夹
                    string dir = item.FullName.Replace(Path, "");
                    DirectorysList.Add(dir);
                    GetDirectoryFileList(item.FullName);
                }
                else
                {
                    string fileName = item.FullName.Replace(Path, "");
                    fileName = fileName.Replace(@"\", @"/");
 
                    //是否在黑名单中
                    bool isBlackList = false;
                    if (LocalFileBlacklist.Count > 0)
                    {
                        for (int i = 0; i < LocalFileBlacklist.Count; i++)
                        {
                            if (LocalFileBlacklist[i].Equals(fileName))
                            {
                                isBlackList = true;
                                break;
                            }
                        }
                    }
                    if (!isBlackList)
                    {
                        //添加文件
                        FileType fileType = new FileType();
                        fileType.Path = fileName;
                        fileType.Hashs = GetHashs(item.FullName);
                        FilesinfoList.Add(fileType);
                    }
                }
            }
        }
 
        /// <summary>
        /// 获取文件的哈希值
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string GetHashs(string path)
        {
            //创建一个哈希算法对象
            using (HashAlgorithm hash = HashAlgorithm.Create())
            {
                using (FileStream file1 = new FileStream(path, FileMode.Open))
                {
                    //哈希算法根据文本得到哈希码的字节数组
                    byte[] hashByte1 = hash.ComputeHash(file1);
                    //将字节数组装换为字符串
                    return BitConverter.ToString(hashByte1);
                }
            }
        }
 
        /// <summary>
        /// 将json字符串内容写入Json文件并保存(若json文件不存在则创建)
        /// </summary>
        /// <param name="path">路径</param>
        /// <param name="jsonConents">Json内容</param>
        private void WriteJsonFile(string path, string jsonConents)
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.SetLength(0);
                using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
                {
                    sw.WriteLine(jsonConents);
                }
            }
        }
    }
}

这样 更新文件列表生成器 就基本完成了。


三、软件启动器

在我们平时用到的很多Windows软件打开时都会有软件启动器,大部分的作用都是基于软件的更新和文件的效验,这也形成了软件体系一个固定的框架,那么为什么要用软件启动器呢,那是因为,Windows软件打开以后,软件的文件就会被占用,无法进行删除,替换等操作,所以,想要更新软件,软件启动器是必须的。

软件启动器需要给项目添加管理员权限,软件启动器是启动软件之前,做的一些安全判断,比如文件是否完整,是否需要更新到最新版本,然后执行文件的新增,删除,覆盖 等任务。


1.判断是否需要更新

判断版本是否更新的方法:

Version v1 = new Version(txt1.Text);
Version v2 = new Version(txt2.Text);
if (v1 > v2)
{
    MessageBox.Show("版本1高于版本2");
}
if (v1 < v2)
{
    MessageBox.Show("版本1低于版本2");
}


2.文件下载

1.如果需要更新,将本地文件的哈希值和服务器返回的列表进行判断,如果服务器文件哈希值和本地文件哈希值不一致,那么就加入到下载列表

2.文件操作,更新软件本体通常的操作是:

① 新增文件直接下载

② 相同文件直接覆盖

③ 删除文件直接删除


3.执行 下载,覆盖,删除任务

需要操作的文件加入到任务列表(下载,覆盖,删除),需要专门写一个任务系统来执行此次操作,将每个子任务的结果显示到UI界面,这里可以用一个进度条和文本来显示执行的进度,

但也需要注意一点,自动更新系统都有一个毛病,就是不能确保文件的完整性,如果在下载的中途结束软件启动器进程,会导致软件的文件不全,从而打不开软件,解决这个问题,可以用一个文件夹先下载好文件,下完之后,直接覆盖现有的文件。


4.执行结果

如果执行完成,打开软件本体

如果执行失败,弹框显示错误信息,退出


1.png


软件启动器的界面就这样,没有过于复杂的部分

代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
 
namespace FileBootUp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            Label_DownLog.Visible = false;
            ProgressBar_DownProgress.Visible = false;
 
            Task.Run(() =>
            {
                //获取服务器文件列表
                GetConfig.StartGetConfig();
                //对比本地文件
                ComparisonVersion.Instance.StartComparison(StartDown);
            });
        }
 
        private void StartDown(List<DownInfo> addedList, List<DownInfo> coverList, List<string> deleteList)
        {
            if(addedList.Count == 0 && coverList.Count == 0 && deleteList.Count == 0)
            {
                MessageBox.Show("没有需要更新的文件");
                return;
            }
 
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                Label_DownLog.Visible = true;
                ProgressBar_DownProgress.Visible = true;
            });
 
            //删除
            if (deleteList.Count > 0)
            {
                for (int i = 0; i < deleteList.Count; i++)
                {
                    string paths = deleteList[i];
                    File.Delete(paths);
                    Console.WriteLine(string.Format("删除文件完成,路径:{0}", paths));
                }
            }
            //新增
            if (addedList.Count > 0)
            {
                for (int i = 0; i < addedList.Count; i++)
                {
                    DownInfo downInfo = addedList[i];
                    HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
                }
            }
            //覆盖
            if(coverList.Count > 0)
            {
                for (int i = 0; i < coverList.Count; i++)
                {
                    DownInfo downInfo = coverList[i];
                    HTTPDownManager.DownloadFileData(downInfo.DownURL, downInfo.SavePath, DownProgress, DownEnd);
                }
            }
 
            AllTaskEnd();
        }
 
        private void DownProgress(string fileName, string size, int percent)
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Format("正在下载:{0}  大小:{1}", fileName, size);
                this.Label_DownProgress.Text = string.Format("{0}%", percent);
                this.ProgressBar_DownProgress.Value = percent;
            });
        }
 
        private void DownEnd(string fileName)
        {
            Console.WriteLine(string.Format("{0} 下载完成", fileName));
        }
 
 
        private void AllTaskEnd()
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Empty;
                this.Label_DownProgress.Text = string.Empty;
                this.ProgressBar_DownProgress.Value = 0;
 
                this.Label_DownLog.Visible = false;
                this.ProgressBar_DownProgress.Visible = false;
 
                MessageBox.Show("所有任务完成");
            });
        }
 
 
    }
}


上面代码中用到的一部分工具类,我这里就不展示了,代码太多了。

1.png


其中的配置文件 App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
	<appSettings>
		<!--服务器版本URL-->
		<add key="ServerFileList" value="http://localhost/Software/FileList.json"/>
		<!--服务器文件库-->
		<add key="ServerFileVersion" value="http://localhost/Software/News"/>
	</appSettings>
</configuration>



四、搭建更新服务器

服务器可以使用软件来实现,不用的时候就关掉,在测试的时候,我们不必使用 IIS 这样的服务器,配置起来太麻烦了,软件名如下图

1.png

1.启动服务器

首先是上面图片中的 index.html 文件,里面没有html 代码,就是一句话

1.png


在双击 NetBox2.exe 后,就会自动打开一个网站

1.png

并且有任务栏中,可以看到图标

1.png

这样服务器就运行起来了


2.新建项目本体

项目本体,就用来作为更新的软件,那就新建一个 winform 项目吧,取名 Test1,界面不重要,也用不上,随便弄一下

1.png


目前作为测试,最主要的是,让软件的生存目录的文件多一些,比如选择这个插件

Swashbuckle.AspNetCore.Swagger.6.4.0

1.png

安装完成后,我们来修改一下,Program.cs 的代码


3.给启动软件加密

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Test1
{
    internal static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            if (args.Length == 0)
                return;
            if (args[0] != "密码")
                return;
 
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

这里的 string[] arrgs 原本是没有的,我这里加上后,另外加上密码,直接双击exe 文件是打不开的,这里代码的主要作用也是如此。

代码添加完成后,点击生成

1.png

由于刚刚添加的插件,生成的文件特别多,先别管他了 


4.修改版本号

另外,就是版本号的修改了,打开 AssemblyInfo.cs

1.png

一般修改版本号改  AssemblyFileVersion

1.png

将版本改为 1.0.0.1 吧

1.png

现在准备工作也做的差不多了,下面开始整体测试。 


五、整体测试

1.生成更新文件

将Test1项目生成的文件复制到服务器固定目录

1.png


打开更新文件列表生成器

1.png


点击生成

1.png


 这时候,就看到了生成的文件

1.png


由于是加密的,直接看是不知道里面有什么的

1.png

在源码中,我将加密部分注释了,那么效果就是这样的

{
	"DirectoryList": [],
	"FilesinfoList": [{
		"Path": "Microsoft.AspNetCore.Http.Abstractions.dll",
		"Hashs": "4F-30-77-3D-BA-F6-84-0E-F7-57-E5-EF-64-7A-96-B2-54-F9-BA-3B"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Abstractions.xml",
		"Hashs": "25-2C-B5-23-37-0F-B2-2F-BF-F9-DA-B1-7E-EE-CA-63-42-7A-06-65"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Extensions.dll",
		"Hashs": "74-56-B5-F3-E1-37-45-D6-A2-99-2F-F9-17-E1-20-E1-BA-7E-37-CE"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Extensions.xml",
		"Hashs": "A8-B0-E3-46-56-25-8A-19-FE-33-91-27-67-3C-EC-62-F5-E0-8F-AE"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Features.dll",
		"Hashs": "1E-CD-4E-A7-C0-11-CE-91-A9-E2-54-25-ED-E4-B0-C1-A6-4D-88-FD"
	}, {
		"Path": "Microsoft.AspNetCore.Http.Features.xml",
		"Hashs": "5F-5D-0D-BB-A3-42-5B-E8-CA-F2-99-5C-26-BC-2F-F4-35-AC-EE-0F"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.Abstractions.dll",
		"Hashs": "60-64-E5-E5-8B-00-A0-EF-6F-0A-0B-82-83-9C-FA-84-91-CD-10-BC"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.Abstractions.xml",
		"Hashs": "40-98-34-26-61-84-70-B0-4E-F4-67-7A-FF-5E-B0-85-25-20-DB-29"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.dll",
		"Hashs": "4A-32-C9-0C-F5-3D-3C-29-E3-B9-81-8C-3C-25-02-E3-31-F7-70-72"
	}, {
		"Path": "Microsoft.AspNetCore.Routing.xml",
		"Hashs": "05-B5-91-BF-D1-2B-97-99-31-9B-4F-7A-88-40-68-A5-75-57-E2-1E"
	}, {
		"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.dll",
		"Hashs": "5E-46-B9-38-63-9A-28-65-1B-D4-DE-8E-DA-43-8C-CC-5A-21-2E-1C"
	}, {
		"Path": "Microsoft.Extensions.DependencyInjection.Abstractions.xml",
		"Hashs": "05-3C-29-79-BE-6B-34-77-69-4D-25-FD-CE-0E-89-C6-FD-71-6D-E1"
	}, {
		"Path": "Microsoft.Extensions.FileProviders.Abstractions.dll",
		"Hashs": "F1-D2-1C-08-B2-AC-28-18-17-37-6C-DB-2D-F2-E5-FA-A2-1E-C8-45"
	}, {
		"Path": "Microsoft.Extensions.FileProviders.Abstractions.xml",
		"Hashs": "D2-BD-BA-26-10-70-52-BE-8D-BE-02-88-63-49-7E-0D-63-BB-D8-50"
	}, {
		"Path": "Microsoft.Extensions.Logging.Abstractions.dll",
		"Hashs": "65-5B-52-5B-F7-33-AF-9F-1F-F7-A7-89-8E-98-11-63-47-E6-7C-F8"
	}, {
		"Path": "Microsoft.Extensions.Logging.Abstractions.xml",
		"Hashs": "E1-82-DC-EA-78-DF-37-4B-5E-16-1D-93-D2-D9-6D-09-B0-78-4F-88"
	}, {
		"Path": "Microsoft.Extensions.ObjectPool.dll",
		"Hashs": "4E-FD-D1-C7-9B-20-F9-D4-A8-18-2E-30-2D-1E-2C-86-98-BB-23-06"
	}, {
		"Path": "Microsoft.Extensions.ObjectPool.xml",
		"Hashs": "4C-0C-2F-42-F1-B0-70-E7-2F-B5-6F-AD-9E-93-40-D8-F3-E7-65-DE"
	}, {
		"Path": "Microsoft.Extensions.Options.dll",
		"Hashs": "C2-F4-79-71-79-93-EA-E1-61-42-A2-07-F1-75-48-21-69-D8-BC-CB"
	}, {
		"Path": "Microsoft.Extensions.Options.xml",
		"Hashs": "CB-37-C1-6A-43-5C-57-BE-7D-4C-C7-1D-29-AD-17-39-11-3C-A6-25"
	}, {
		"Path": "Microsoft.Extensions.Primitives.dll",
		"Hashs": "91-4D-D1-1A-94-E9-FB-1C-A7-9D-BF-F6-BE-C6-61-D4-E2-9C-D0-B0"
	}, {
		"Path": "Microsoft.Extensions.Primitives.xml",
		"Hashs": "DE-7E-1A-8D-C9-27-3F-C0-B6-3A-3D-4D-1B-88-EB-A2-F5-E1-84-9B"
	}, {
		"Path": "Microsoft.Net.Http.Headers.dll",
		"Hashs": "98-D4-88-0C-05-CD-B1-32-BB-D7-F7-3B-EF-A0-CF-A7-B2-2A-57-78"
	}, {
		"Path": "Microsoft.Net.Http.Headers.xml",
		"Hashs": "63-68-C4-F0-5D-24-8D-94-9D-1A-F5-29-C9-BF-05-DB-E0-81-A0-38"
	}, {
		"Path": "Microsoft.OpenApi.dll",
		"Hashs": "45-FB-EB-24-39-BE-3F-E9-15-A4-62-3C-01-30-DC-B1-08-AC-E8-08"
	}, {
		"Path": "Microsoft.OpenApi.pdb",
		"Hashs": "FA-6C-06-BA-24-9B-80-50-BF-A0-FB-17-57-BE-82-EE-D1-CE-B0-DE"
	}, {
		"Path": "Microsoft.OpenApi.xml",
		"Hashs": "1E-1D-79-43-25-67-4C-52-5C-A3-02-E9-30-2A-17-A4-A9-4E-38-B2"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.dll",
		"Hashs": "A8-52-E6-35-A3-A4-9B-B5-A8-B3-F5-44-C6-C3-89-75-87-BB-7D-A8"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.pdb",
		"Hashs": "48-3B-7C-A7-EA-B8-E2-61-67-F9-FE-00-A3-9F-AD-A2-20-D4-73-D7"
	}, {
		"Path": "Swashbuckle.AspNetCore.Swagger.xml",
		"Hashs": "AA-2F-DD-84-B8-9E-2C-68-04-92-4A-8E-0B-12-D3-81-9E-A9-B2-78"
	}, {
		"Path": "System.Buffers.dll",
		"Hashs": "BA-C3-4C-8A-68-C1-20-51-C6-F5-39-5C-5A-75-9D-7A-B5-19-A8-BA"
	}, {
		"Path": "System.Buffers.xml",
		"Hashs": "7F-40-69-34-1C-6B-62-EC-FC-99-9A-6C-2D-8A-2D-5F-B5-9D-44-F6"
	}, {
		"Path": "System.Memory.dll",
		"Hashs": "A8-8D-AB-84-A7-D3-13-BF-49-EE-01-C7-00-04-37-E5-7D-BB-A6-97"
	}, {
		"Path": "System.Memory.xml",
		"Hashs": "CF-44-E6-55-7F-DE-93-28-8F-F2-56-7A-00-2A-69-27-99-65-CA-BA"
	}, {
		"Path": "System.Numerics.Vectors.dll",
		"Hashs": "35-9F-9F-8A-3E-0E-E6-3F-9E-B6-BC-56-E3-BA-C3-00-C7-31-C0-80"
	}, {
		"Path": "System.Numerics.Vectors.xml",
		"Hashs": "E2-A3-B3-AC-B3-80-A4-EB-62-6B-44-FF-6E-E0-4A-37-11-0A-33-89"
	}, {
		"Path": "System.Runtime.CompilerServices.Unsafe.dll",
		"Hashs": "FD-55-A3-BC-6B-09-28-A0-29-B2-9D-D0-55-9F-ED-4C-E3-0B-79-D4"
	}, {
		"Path": "System.Runtime.CompilerServices.Unsafe.xml",
		"Hashs": "E7-05-41-4B-E7-2B-48-66-BC-3A-D0-2B-95-29-65-60-14-C6-3C-B1"
	}, {
		"Path": "System.Text.Encodings.Web.dll",
		"Hashs": "D5-9E-68-86-E0-1F-31-F5-6F-84-8A-5A-A7-28-19-0F-0C-27-AA-49"
	}, {
		"Path": "System.Text.Encodings.Web.xml",
		"Hashs": "84-8E-B4-3B-4C-C5-37-51-FC-DC-27-B3-6B-A6-4B-AF-4B-E2-28-80"
	}, {
		"Path": "Test1.exe",
		"Hashs": "92-04-71-3C-1B-B0-7B-05-EA-3F-46-71-4A-55-1D-E4-17-39-C1-23"
	}, {
		"Path": "Test1.exe.config",
		"Hashs": "61-F0-51-D8-F3-00-F7-BA-4C-AA-36-CE-89-11-D0-EC-0F-17-5C-C4"
	}, {
		"Path": "Test1.pdb",
		"Hashs": "E3-D6-DD-6B-AF-A4-FB-C8-4F-2C-65-96-D4-E4-B0-BA-8C-25-CD-3D"
	}],
	"Version": "1.0.0.0"
}



2.软件更新

打开 软件启动器

如果是第一次运行的话,会直接下载所有的文件

1.png

然后就可以看到这个Test1 所有项目文件

1.png

这时候,就完成了基本的更新功能了,当然,我们要做的功能不是下载文件就完事了,最主要的是更新的功能,现在我们更新服务器的版本看看。

在更新服务器文件之前,最好先把刚刚下载好的文件删除一些,不然软件启动器只会下载服务器端和本地端文件不一致的文件,删除后如下图

1.png



Test1.exe 相关的文件不要删除了,不然软件启动器会认为当前是第一次下载,将把所有的文件下载一遍。


3.下载最新的版本

打开 Test1的项目,将 Test1 的版本号改为 1.0.0.2

1.png

然后生成项目,将这三个文件复制到 D:\H\本地服务器工具\Software\News 目录下,就是刚刚搭建的服务器那个目录,这个根据个人的配置,目录不一定非得和我的一样。

1.png

然后用 更新文件列表生成器 重新生成 json 文件,可以打开json文件,看看版本是否和你设置的一致。

1.png

这时候,打开 软件启动器,就会发现弹出了是否更新的提示框

1.png

点击确定,由于文件比较小,所以下载的有点快

01174706_63da351a96fff24847.gif

 下载完成

1.png


在控制台同样有下载的日志

1.png

这样更新的工作就算完成了


4.打开软件本体

在这里需要更新一下 软件启动器的 Form1 的代码,如下

        private void AllTaskEnd()
        {
            FormControlExtensions.InvokeIfRequired(this, () =>
            {
                this.Label_DownLog.Text = string.Empty;
                this.Label_DownProgress.Text = string.Empty;
                this.ProgressBar_DownProgress.Value = 0;
 
                this.Label_DownLog.Visible = false;
                this.ProgressBar_DownProgress.Visible = false;
 
                //MessageBox.Show("所有任务完成");
                Console.WriteLine("所有任务完成");
 
                string path = Application.StartupPath + "\\Test1.exe";
                string pwd = "密码";
                string[] parameter = { pwd };
                bool startResult = StartProcess(path, parameter);
                if (startResult)
                    System.Environment.Exit(0);//退出软件启动器
            });
        }
 
        /// <summary>
        /// 启动一个软件,并传入参数
        /// </summary>
        /// <param name="runFilePath"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool StartProcess(string runFilePath, params string[] args)
        {
            string s = "";
            foreach (string arg in args)
            {
                s = s + arg + " ";
            }
            s = s.Trim();
            Process process = new Process();//创建进程对象    
            ProcessStartInfo startInfo = new ProcessStartInfo(runFilePath, s); // 括号里是(程序名,参数)
            process.StartInfo = startInfo;
            process.Start();
            return true;
        }

 再次重复和上节重复的操作,就会实现下面的效果

01174706_63da351a96fff24847.gif


更新完成后,会自动打开软件本体,这样,我们所有的效果都实现了。


5.总结

虽然说效果基本是实现了,但是项目依然有不足的地方,需要好好打磨,比如,假如更新过程中突然断网了,就会出现更新了一部分,还有一部分没有更新完成,这样可能会导致软件本体打不开,或者打开后异常,我其实也想好好完善这些功能,但是,自己的工作中,各种烦人的事实在太多了,每次下班基本什么都不想弄了,这个帖子也一直拖了很久才更新完,如果你发现代码有那些bug ,或者需要完善的地方,欢迎评论告诉我,谢谢。

项目源码:点击下载

源码有错误,或者疑问的,可以随时私信给我,我看到了都会回复的,谢谢


结束

如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

end


原文链接C# Winform自动更新_winform自动更新程序-CSDN博客

猜你喜欢

【C#】C# Winform自动更新
目录一、需求二、更新文件列表生成器三、软件启动器1.判断是否需要更新2.文件下载3.执行 下载,覆盖,删除任务4.执行结果四、搭建更新服务器1.启动服务器2.新建项目本体3.给启动软件加密4.修改版本号五、整体测试1.生成更新文件2.软件更新3.下载最新的版本4.打开软件本体5.总结结束当前项目已停止维护,推荐使用 FTP 版自动更新C# 自动更新(基于FTP)_c# 程序自动升级-CSDN博客一、需求在Unity里面,有XLua,ILRuntime 这样的热更新框架,有Unity经验的人都知道
发表于:2024-02-01 浏览:349 TAG:
【C#】C# Winform GDI+ 绘图
目录一、概述二、绘图1.画直线2.画矩形3.画圆、圆弧4.画扇形5.画多边形6.绘制字符串7.填充图形结束一、概述Graphics类是GDI+技术的一个基本类。GDI+(Graphics Device Interface)是.NET框架的重要组成部分,提供对二维图形图像和文字排版处理的支持。GDI+相关的类分布在下列命名空间中: System.Drawing:提供了最基本的绘图功能(比如画直线、矩形、椭圆等); System.Drawing.Drawing2D: 提供了高级的二维和矢量绘图功能(
发表于:2024-02-18 浏览:387 TAG:
【C#】C# Winform DataGridView 数据刷新问题
目录一、问题二、创建项目三、绑定空的数据源四、绑定有数据的数据源五、修改绑定的数据源六、解决数据源刷新问题七、解决刷新数据界面闪烁一、问题DataGridView 是比较常用的表格控件,在 DataGridView 中显示数据,&nbsp;一般使用 dataGridView1.DataSource = 数据源,来绑定数据,数据源可以是 DataTable、List、Dictionary 等,那么如何做到及时刷新数据呢,这里我提出几个问题:1.绑定一个空的数据源,后面向数据源添加数据。2.Data
发表于:2024-02-05 浏览:353 TAG:
【C#】C# Winfrom Chart 图表控件 柱状图、折线图
目录一、参考二、柱状图1.新建项目2.修改图表样式3.绑定数据4.删除Series1图例1)使用属性窗体删除2)使用代码删除5.自定义X轴的刻度值1)使用List绑定2)使用LabelStyle.Format绑定6.自定义Y轴的刻度值7.X轴刻度值显示不全的解决方法8.修改X Y刻度值的字体样式9.X轴刻度值旋转90°10.禁用Y轴刻度线11.去掉Y轴刻度值12.改变柱子的宽度13.设置网格线的颜色14.设置网格线的线型三、折线图1.图表设置2.绑定数据结束效果:一、参考c# Chart设置样式
发表于:2024-02-02 浏览:422 TAG:
【C#】Winform NanUI 相关功能整合
目录NanUI 0.88版本 去掉启动界面(遮罩)NanUI 0.88版本 读取本地资源和嵌入式资源NanUI 0.77版本 打开控制台NanUI 0.77版本 C#调用多个参数的JS方法NanUI 0.77版本 传递数组参数NanUI 0.77版本 设置窗体全屏显示(类似Kiosk模式)NanUI 0.77版本 Bootstrap类 APINanUI 0.88版本 去掉启动界面(遮罩)启动界面是作者给我们显示公司产品的Logo用的,如果用不着可以去掉,但必须要更改源码,如果不想改源码,直接将启
发表于:2024-02-09 浏览:380 TAG:
【C#】CSDK/IDE-VSCode 搭建 C# 开发环境
&nbsp;&nbsp;&nbsp;&nbsp;最近准备写&nbsp;C#&nbsp;的笔记总结专栏&nbsp;bug&nbsp;笔记本硬盘空间实在是不够用了 &nbsp;&nbsp;&nbsp;&nbsp;根本没有办法再安装一个&nbsp;Visual&nbsp;Studio&nbsp;集成开发环境了!!! &nbsp;&nbsp;&nbsp;&nbsp;在学&nbsp;Java&nbsp;的过程中基本都是用记事本和命令提示符……再也不想经历了 &nbsp; &nbsp;&nbsp;&amp;nbs
发表于:2024-01-28 浏览:437 TAG:
【C#】C# Winform 文本面板带滚动条
在PC软件开发中经常有这样的需求,需要在一个固定大小的面板中显示一些内容,并且面板能上下拖动,将所有的内容完整的展示,有点类似网页上看新闻,如果要在 winfrom 中要如何实现的呢,下面就演示如何实现的吧效果:1.新建一个winform 项目,在界面中拖入一个Panel&nbsp;将 panel1 的&nbsp;AutoScroll 设置为 True2.再次拖入一个&nbsp;Panel ,将高度拉长,这时就自动出现了滚动条,只是此时里面还没有任何内容,下面就在 panel2 中加入一点内容。
发表于:2024-02-03 浏览:329 TAG:
【C#】C# Winform 自定义进度条ProgressBar
效果:一、前言Winfrom各种老毛病真的不适合做大型项目,甚至中型项目都不适合,一些小功能都能把你折腾半死,比如,我想在界面上显示一个进度条,用来显示现在硬盘和内存已经使用了多少,使用了 ProgressBar 控件你看看效果:进度条中间一直有个白色光影在晃来晃去的,是不是想让别人感慨:“哇!好强的光芒,我的眼睛快睁不开了...”。而且背景颜色无法改变,这个动画也无法关掉,为了解决这两个问题,我找了很久,终于找到了下面的解决方法。二、自定义进度条于是我在网上找了一些资料,有到效果有,但不是特别
发表于:2024-02-01 浏览:335 TAG:
【C#】C# Winform 配置文件App.config
目录一、简介二、添加引用&nbsp;三、添加节点1.普通配置节点2.数据源配置节点四、管理类 ConfigHelper.cs1.获取配置节点2.更新或加入配置节点结束一、简介在C#中,配置文件很常用,ASP.NET 和 Winform 名称不同,用法一样,如下图config 文件通常用来存储一些需要修改的数据,比如用户名密码,连接数据库的IP地址等,而不是在代码中写死。有人可能会问,那我自己自定义一个配置文件也行,为什么要用它这个?区别当然有,微软自己封装的读取和写入会更简单一些,你自己封装的,
发表于:2024-01-31 浏览:385 TAG:
【C#】C# Winform SplitContainer组件创建侧边菜单
效果一,SplitContainer 基本操作新建一个 Winform 项目,在Form1中拖进一个 SplitContainer 组件默认的界面如下这时候,你会发现,左侧菜单栏的宽度也太宽了吧,按照以前的经验,你一定会用鼠标去拖拽,这时候你就会发现,鼠标根本拖不动,不信你可以试试这时候,我们按Esc键,鼠标再移动到边框的时候,鼠标图标就会变成一个 “+” 状的图标,这时候就可以拖拽了此时,左侧的Panel1内还没有任何组件,运行后的效果我们添加一个按钮到&nbsp;Panel1 试试运行后发现
发表于:2024-02-01 浏览:547 TAG: