当前位置: 首页 > 编程日记 > 正文

Winform窗体应用程序的自动更新功能

本文将演示一种桌面程序自动更新方案,其步骤比较多,但原理非常简单,通用性尚可,对于小型应用来说,直接拿去就可以用了。

原理

服务器端的结构是这样的:



其工作原理如下:

Update.asmx 仅提供一个功能,就是检测是否需要更新,在需要更新的时候就返回一个更新地址,通常情况下返回的地址就是Download.ashx,而在某些特殊情况下,也可以修改服务端使之从其他Url提供更新下载。检测是否需要更新的的具体做法是:首先获取Updata目录中的主程序版本号,再获取数据库中的最新版本号,两者对比。如果相同则直接与客户端提供的版本号相对比并返回结果;如果不同则将主程序版本号写入数据库,然后生成新的更新文件包,直接向客户端返回更新地址。

Download.ashx的功能仅仅是将最新版本更新文件包输出。

而客户端部分包含主程序、 Update.exe以及其他附属文件,更新时由主程序检测并下载更新,在主程序退出时,如有更新并已成功下载,则调用Update.exe完成解包及更新覆盖工作。需注意的是:Update.exe永远不能被更新,因为它无法更新其自身,所以服务端更新时也不要将Update.exe纳入更新包。

下面就是来实际编写一个自动更新解决方案:

服务器端

首先建立一个Web服务项目,项目名为“自动更新服务”:



添加一数据库,名为Database.mdf:



在数据库中创建新的数据库关系图,并如下设计数据库表:

创建一个Ado.Net Entity Data Model,名为Model.edmx:



从刚才的建立的数据库中生成模型:



在Web.Config的appSettings节点中新增两个节点,用以设置更新程序的主文件名及更新包下载地址:
  1. <appSettings>
  2. <add key="主程序文件名" value="MyApp.exe"/>
  3. <add key="更新包下载地址" value="Download.ashx"/>
  4. </appSettings>
复制代码
引入一个GZip类用以打包(该类的源码将在文章末尾随本文示例源代码一并提供):



添加一个新的Web服务,名为Update.asmx:



书写如下代码:
  1. [WebMethod]
  2. public string GetUpdate(string ClientVerison)
  3. {
  4. if (获取最新版本() != ClientVerison)
  5. {
  6. return System.Web.Configuration.WebConfigurationManager.AppSettings["更新包下载地址"];
  7. }
  8. return null;
  9. }

  10. static string 获取最新版本()
  11. {
  12. string v = 获取文件版本(HttpContext.Current.Server.MapPath(string.Format("~/App_Data/Update/{0}", System.Web.Configuration.WebConfigurationManager.AppSettings["主程序文件名"])));
  13. using (var c = new DatabaseEntities())
  14. {
  15. //从数据库取得最新版本信息
  16. var q = c.UpdateVersion.OrderByDescending(f => f.PublicTime).FirstOrDefault();
  17. if (q == null || v != q.Version)
  18. {
  19. //数据库中的版本与当前主程序版本不一致时,以主程序版本为准,写入数据库,并生成新的更新文件包
  20. var d = new UpdateVersion() { Version = v, PublicTime = DateTime.Now };
  21. c.AddToUpdateVersion(d);
  22. c.SaveChanges();
  23. 打包更新文件(HttpContext.Current.Server.MapPath("~/App_Data/Update/"), HttpContext.Current.Server.MapPath("~/App_Data/Update.gzip"));
  24. }
  25. }
  26. return v;
  27. }

  28. public static void 打包更新文件(string 打包目录, string 输出文件)
  29. {
  30. GZip.压缩(输出文件, Directory.GetFiles(打包目录).Concat(Directory.GetDirectories(打包目录)).ToArray());
  31. }

  32. public static string 获取文件版本(string 文件路径)
  33. {
  34. FileVersionInfo f = FileVersionInfo.GetVersionInfo(文件路径);
  35. return f.FileVersion;
  36. }
复制代码
创建Download.ashx,用以输出更新文件包:



代码:
  1. public void ProcessRequest(HttpContext context)
  2. {
  3. context.Response.ContentType = "application/zip";
  4. context.Response.WriteFile(context.Server.MapPath("~/App_Data/Update.gzip"));
  5. }
复制代码

服务端至此就编写完毕了。

客户端

新建一个WinForm应用程序项目,名为Update:



建好之后直接删掉Form1.cs吧,此程序不需要界面,在Program.cs中写代码就可以了。

同样需要引入GZip类用于解包:



然后编写代码:
  1. [STAThread]
  2. static void Main()
  3. {
  4. try
  5. {
  6. var d = DateTime.Now;
  7. while (DateTime.Now.Subtract(d).TotalSeconds < 10) Application.DoEvents();
  8. GZip.解压缩(Path.Combine(Application.StartupPath, "update.data"), Application.StartupPath);
  9. }
  10. catch { }
  11. }
复制代码
这里的作用就是等待10秒,然后解包update.data文件,覆盖到当前目录中。

现在来建立主程序,主程序是WinForm、命令行、WPF都可以,我们新建一个WPF应用程序,命名为MyAPP:



为程序添加服务引用:



这里的地址使用的是本地的调试地址。

为了检测主程序自身的版本号,还需要添加对System.Windows.Forms的引用。

然后开始设计界面,这里仅为演示更新操作,所以界面上只是简单的设计了更新相关的提示、操作控件:



代码为:
  1. <Window x:Class="MyApp.Window1"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="Window1" Height="300" Width="377" Loaded="Window_Loaded" Closed="Window_Closed">
  5. <Grid>
  6. <Grid.RowDefinitions>
  7. <RowDefinition Height="1*" />
  8. <RowDefinition Height="1*" />
  9. <RowDefinition Height="1*" />
  10. </Grid.RowDefinitions>
  11. <Label Margin="0" Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden">检测到新版本,是否下载?</Label>
  12. <Button Grid.Row="1" Height="23" Name="button1" VerticalAlignment="Center" Visibility="Hidden" Click="button1_Click">开始下载</Button>
  13. <Label Grid.Row="2" Margin="0" Name="label2" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Hidden">更新包已下载完毕,在程序关闭后将自动执行更新操作。</Label>
  14. </Grid>
  15. </Window>
复制代码
需注意的是,这里控件都被设置为Visibility="Hidden",我们将会在需要时再将其显示出来。

编写后台代码:
  1. public Uri DownloadUri
  2. {
  3. get
  4. {
  5. return _DownloadUri;
  6. }
  7. set
  8. {
  9. _DownloadUri = value;
  10. }
  11. }
  12. private Uri _DownloadUri;

  13. public bool UpdateReady
  14. {
  15. get
  16. {
  17. return _UpdateReady;
  18. }
  19. set
  20. {
  21. _UpdateReady = value;
  22. }
  23. }
  24. private bool _UpdateReady;

  25. private void Window_Loaded(object sender, RoutedEventArgs e)
  26. {
  27. var u = new MyApp.ServiceReference.UpdateSoapClient();
  28. var s=u.GetUpdate(System.Windows.Forms.Application.ProductVersion);
  29. if (!string.IsNullOrEmpty(s))
  30. {
  31. //获取相对于Web服务所在Uri的Uri
  32. DownloadUri = new Uri(u.Endpoint.ListenUri, s);
  33. label1.Visibility = button1.Visibility = Visibility.Visible;
  34. }
  35. }

  36. private void button1_Click(object sender, RoutedEventArgs e)
  37. {
  38. var c = new WebClient();
  39. c.DownloadFile(DownloadUri, System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.data"));
  40. UpdateReady = true;
  41. label2.Visibility = Visibility.Visible;
  42. }

  43. private void Window_Closed(object sender, EventArgs e)
  44. {
  45. if (UpdateReady)
  46. {
  47. Process.Start(System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "update.exe"));
  48. }
  49. }
复制代码
测试

现在将主程序、附属文件和Update.exe放在一起,并将主程序及附属文件复制一份放到服务器端的App_data/Update/目录中,再添加一个“更新说明.txt”:



然后启动客户端程序进行测试,应该看到程序界面里什么都没有,因为客户端和服务器端程序版本是一致的。

现在我们修改客户端版本号为1.0.0.1:



然后重新编译程序。

因为服务器仅仅是判断版本号是否不同,而不是哪个更高,所以不仅仅是升级,降级更新也是可以的,我们来测试一下:



找到所谓的新版本了^^,点开始下载:



下载完成,这时目录里就有update.data这个文件了。

现在关闭程序,等待10秒,让Update.exe完成更新:



可以看到,程序被降级为1.0.0.0了,而且那个“更新说明.txt”也被更新出来了。

转载于:https://www.cnblogs.com/guoqiao/archive/2010/08/17/1801444.html

相关文章:

[UWP] 用 AudioGraph 来增强 UWP 的音频处理能力——AudioFrameInputNode

原文:[UWP] 用 AudioGraph 来增强 UWP 的音频处理能力——AudioFrameInputNode上一篇心得记录中提到了 AudioGraph, 描述了一下 什么是 AudioGraph 以及其中涉及到的各种类型的 节点&#xff08;Node&#xff09;。 这一篇就其中比较有意思的 AudioFrameInputNode 来详细展开一…

Png透明背景的电话图标。

转载于:https://www.cnblogs.com/li0566/p/4343427.html

CSS改变nth-child()和nth-last-child()的参数灵活选择元素编号

注&#xff1a;下面的所有示例 1. div可以更换成任意标签 2. k是变量&#xff0c;可以换成特定数值&#xff0c;n保持不变 选中偶数行 div: nth-child(2n)div: nth-child(even) 选中奇数行 div :nth-child(odd)div :nth-child(2n-1) 选中前k行 div :nth-child(-nk) 选…

关于Silverlight中多项目共享DLL文件的讨论

假如你的解决方案中有两个Silverlight项目&#xff0c;其中的DLL文件时两个SL项目都使用到的&#xff0c;为了能够最大程度的减小XAP包的体积&#xff0c;你选择了系统的这个选项 编译后在Web的ClientBin文件夹下会出现这样的结构 这样呢&#xff0c;两个项目共享这些DLL的压缩…

核方法---径向基函数网络

为什么80%的码农都做不了架构师&#xff1f;>>> Nadarayas-Watson模型 转载于:https://my.oschina.net/liyangke/blog/2986510

c# 获取客户端IP地址方法

客户端ip: Request.ServerVariables.Get("Remote_Addr").ToString(); 客户端主机名: Request.ServerVariables.Get("Remote_Host").ToString(); 客户端浏览器IE&#xff1a; Request.Browser.Browser; 客户端浏览器 版本号&#xff1a; Request.Browser.M…

CSS结构选择器四种结构关系的范围

1. 空格&#xff1a; 表示<div>标签下所有的<h1>标签 div h1 2. >: 表示<div>标签下直接的<h1>标签 div>h1 3. ~:表示与<div>并列的所有<h1>标签 div~h1 4. :表示与<div>并列且紧邻的<h1>标签 divh1 注&#xff…

VMware前路难测,多个厂家群雄逐鹿

2019独角兽企业重金招聘Python工程师标准>>> 在人们高谈Salesforce、亚马逊等新兴云计算厂商取得的成就时&#xff0c;以VMware、HPE和Cisco为代表的老牌厂商也在进行着自己的转型和变化&#xff0c;而且还取得一定的进展。以VMware为例&#xff0c;虚拟机巨头公布了…

Silverlight学习笔记十七BingMap(六)之获取图片系统的图片信息ImageryService的应用...

BIngMap的ImageryService服务是一个微软发布的WCF服务&#xff0c;它用来获取图片系统的图片信息.服务地址&#xff1a;http://dev.virtualearth.net/webservices/v1/imageryservice/ImageryService.svc 本例中使用的是中文图片系统 效果如图 一、获取中文图片系统类&#xff0…

c++ stack 的使用

(1) stack::empty bool empty ( ) const; 判断是否为空。 return Value : true if the container size is 0, false otherwise; (2) stack::pop void pop ( ); 在栈的顶部移除元素。 (3) stack::push void push ( const T& x ); 在栈顶添加元素 (4) stack::size size_type …

ES和JS的区别,以及JavaScript的基本组成

JavaScript是语言&#xff0c;而ECMAScript(即ECMA-262,ECMA是欧洲计算机制造商协会)是为了规范JS而制定的标准&#xff0c;ECMAScript有不同版本&#xff0c;最近的版本是第10版&#xff0c;发布于2019.6。 完整的JavaScript的实现包含以下几个部分 核心(ECMAScript)&#x…

微软职位内部推荐-Senior Software Engineer-Eco

微软近期Open的职位:The MOD Ecosystem team is dedicated to expanding the reach and value of Office by enabling developers to create solutions built on the Office suite of applications or powered by the O365 services. &nbsp We have an exciting mix of cha…

转Meta的http-equiv属性详解

http-equiv顾名思义&#xff0c;相当于http的文件头作用&#xff0c;它可以向浏览器传回一些有用的信息&#xff0c;以帮助正确和精确地显示网页内容&#xff0c;与之对应的属性值为content&#xff0c;content中的内容其实就是各个参数的变量值。 meat标签的http-eq…

bzoj 2946 [Poi2000]公共串——后缀自动机

题目&#xff1a;https://www.lydsy.com/JudgeOnline/problem.php?id2946 对每个串都建一个后缀自动机&#xff0c;然后 dfs 其中一个自动机&#xff0c;记录同步的话在别的自动机上走到哪些点了&#xff1b;只要有一个自动机上走不下去了&#xff0c;就都走不下去了。每走到一…

CSS:当子元素皆浮动,撑开父元素的3种方式

1. 在子元素后面补充同级的空元素&#xff0c;并定义清除浮动样式 html文件 <main><div><span>肥水东流无尽期。当初不合种相思。梦中未比丹青见&#xff0c;暗里忽惊山鸟啼。</span><br><br><span>春未绿&#xff0c;鬓先丝。人间…

js正则表达式的使用详解

本文转自&#xff1a;http://www.jb51.net/article/39623.htm 1定义正则表达式2关于验证的三个这则表达式方法3正则表达式式的转义字符1定义正则表达式在js中定义正则表达式很简单&#xff0c;有两种方式&#xff0c;一种是通过构造函数&#xff0c;一种是通过//&#xff0c;也…

Python安装及netcdf数据读写

为什么80%的码农都做不了架构师&#xff1f;>>> 一、在CentOS7系统上安装Python3 在anaconda官网下载&#xff08;http://https://www.anaconda.com/download/#linux&#xff09;&#xff08;Anaconda指的是一个开源的Python发行版本&#xff0c;是Python的包管理器…

用Visio进行数据库建模、设计和实现

用Visio进行数据库建模、设计和实现 摘要&#xff1a;Visio是微软著名的图形软件&#xff0c;功能强大。使用Visio完成绘图任务时能够显著地提高工作效率和质量。目前功能最全的Visio版本是VSEA(Visual Studio Enterprise Architect)2003所自带的2003版。 原文发表在中科院网络…

JavaScript如何声明对象、函数以及对象中的函数(即方法)

目录 声明对象的2种最常见方法 声明函数的2种最常见方法 在对象中声明函数 声明对象的2种最常见方法 1&#xff09; var Zhihuijun {name:彭志辉,age:28,upName:稚晖君,company:Huawei,};console.log(Zhihuijun.name目前在Zhihuijun.company工作); 2&#xff09; var Zhi…

python之抽象基类

抽象基类特点 1.不能够实例化 2.在这个基础的类中设定一些抽象的方法&#xff0c;所有继承这个抽象基类的类必须覆盖这个抽象基类里面的方法 思考 既然python中有鸭子类型&#xff0c;为什么还要使用抽象基类&#xff1f; 一是我们在某些情况下希望判定某个对象的类型&#xff…

Poolmon

P 排序标记列表中的通过分页&#xff0c;无-分页或混合。请注意 P 循环通过每个。B 进行排序按最大字节使用情况的标记。M 按最大字节分配对标签进行排序。T 按标记名称按字母顺序排序标记。E 显示分页&#xff0c;跨底部未分页的总计。循环。A 按分配大小对标签进行排序。F 按…

移动网站性能优化(未完。。。)

移动网站天生有三种性能限制&#xff1a;带宽低&#xff0c;内存小&#xff0c;处理器性能低。 这些性能挑战又加上其他的问题&#xff0c;如&#xff1a; 网页比以前更大延迟相差巨大下载速度相差巨大解决问题&#xff1a; 改善网站性能的主要策略并没有因为从PC变成手机或者平…

JavaScript对象数组示例

可以用于暂时无法从数据库中拿到数据时&#xff0c;模拟数据使用 var datas [{name:囧菌,subject:JavaScript,grade:100 },{name : 双笙,subject : React,grade : 100 },{name:陈拾月,subject:Vue,grade:100 }]; 其实相当于 var datasInt [1,2,3]; 注意&#xff1a;是引号…

PHP协程:并发 shell_exec

在PHP程序中经常需要用shell_exec执行一些命令&#xff0c;而普通的shell_exec是阻塞的&#xff0c;如果命令执行时间过长&#xff0c;那可能会导致进程完全卡住。在Swoole4协程环境下可以用Co::exec并发地执行很多命令。 本文基于Swoole-4.2.9和PHP-7.2.9版本协程示例 <?p…

oralce 增加表字段命令|oralce 增加表字段类型命令

oralce 增加表字段命令 语法 alter table 表明 add 字段名 类型 alter table aqcuser add email varchar(36)转载于:https://www.cnblogs.com/bestsaler/archive/2010/09/08/1835430.html

mac 下周期调度命令或脚本

crontab 是在linux服务器上部署定时任务的方法0 5 * * * /usr/bin/python /data/www/tools/mysql_backup.pycmd之前有5个项目要填&#xff0c;分别对应分钟 小时 天 月 一周当中第几天( 0-6 ,0表示星期天)填写方法* 表示都满足&#xff0c;例如 * * * * * 表示每分钟执行一次&a…

JavaScript封装一个注册函数解决兼容问题

我们知道JavaScript注册(绑定)事件主要有两类方式&#xff0c;第一类传统方式具有注册事件的唯一性&#xff0c;即对于同一元素的同一事件&#xff0c;不会出现两个处理函数&#xff0c;如下 var btn document.querySelector(button);btn.onclick function(){document.body.s…

四种DCOM错误的区别,0x80080005 0x800706be 0x80010105 0x

四种DCOM错误的区别Differences between the following DCOM error0x800800050x800706be0x800101050x800706ba0x80080005:CO_E_Server_Exec_FailureServer execution failedIt is usually quite clear: COM (really, RPCSS) tried to launch a particular server process and e…

给django视图类添加装饰器

要将login_required装饰到view class的dispatch方法上&#xff0c; 因为dispatch方法为类方法&#xff0c;不是单个的函数&#xff0c;所以需要将装饰函数的装饰器 login_required转化为装饰类方法的装饰器&#xff0c;就要用到method_decorator . method_decorator的参数可以是…

JavaScript实现智能搜索框

应用场景&#xff1a; 1. 搜索框在页面中占据的空间过小&#xff0c;希望无论浏览到什么位置&#xff0c;可以轻易地回到并聚焦搜索框。 2. 搜索框里面的文字大小过小&#xff0c;希望能够在上方开辟一块空间放大内容 解决思路&#xff1a; 1. 对整个页面添加键盘事件keyup…