用Application Updater Block生成一个自我更新的WinForms 应用(amart client)

在过去的两个星期里, 我一直在做我的第一个真正的.net WinForm应用的开发. 这是一个很有趣的过程,我一直在疯了似的学习东西. 其中之一就是我要允许应用程序能够用微软的Application Updater Block进行自我更新。 当它正常工作的那一刻,让我有一种很大的成就感,同时我也意识到微软没有提供那种按步骤顺序的例子。 Duncan Mackenzie 有一个 很好的blog文章 可以做一个开始,但是这个例子是VB做的并且没有提供RSA公钥和私钥的细节情况,所以我决定说一下我的工作过程。 希望能对你有用!

Step #1 Install the Application Blocks

Download the Updater Application Block from Microsoft .

Run the MSI Installer.

Step #2 在项目中加入代码和引用:

把下列工程加入到你的WinForm工程所在的解决方案:

Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces

如果你选择默认安装的话,它们的位置可能是:

C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater

在你的WinForm工程中引用下列工程

Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement

把下列命名空间加入到你Form的.cs文件中

using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Xml;

然后 添加这个位置的应用程序更新代码到你的代码中. 你需要从你的MainForm初始化方法中调用 InitializeAutoUpdate()。

Step #3 生成你应用程序的发布目录结构并配置 AppStart.exe

生成一个用于客户端程序安装的目录. 本例子中,我们用如下的目录:

C:/Program Files/YourApp/1.0.0.0/

现在复制 AppStart.exe 和 AppStart.exe.config 到类似如下的根目录中

C:/Program Files/YourApp/AppStart.exe
C:/Program Files/YourApp/AppStart.exe.config

说明: 这两个文件你可以在如下目录中找到 “C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/AppStart/bin/Debug“

Step #4 修改 AppStart.exe.config 文件

AppStart.exe 会启动你的应用程序,如果更新文件下载完成之后还有可能要重启. 它需要知道启动你最新的程序的目录位置.
修改配置文件以配合当前的版本:

< appStart >
< ClientApplicationInfo >
< appFolderName > C:ProgramFilesYourApp 1.0 . 0.0 </ appFolderName >
< appExeName > YourAppName.exe </ appExeName >
< installedVersion > 1.0 . 0.0 </ installedVersion >
< lastUpdated > 2004 - 06 - 10T15: 33 : 17.3745836 - 04 : 00 </ lastUpdated >
</ ClientApplicationInfo >
</ appStart >

Step #5: 生成你的公钥和私钥

运行 "C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/ManifestUtility/bin/Debug/ManifestUtility.exe"

选择 “File..Generate Keys” 会提示你是否需要保存: PublicKey.xml 和 PrivateKey.xml 这两个密钥接下来就会用到.

我这里要提醒大家,这些密钥只要生成一次就可以了, 因为下面几个地方需要引用到RSA公钥和私钥. 你需要把这些密钥存放在一个安全的地方,因为在发布一个新的更新的时候会用到它

Step #6 创建IIS 虚拟目录

在你的Web服务器上生成一个目录来存放你的更新文件. 在这两个目录中要放两样东西 1) ServerManifest.xml 文件,包含最后版本的一些信息;2) 你的新程序的目录. 在这个目录里,生成一个目录来存放你的新版本程序. 在我们的例子中,我们用这两个目录, C:/Inetpub/AppUpdates 和C:/Inetpub/AppUpdates/1.0.0.1

用 IIS 管理器生成一个虚拟目录指向刚才的实际目录. 记下你的 URL, 在上传步骤中我们需要用到它. 你必须要打开虚拟目录的“目录浏览”选项.

Step #7. 配置你的版本 1.0.0.0 的App.config 文件

这里,我们会需要往里添加一些新东西. 首先, 我们需要加入一个configSections 元素来定义我们的 appUpdater 节:

<configSections>
<section name="appUpdater" type="Microsoft.ApplicationBlocks.ApplicationUpdater.UpdaterSectionHandler,Microsoft.ApplicationBlocks.ApplicationUpdater" />
</configSections>

接下来,我们需要添加一个 Version 键到我们的 appsettings 中, 我们首先设置我们的本地版本为 1.0.0.0, 这样我们就可以测试自动更新到版本 1.0.0.1

<appSettings>
<add key="VERSION" value="1.0.0.0" />
</appSettings>

最后,, 加入 appUpdater 节到你的配置文件中. 我这里用一对方括号把你要修改的值包含起来. 你可以直接从你上一步生成的 PublicKey.xml文件中复制 <RSAKeyValue> 元素.

<xmlFile> 元素必须要指向你在Step #6创建的虚拟目录的 URL .

< appUpdater >
< UpdaterConfiguration >
< pollingtype = " Seconds " value = " 120 " />
< logListenerlogPath = " C:ProgramFilesYourAppUpdaterLog.txt " />
< downloadertype = " Microsoft.ApplicationBlocks.ApplicationUpdater.Downloaders.BITSDownloader "
assembly
= " Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null " />
< validatortype = " Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator " assembly = " Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null " >
< key >
< RSAKeyValue >
< Modulus > [YOURMODULUSKEY] </ Modulus >
< Exponent > [YOUREXPONENET] </ Exponent >
</ RSAKeyValue >
</ key >
</ validator >
< applicationname = " [YOURAPPNAME] " useValidation = " true " >
< client >
< baseDir > C:ProgramFilesYourApp </ baseDir >
< xmlFile > C:ProgramFilesYourAppAppStart.exe.config </ xmlFile >
< tempDir > C:ProgramFilesYourApp emp </ tempDir >
</ client >
< server >
< xmlFile > http: // [YOURURL]/ServerManifest.xml</xmlFile>
< xmlFileDest > C:ProgramFilesYourAppServerManifest.xml </ xmlFileDest >
< maxWaitXmlFile > 60000 </ maxWaitXmlFile >
</ server >
</ application >
</ UpdaterConfiguration >
</ appUpdater >

Step #8 发布版本 1.0.0.0

设置应用程序版本号. 可以通过设置在 AssemblyInfo.cs 文件中的版本属性来设置版本号.

[assembly: AssemblyVersion("1.0.0.0")]

编译应用程序并复制 1.0.0.0 版程序到你程序的 1.0.0.0 目录中. “C:/Program Files/YourApp/1.0.0.0“

这里,你需要运行一下 AppStart.exe. 更新过程会失败,因为我们并没有把发布 ServerManifest XML 文件来指示应用程序新版本是否可用. 你可以检查日志文件,位置在 C:/Program Files/YourApp/ 目录中.

Step #9 构建版本 1.0.0.1

这是最有趣的部分. 首先, 通过更新应用程序的 AssemblyInfo.cs 和 App.config 文件内容来生成修订版本 1.0.0.1 . 编译程序, 然后复制文件到step #6生成的Web服务器目录中.

Step #10 生成服务器的清单文件

这个是最后一步. 如果你对本步骤中的.config文件作了任何修改的话,都必须把本步骤重来一遍. 做法如下:

再次运行 ManifestUtility 程序.
在 “Update files folder“ 选择器中选择 1.0.0.1 目录 .
输入更新位置的 URL .
输入新版本号 1.0.0.1
打开之前生成的 PrivateKey.xml 文件.
选择验证类 “Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”
鼠标点击 CreateManifest, 并保存 ServerManifest.xml 文件到你的虚拟服务器目录中.
就 这些! Pheeew! 从你的 C:/Program Files/YourApp/ 目录中运行你的 AppStart.exe . 你的程序就会被装入, 当你的程序运行的时候,你就会得到一个提示 “新版本可用” . 新版本会下载到目录 C:/Program Files/YourApp/1.0.0.1 中, 然后程序会自动重启. 如果有任何问题, 记得检查一下日志文件. 这些日志在诊断问题的时候会很有用的.

-Brendan

posted on Thursday, June 10, 2004 11:25 AM

附录:文中步骤 #2 包含的代码如下:

Auto - UpdateStuff Auto-UpdateStuff #regionAuto-UpdateStuff

privateApplicationUpdateManager_updater=null;
privateThread_updaterThread=null;
privateconstintUPDATERTHREAD_JOIN_TIMEOUT=3*1000;

privatedelegatevoidMarshalEventDelegate(objectsender,UpdaterActionEventArgse);

privatevoidInitializeAutoUpdate()
...{
//hookProcessExitforachancetocleanupwhenclosedperemptorily
AppDomain.CurrentDomain.ProcessExit+=newEventHandler(CurrentDomain_ProcessExit);

//makeanUpdaterforusein-processwithus
_updater=newApplicationUpdateManager();

//hookUpdaterevents
_updater.DownloadStarted+=newUpdaterActionEventHandler(OnUpdaterDownloadStarted);
_updater.FilesValidated
+=newUpdaterActionEventHandler(OnUpdaterFilesValidated);
_updater.UpdateAvailable
+=newUpdaterActionEventHandler(OnUpdaterUpdateAvailable);
_updater.DownloadCompleted
+=newUpdaterActionEventHandler(OnUpdaterDownloadCompleted);

//starttheupdateronaseparatethreadsothatourUIremainsresponsive
_updaterThread=newThread(newThreadStart(_updater.StartUpdater));
_updaterThread.Start();

//getversionfromconfig,setcaptioncorrectly
stringversion=System.Configuration.ConfigurationSettings.AppSettings["version"];
this.Text=this.Text+String.Format("v.{0}",version);
}


privatevoidCurrentDomain_ProcessExit(objectsender,EventArgse)
...{
StopUpdater();
}



privatevoidStopUpdater()
...{
//tellupdatertostop
_updater.StopUpdater();
if(null!=_updaterThread)
...{
//jointheupdaterthreadwithasuitabletimeout
boolisThreadJoined=_updaterThread.Join(UPDATERTHREAD_JOIN_TIMEOUT);
//checkifwejoined,ifwedidn'tinterruptthethread
if(!isThreadJoined)
...{
_updaterThread.Interrupt();
}

_updaterThread
=null;
}

}


/**//**//**////<summary>
///ThishandlergetsfiredbytheWindowsUIthreadthatisthemainSTAthreadforTHISFORM.Ittakesthesame
///argumentsastheeventhandlerbelowit--sender,e--andactsonthemusingthemainthreadNOTtheeventingthread
///</summary>
///<paramname="sender">marshalledreferencetotheoriginalevent'ssenderargument</param>
///<paramname="e">marshalledreferencetotheoriginalevent'sargs</param>

privatevoidOnUpdaterDownloadStartedHandler(objectsender,UpdaterActionEventArgse)
...{
Debug.WriteLine(
"Thread:"+Thread.CurrentThread.GetHashCode().ToString());

Debug.WriteLine(String.Format(
"DownloadStartedforapplication'{0}'",e.ApplicationName));
}



/**//**//**////<summary>
///EventhandlerforUpdaterevent.Thiseventisfiredbytheoriginatingthreadfrom"inside"theUpdater.Whileitis
///possibleforthissamethreadtoactonourUI,itisNOTagoodthingtodo--UIisnotthreadsafe.
///ThereforeherewemarshalfromtheEventingthread(belongstoUpdater)toourwindowthreadusingthesynchronousInvoke
///mechanism.
///</summary>
///<paramname="sender">eventsenderinthiscaseApplicationUpdaterManager</param>
///<paramname="e">theUpdaterActionEventArgspackagedbyUpdater,whichgivesusaccesstoupdateinformation</param>

privatevoidOnUpdaterDownloadStarted(objectsender,UpdaterActionEventArgse)
...{
//usingthesynchronous"Invoke".Thismarshalsfromtheeventingthread--whichcomesfromtheUpdaterandshouldnot
//beallowedtoenterand"touch"theUI'swindowthread
//soweuseInvokewhichallowsustoblocktheUpdaterthreadatwillwhileonlyallowingwindowthreadtoupdateUI
Debug.WriteLine(String.Format("[OnUpdaterDownloadStarted]Thread:{0}",Thread.CurrentThread.GetHashCode().ToString()));
this.Invoke(
newMarshalEventDelegate(this.OnUpdaterDownloadStartedHandler),
newobject[]...{sender,e});
}



/**//**//**////<summary>
///ThishandlergetsfiredbytheWindowsUIthreadthatisthemainSTAthreadforTHISFORM.Ittakesthesame
///argumentsastheeventhandlerbelowit--sender,e--andactsonthemusingthemainthreadNOTtheeventingthread
///</summary>
///<paramname="sender">marshalledreferencetotheoriginalevent'ssenderargument</param>
///<paramname="e">marshalledreferencetotheoriginalevent'sargs</param>

privatevoidOnUpdaterFilesValidatedHandler(objectsender,UpdaterActionEventArgse)
...{
Debug.WriteLine(String.Format(
"FilesValidatedsuccessfullyforapplication'{0}'",e.ApplicationName));

//askusertousenewapp
DialogResultdialog=MessageBox.Show(
"Wouldyouliketostopthisapplicationandopenthenewversion?","OpenNewVersion?",MessageBoxButtons.YesNo);
if(DialogResult.Yes==dialog)
...{
StartNewVersion(e.ServerInformation);
}

}


/**//**//**////<summary>
///EventhandlerforUpdaterevent.Thiseventisfiredbytheoriginatingthreadfrom"inside"theUpdater.Whileitis
///possibleforthissamethreadtoactonourUI,itisNOTagoodthingtodo--UIisnotthreadsafe.
///ThereforeherewemarshalfromtheEventingthread(belongstoUpdater)toourwindowthreadusingthesynchronousInvoke
///mechanism.
///</summary>
///<paramname="sender">eventsenderinthiscaseApplicationUpdaterManager</param>
///<paramname="e">theUpdaterActionEventArgspackagedbyUpdater,whichgivesusaccesstoupdateinformation</param>

privatevoidOnUpdaterFilesValidated(objectsender,UpdaterActionEventArgse)
...{
//usingtheasynchronous"BeginInvoke".
//wedon'tneed/wanttoblockhere
this.BeginInvoke(
newMarshalEventDelegate(this.OnUpdaterFilesValidatedHandler),
newobject[]...{sender,e});
}


/**/ /**/ /**/ ///<summary>
///ThishandlergetsfiredbytheWindowsUIthreadthatisthemainSTAthreadforTHISFORM.Ittakesthesame
///argumentsastheeventhandlerbelowit--sender,e--andactsonthemusingthemainthreadNOTtheeventingthread
///</summary>
///<paramname="sender">marshalledreferencetotheoriginalevent'ssenderargument</param>
///<paramname="e">marshalledreferencetotheoriginalevent'sargs</param>

private void OnUpdaterUpdateAvailableHandler( object sender,UpdaterActionEventArgse)
... {
Debug.WriteLine(
"Thread:"+Thread.CurrentThread.GetHashCode().ToString());

stringmessage=String.Format(
"Updateavailable:Thenewversionontheserveris{0}andcurrentversionis{1}wouldyouliketoupgrade?",
e.ServerInformation.AvailableVersion,
System.Configuration.ConfigurationSettings.AppSettings[
"version"]);

//forupdateavailableweactuallyWANTtoblockthedownloadingthreadsowecanrefuseanupdate
//andresetuntilnextpollingcycle;
//NOTEthatwedon'tblockthethread_intheUI_,wehaveitblockedatthemarshallingdispatcher"OnUpdaterUpdateAvailable"
DialogResultdialog=MessageBox.Show(message,"UpdateAvailable",MessageBoxButtons.YesNo);

if(DialogResult.No==dialog)
...{
//ifno,stoptheupdaterforthisapp
_updater.StopUpdater(e.ApplicationName);
Debug.WriteLine(
"UpdateCancelled.");
}

else
...{
Debug.WriteLine(
"Updateinprogress.");
}

}


/**/ /**/ /**/ ///<summary>
///EventhandlerforUpdaterevent.Thiseventisfiredbytheoriginatingthreadfrom"inside"theUpdater.Whileitis
///possibleforthissamethreadtoactonourUI,itisNOTagoodthingtodo--UIisnotthreadsafe.
///ThereforeherewemarshalfromtheEventingthread(belongstoUpdater)toourwindowthreadusingthesynchronousInvoke
///mechanism.
///</summary>
///<paramname="sender">eventsenderinthiscaseApplicationUpdaterManager</param>
///<paramname="e">theUpdaterActionEventArgspackagedbyUpdater,whichgivesusaccesstoupdateinformation</param>

private void OnUpdaterUpdateAvailable( object sender,UpdaterActionEventArgse)
... {
//usingthesynchronous"Invoke".Thismarshalsfromtheeventingthread--whichcomesfromtheUpdaterandshouldnot
//beallowedtoenterand"touch"theUI'swindowthread
//soweuseInvokewhichallowsustoblocktheUpdaterthreadatwillwhileonlyallowingwindowthreadtoupdateUI
this.Invoke(
newMarshalEventDelegate(this.OnUpdaterUpdateAvailableHandler),
newobject[]...{sender,e});
}



/**/ /**/ /**/ ///<summary>
///ThishandlergetsfiredbytheWindowsUIthreadthatisthemainSTAthreadforTHISFORM.Ittakesthesame
///argumentsastheeventhandlerbelowit--sender,e--andactsonthemusingthemainthreadNOTtheeventingthread
///</summary>
///<paramname="sender">marshalledreferencetotheoriginalevent'ssenderargument</param>
///<paramname="e">marshalledreferencetotheoriginalevent'sargs</param>

private void OnUpdaterDownloadCompletedHandler( object sender,UpdaterActionEventArgse)
... {
Debug.WriteLine(
"DownloadCompleted.");

}


/**/ /**/ /**/ ///<summary>
///EventhandlerforUpdaterevent.Thiseventisfiredbytheoriginatingthreadfrom"inside"theUpdater.Whileitis
///possibleforthissamethreadtoactonourUI,itisNOTagoodthingtodo--UIisnotthreadsafe.
///ThereforeherewemarshalfromtheEventingthread(belongstoUpdater)toourwindowthreadusingthesynchronousInvoke
///mechanism.
///</summary>
///<paramname="sender">eventsenderinthiscaseApplicationUpdaterManager</param>
///<paramname="e">theUpdaterActionEventArgspackagedbyUpdater,whichgivesusaccesstoupdateinformation</param>

private void OnUpdaterDownloadCompleted( object sender,UpdaterActionEventArgse)
... {
//usingthesynchronous"Invoke".Thismarshalsfromtheeventingthread--whichcomesfromtheUpdaterandshouldnot
//beallowedtoenterand"touch"theUI'swindowthread
//soweuseInvokewhichallowsustoblocktheUpdaterthreadatwillwhileonlyallowingwindowthreadtoupdateUI
this.Invoke(
newMarshalEventDelegate(this.OnUpdaterDownloadCompletedHandler),
newobject[]...{sender,e});
}



private void StartNewVersion(ServerApplicationInfoserver)
... {
XmlDocumentdoc
=newXmlDocument();

//loadconfigfiletogetbasedir
doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);

//getthebasedir
stringbaseDir=doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/application/client/baseDir").InnerText;
stringnewDir=Path.Combine(baseDir,"AppStart.exe");

ProcessStartInfoprocess
=newProcessStartInfo(newDir);
process.WorkingDirectory
=Path.Combine(newDir,server.AvailableVersion);

//launchnewversion(actually,launchAppStart.exewhichHASpointertonewversion)
Process.Start(process);

//tellupdatertostop
CurrentDomain_ProcessExit(null,null);
//leavethisapp
Environment.Exit(0);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值