深度优先

这个家伙好懒,除了文章什么都没留下

0%

【WinForm】自动升级更新

https://www.cnblogs.com/zhaopei/p/12840786.html

打包成单文件exe

通常我们开发出来的WinForm程序,除了一个exe文件还会有很多dll文件。
那么有没有办法只生成一个exe文件,让程序更加方便传播和使用,答案是肯定的。
NuGet搜索 Costura.Fody并下载,然后重新生成解决方案即可,你在去bin目录查看,原来的一堆dll不见了。

.net core官方支持打包成单文件

如果你使用的.net core 3.0,那么你可以直接使用官方支持的发布单文件功能。
直接使用命令 dotnet publish -r win10-x64 /p:PublishSingleFile=true
或者修改一下项目文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>//发布平台
<PublishSingleFile>true</PublishSingleFile>//是否单个exe
</PropertyGroup>
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>//启用压缩
</PropertyGroup>
</Project>

自动升级更新

一个有生命的桌面程序理应做到可以自动升级。很多人在做自动升级更新时会执行一个单独的升级exe,也就是说一个完整的程序起码包括两个exe。个人觉得不够优雅,如果能用一个exe自己更新自己岂不是完美。思考如下:

自己更新自己 ,然后杀了自己,启动新的自己。

代码可参考 https://github.com/zhaopeiym/IoTClient/blob/master/IoTClient.Tool/IndexForm.cs
中的 CheckUpgradeAsync 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/// <summary>
/// 检查当前是否需要升级
/// </summary>
private async Task CheckUpgradeAsync()
{
UpgradeFileManage();
HttpClient http = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(new VersionCheckInput()));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await http.PostAsync("https://download.haojima.net/api/IoTClient/VersionCheck", content);
var result = await response.Content.ReadAsStringAsync();
var VersionObj = JsonConvert.DeserializeObject<ResultBase<VersionCheckOutput>>(result);
if (VersionObj.Code == 200 && VersionObj.Data.Code == 1)
{
if (MessageBox.Show("IoTClient有新版本,是否升级到最新版本?", "版本升级", MessageBoxButtons.OKCancel) == DialogResult.OK)
{
if (new UpgradeForm().ShowDialog() != DialogResult.OK) return;
var newApp = Application.StartupPath + @"\temp." + Path.GetFileName(Application.ExecutablePath);
//打开临时文件 关闭并旧版本
Process.Start(newApp);
Close();
Environment.Exit(0);
}
}
}

/// <summary>
/// 升级文件处理
/// </summary>
private void UpgradeFileManage()
{
//如果启动的升级临时文件,
//则1、删除旧版本 2、复制当前临时文件为新版本 3、启动新版本 4、关闭当前打开的临时版本
if (Path.GetFileName(Application.ExecutablePath).Contains("temp."))
{
var filePath = Path.Combine(Application.StartupPath, Path.GetFileName(Application.ExecutablePath).Replace("temp.", ""));
var newFilePath = filePath;
try
{
try
{
//2.1删除旧版本
if (File.Exists(filePath)) File.Delete(filePath);
}
catch (Exception)
{
//如果因为进程正在使用中则休眠后再重试
//出现此问题的原因是,上一个程序还没关闭,这个程序就启动了,启动后会执行删除上一个程序,所以报错。
Thread.Sleep(500);
if (File.Exists(filePath)) File.Delete(filePath);
}
//3、复制临时文件为新的文件 打开新的文件
File.Copy(Application.ExecutablePath, newFilePath);
//3、打开新的文件
Process.Start(filePath);
//4、关闭临时文件
//Close();
Environment.Exit(0);
}
catch (Exception ex)
{
MessageBox.Show("升级失败 " + ex.Message);
}
}
//4.2如果当前启动的不是临时文件,则删除临时文件。
else
{
var filePath = Path.Combine(Application.StartupPath, "temp." + Path.GetFileName(Application.ExecutablePath));
try
{
if (File.Exists(filePath)) File.Delete(filePath);
}
catch (Exception)
{
Thread.Sleep(500);
if (File.Exists(filePath)) File.Delete(filePath);
}
}
}

下载新版本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public UpgradeForm()
{
InitializeComponent();
TopMost = true;
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.FixedSingle;
CheckForIllegalCrossThreadCalls = false;
Task.Run(async () =>
{
await DownloadAsync();
DialogResult = DialogResult.OK;
Close();
});
}

public async Task DownloadAsync()
{
long downloadSize = 0;//已经下载大小
long downloadSpeed = 0;//下载速度
using (HttpClient http = new HttpClient())
{
string contentType = "application/json";
http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(contentType));
http.DefaultRequestHeaders.Add("Authorization", $"Bearer 043fc7d1a15bbaa6e48591baadc6f8ea");

var httpResponseMessage = await http.GetAsync("http://localhost/api/v1.0/niuNiuCalc/download?fileName=NiuNiuCalcCsClient.exe&clientType=PC", HttpCompletionOption.ResponseHeadersRead);//发送请求
var contentLength = httpResponseMessage.Content.Headers.ContentLength; //文件大小
if (contentLength == null)
{
MessageBox.Show("服务器忙,请稍后再试。");
return;
}
using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
{
var readLength = 102400;//100K
byte[] bytes = new byte[readLength];
int writeLength;
var beginSecond = DateTime.Now.Second;//当前时间秒
//使用追加方式打开一个文件流
var filePath = Application.StartupPath + @"\temp." + Path.GetFileName(Application.ExecutablePath);
using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write))
{
while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)
{
fs.Write(bytes, 0, writeLength);
downloadSize += writeLength;
downloadSpeed += writeLength;
progressBar1.Invoke((Action)(() =>
{
var endSecond = DateTime.Now.Second;
if (beginSecond != endSecond)//计算速度
{
downloadSpeed = downloadSpeed / (endSecond - beginSecond);
Text = "下载速度" + downloadSpeed / 1024 + "KB/S";
beginSecond = DateTime.Now.Second;
downloadSpeed = 0;//清空
}
progressBar1.Value = Math.Max((int)(downloadSize * 100 / contentLength), 1);
}));
}
}
}
}
}

部分更新可配置文件

Version.json 文件用于比较每个组件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "1.0.01",
"FileName": "DcMoitorTools.zip",
"ReleaseTime": "2019-11-29 12:10:07",
"UpdateContent": "修复一堆bug,添加一些功能,优化一些体验...",
"Sha1": "703b9e5ae97e51c24b6ca774a6e97a5f0c608fa1",
"Files": [
{
"Name": "BD.Text.Test.dll",
"Version": "1.0.0.10",
"Path": "BD.Text.Test.dll",
"Sha1": "852ac3cc7a75f4554c14f9901996ea409b692fa9"
}
]
}

效果图