您的位置:首页 > 移动开发

在VS2005(C#winform)中应用Updater Application Block(UAB)进行在线更新

2006-11-11 02:32 477 查看
一、Updater Application Block(UAB)实现自动更新特性
使用UAB可以实现对.NET应用智能更新支持,UAB为应用提供了下载,验证和后置处理机制。通过UAB提供的接口,我们可以轻易对UAB根据自己需要进行扩展。


UAB主要有四个模块组成Updater, Downloader, Validate以及Post Processor组成。Updater负责整个更新工作的管理;Downloader实现文件的下载,UAB中采用BITS (Background Intelligence Transfer Service)作为Downloader,UAB提供了IDownloader 接口,实现这个接口,开发者可以开发基于任意协议的下载器;Validator完成对下载文件的校验,UAB中提供KeyValidator和RSAValidator两个类,分别来实现对对称和非对称加密文件的校验;Post Processor则提供完成文件更新后需要进行的各种操作。

二、自动更新程序的制作方法
Step#1 下载安装UAB
记下安装路径,安装目录下有以后要引用的项目文件。如果你选择默认安装的话,它们的位置可能是: C:\Program Files\Microsoft ApplicationBlocksfor .NET\Updater\Code\CS\Microsoft.ApplicationBlocks.Updater (建议:把它们拷贝到与您的项目相同文件夹,方便后面应用)。

Step#2 在你的项目中添加项目引用及相关代码
(1) 把下列项目加入到你的项目所在的解决方案:
Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces

(2)在项目(要进行自动更新的项目)中添加项目引用
Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement

(3)我们装自动更新的主要功能封装在SelfUpdater类中,代码请查看附录
在类文件中添加以下命名空间
using System;
using System.Xml;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using Config;
using Microsoft.ApplicationBlocks.ApplicationUpdater;
其中Config是当前解决方案中新增的类项目,用来更改程序的配置信息,代码请查看Step #4; SelfUpdater类中通过InitUpdater()方法对Updater进行初始化,主要工作包括实例化ApplicationUpdateManager,在需要进行更新时调用函数StartUpdate()即可,调用代码请查看Step #5

Step #3 创建发布目录并配置配置文件
(1)现在复制 AppStart.exe 和 AppStart.exe.config 到您的项目中,以便打包时应用。
说明:这两个文件你可以在如下目录中(安装UAB的目录)找到 “C:\Program Files\Microsoft Application Blocks for .NET\Updater\Code\CS\Microsoft.ApplicationBlocks.Updater\AppStart\bin\Debug“

(2)配置文件 AppStart.exe.config
<appStart>
<ClientApplicationInfo>
<appFolderName>C:\Program Files\YourApp\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>
appFolderName 存放当前版本的exe所在的文件夹(可写成安装目录,可写成项目打包后安装所在的默认目录)
appExeName是运行的exe的文件名
installedVersion是当前版本号
lastUpdated是最新更新的时间
更新成功以后这几个项都会自动更改
另外不会更改的有:
   YourApp是项目名
   YourAppName.exe项目的可执行文件

Step #4在当前解决方案中添加一个类项目Config,(注意哦,是类项目,不是类哦)
  该项目的主要作用:是修改配置文件的路径,包括:AppStart.exe.config,YourApp.exe.config   
代码如下:
using System;
using System.Xml;
using System.Xml.XPath;
using System.IO;
namespace Config
{
/// <summary>
/// Config 的摘要说明。
/// </summary>
public class ConfigFile:IDisposable
{
private XmlDocument xmlconfig;
private string configfilepath=@".\Appstart.config";
private class AppStartConfig{
string starterconfig="";
public AppStartConfig(string file){
starterconfig=file;
}
public void ChangeAppStartPath(string startpath){
XmlDocument doc=new XmlDocument();
doc.Load(starterconfig);
XmlNodeList nodes=doc.GetElementsByTagName("appFolderName");
if(nodes.Count>0)
nodes[0].InnerText=startpath;
doc.Save(starterconfig);
}
}
public ConfigFile(string path)
{
configfilepath=path;
xmlconfig=new XmlDocument();
xmlconfig.Load(path);
}
/// <summary>
/// 设置日志路径
/// </summary>
/// <param name="path"></param>
public void SetLogPath(string path){
XmlDocument doc=new XmlDocument();
doc.Load(configfilepath);
XmlNodeList nodes=doc.GetElementsByTagName("logListener");
if(path.Length>0)
{
XmlNode node=null;
if(nodes.Count>0)
node=nodes[0];
else
{
node=doc.CreateElement("logListener");
XmlAttribute attr=doc.CreateAttribute("logPath");
node.Attributes.Append(attr);
doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration").AppendChild(node);
}
node.Attributes["logPath"].Value=path;
}
else{
XmlNode node=doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/logListener");
if(node!=null)
node.ParentNode.RemoveChild(node);
}
SetEMABLogPulish(path.Length>0,doc);
doc.Save(configfilepath);
}

/// <summary>
/// 设置是否通过Exception Managment Application Block发布日志到Event log
/// </summary>
/// <param name="enable"></param>
private void SetEMABLogPulish(bool enable,XmlDocument doc){
XmlNode node=doc.SelectSingleNode("configuration/exceptionManagement");
if(enable)
node.Attributes["mode"].Value="on";
else
node.Attributes["mode"].Value="off";
}

/// <summary>
/// 配置文件路径变更,包括Appstart的配置
/// </summary>
/// <param name="Appname"></param>
/// <param name="newPath"></param>
public void ChangePath(string Appname,string newPath){
try
{
//更改应用程序配置信息
ChangeAppConfigPathOnly(Appname,newPath);
//更改Appstarter启动目录信息
string basedir=GetPathFullName(newPath);
AppStartConfig starterconfig=new AppStartConfig(basedir+"AppStart.exe.config");
starterconfig.ChangeAppStartPath(basedir+"1.0.0.0");
}
catch(Exception ex){
throw new ConfigException("更改配置文件时产生错误!",ex);
}
}
/// <summary>
/// 改变应用程序配置文件路径
/// </summary>
/// <param name="Appname"></param>
/// <param name="newPath"></param>
public void ChangeAppConfigPathOnly(string Appname,string newPath){
try
{
//更改应用程序配置信息
ChangeApplicationPath(Appname,newPath);
ChangelogListener(newPath);
xmlconfig.Save(configfilepath);
}
catch(Exception ex)
{
throw new ConfigException("更改配置文件时产生错误!",ex);
}
}
public int UpdateInterval{
get{return GetUpdateInterval();}
set{SetUpdateInterval(value);}
}
/// <summary>
/// 设置更新间隔
/// </summary>
/// <param name="interval"></param>
private void SetUpdateInterval(int interval){
XmlNode node=GetPollingNode();
node.Attributes["type"].Value="Seconds";
node.Attributes["value"].Value=interval.ToString();
xmlconfig.Save(configfilepath);

}
/// <summary>
/// 获取更新间隔
/// </summary>
/// <returns></returns>
private int GetUpdateInterval(){
XmlNode node=GetPollingNode();
int ival=Convert.ToInt32(node.Attributes["value"].Value);
string strunit=node.Attributes["type"].Value;
return ToSeconds(ival,strunit);
}
private XmlNode GetPollingNode(){
XmlNodeList node=GetXmlNode("polling");
if(node.Count>0)
{
return node[0];
}
else
throw new ConfigException("未找到Polling节点,请确认配置文件!");
}
public int ToSeconds(int ivalue,string unit){
switch(unit.ToUpper()){
case "SECONDS":
return ivalue;
case "MINUTES":
return ivalue*60;
case "HOURS":
return ivalue*3600;
default:
throw new ConfigException("请使用小的单位(秒/分钟/小时)");
}
}
/// <summary>
/// 更改应用程序节点路径
/// </summary>
/// <param name="newpath"></param>
private void ChangeApplicationPath(string appname,string newpath){
XmlNodeList node=GetXmlNode("application");
if(node.Count>0)
{
node[0].Attributes["name"].InnerText=appname;
XmlNode nodetmp=node[0].SelectSingleNode(@"descendant::baseDir");
ChangeXmlNodeInnerText(nodetmp,newpath);
nodetmp=node[0].SelectSingleNode(@"descendant::xmlFile");
ChangeXmlNodeInnerText(nodetmp,GetPathFullName(newpath)+Path.GetFileName(nodetmp.InnerText));
nodetmp=node[0].SelectSingleNode(@"descendant::tempDir");
nodetmp.InnerText=GetPathFullName(newpath)+"DownloadFiles";
nodetmp=node[0].SelectSingleNode(@"descendant::xmlFileDest");
ChangeXmlNodeInnerText(nodetmp,GetPathFullName(newpath)+Path.GetFileName(nodetmp.InnerText));
}
}

private void ChangeXmlNodeInnerText(XmlNode node,string innertext){
node.InnerText=innertext;
}

private void ChangelogListener(string newpath){
XmlNodeList node=GetXmlNode("logListener");
if(node.Count>0){
node[0].Attributes["logPath"].InnerText=GetPathFullName(newpath)+Path.GetFileName(node[0].Attributes["logPath"].InnerText);
}
}
private string GetPathFullName(string path){
if(path.Length>0)
if(path.Substring(path.Length-1,1)!=@"\")
return path+@"\";
return path;
}
private XmlNodeList GetXmlNode(string nodename){
return xmlconfig.GetElementsByTagName(nodename);
}
public void Dispose(){
xmlconfig=null;
}
}
public class ConfigException: ApplicationException
{
public ConfigException():base(){}
public ConfigException(string message):base(message){}
public ConfigException(string message, Exception innerException):base(message,innerException){}
}
}

Step #5在主项目中(YourApp)中添加一个类文件,我们给其起名为SelfUpdater.cs(注意哦,是类文件,即你要更新的项目中)
该类的主要作用:进行在线更新的相关操作。通过InitUpdater()方法对Updater进行初始化,其中还包括实例化ApplicationUpdateManager,更新过程中相应触发的事件UpdaterAvailable和FilesValidated。

主要代码如下:
using System;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using Config;
using Microsoft.ApplicationBlocks.ApplicationUpdater;
namespace houselease
{
public delegate void UpdaterCallBackHandle(object sender, UpdaterArgs args);
public class UpdaterArgs : EventArgs
{
public UpdaterArgs()
{
}
public UpdaterArgs(bool isrunning)
{
IsRunning = isrunning;
}
public bool IsRunning;
}

/// <summary>
/// SelfUpdater 的摘要说明。
/// </summary>
public class SelfUpdater : IDisposable
{
ApplicationUpdateManager updater;
public UpdaterCallBackHandle UpdaterStateCallBack;
// private Thread _updaterThread = null;
public IWin32Window MsgBoxOwner;
public SelfUpdater()
{
InitUpdater();
}
/// <summary>
/// 初始化Updater
/// </summary>
private void InitUpdater()
{
updater = new ApplicationUpdateManager();
updater.UpdateAvailable += new UpdaterActionEventHandler(updater_UpdateAvailable);
updater.FilesValidated += new UpdaterActionEventHandler(updater_FilesValidated);
}
/// <summary>
/// 开始更新
/// </summary>
public void StartUpdate()
{
updater.StartUpdater();

}
/// <summary>
/// 重新开始更新
/// </summary>
public void ReStartUpdate()
{
updater = null;
InitUpdater();
updater.StartUpdater();
}
/// <summary>
/// 设置Polling间隔
/// </summary>
public int Interval
{
set
{
SavePollingIntoConfigFile(value);
}
get
{
return GetPollingInterval();
}
}
/// <summary>
/// 保存更新间隔,单位(秒)
/// </summary>
/// <param name="interval"></param>
private void SavePollingIntoConfigFile(int interval)
{
Config.ConfigFile configfile = new Config.ConfigFile(System.Reflection.Assembly.GetExecutingAssembly().Location + ".config");
configfile.UpdateInterval = interval;
UpdaterConfiguration.Instance.Polling.Value = interval.ToString();
UpdaterConfiguration.Instance.Polling.Type = PollingType.Seconds;

}
/// <summary>
/// 日志文件,如果为空则取消日志
/// </summary>
public string LogFile
{
set
{
SetLogFile(value);
}
get
{
return GetLogFile();
}
}
/// <summary>
/// 设置日志文件
/// </summary>
/// <param name="logfile"></param>
private void SetLogFile(string logfile)
{
Config.ConfigFile configfile = new Config.ConfigFile(System.Reflection.Assembly.GetExecutingAssembly().Location + ".config");
configfile.SetLogPath(logfile);
if (logfile.Length > 0)
UpdaterConfiguration.Instance.Logging.LogPath = logfile;
else
UpdaterConfiguration.Instance.Logging.LogPath = "";
}
private string GetLogFile()
{
return UpdaterConfiguration.Instance.Logging.LogPath;
}
/// <summary>
/// 获取更新间隔,单位(秒)
/// </summary>
/// <returns></returns>
private int GetPollingInterval()
{
Config.ConfigFile configfile = new Config.ConfigFile(System.Reflection.Assembly.GetExecutingAssembly().Location + ".config");
return configfile.UpdateInterval;

}
/// <summary>
/// 停止更新
/// </summary>
public void StopUpdate()
{
updater.StopUpdater(UpdaterConfiguration.Instance.Applications[0].Name);
}
/// <summary>
/// 实现IDisposable.Dispose接口方法,释放资源
/// </summary>
public void Dispose()
{
if (null != updater)
{
StopUpdate();
updater = null;
}
}
/// <summary>
///当判定存在更新文件时相应的事件方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void updater_UpdateAvailable(object sender, UpdaterActionEventArgs e)
{
// Show a message allowing the user to determine if they would like to download the update
// and perform the upgrade
string message = String.Format(
"升级提示: 现在服务器上的最新版本是 {0} , 需要更新吗?",
e.ServerInformation.AvailableVersion);
DialogResult dialog;
if (MsgBoxOwner != null)
dialog = MessageBox.Show(MsgBoxOwner, message, "升级提示", MessageBoxButtons.YesNo);
else
dialog = MessageBox.Show(message, "升级提示", MessageBoxButtons.YesNo);
UpdaterArgs args = new UpdaterArgs();

// The user has indicated they don't want to upgrade
if (DialogResult.No == dialog)
{
// stop the updater for this app
updater.StopUpdater(e.ApplicationName);
args.IsRunning = false;
}
else
{
args.IsRunning = true;
}
if (UpdaterStateCallBack != null)
UpdaterStateCallBack(this, args);
}

/// <summary>
/// 当更新文件下载完毕时相应的事件方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void updater_FilesValidated(object sender, UpdaterActionEventArgs e)
{
// Load this applications configuration file to read the
// UAB information and obtain the basedir
XmlDocument doc = new XmlDocument();
doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);

//更改新版本下的配置信息(YourApp.exe.config中的相应路径位置)
string baseDir = doc.SelectSingleNode(
"configuration/appUpdater/UpdaterConfiguration/application/client/baseDir"
).InnerText;
string myDir = Path.Combine(baseDir, e.ServerInformation.AvailableVersion);
DirectoryInfo dir = new DirectoryInfo(myDir);
Config.ConfigFile config = new Config.ConfigFile(Path.Combine(myDir, @"houselease.exe.config"));
config.ChangeAppConfigPathOnly("houselease", dir.Parent.FullName);

// Ask user if they want to use the new version of the application
DialogResult dialog = MessageBox.Show("您要立即停用当前应用程序并运行新版本程序吗?", "运行新版本程序?", MessageBoxButtons.YesNo);

if (DialogResult.Yes == dialog)
{
// Figure out the path to AppStart.exe which we will chain to
string newDir = Path.Combine(baseDir, "AppStart.exe");

// Launch AppStart.exe which will launch the new version
ProcessStartInfo process = new ProcessStartInfo(newDir);
process.WorkingDirectory = Path.Combine(newDir,
e.ServerInformation.AvailableVersion);
Process.Start(process);

StopUpdate();
Environment.Exit(0);
}
}
}
}

贴出我项目中的调用代码(小小牺牲一些私有代码,真希望老板不会发现)
  using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace houselease
{
public partial class frmBaseForm : Form
{
private SelfUpdater updater;
public frmBaseForm()
{
InitializeComponent();
//Init update obeject
updater = new SelfUpdater();
updater.MsgBoxOwner = this;
updater.UpdaterStateCallBack = new UpdaterCallBackHandle(UpdateMenuState);
}
/// <summary>
/// 重新启动更新
/// </summary>
private void ReStartUpdate()
{
updater.ReStartUpdate();
}
private void UpdateMenuState(object sender, UpdaterArgs args)
{
if (args.IsRunning)
t_updateonline.Text = "停止更新";
else
t_updateonline.Text = "开始更新";
}
/// <summary>
/// 停止更新
/// </summary>
private void StopUpdater()
{
if (null != updater)
{
updater.StopUpdate();
}
}

private void frmBaseForm_Load(object sender, EventArgs e)
{
updater.StartUpdate(); //开始更新
@default newfrm = new @default();
newfrm.MdiParent = this;
newfrm.Show();
}

private void t_listhouse_Click(object sender, EventArgs e)
{
house.list_house newfrm = new houselease.house.list_house();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_addhouse_Click(object sender, EventArgs e)
{
house.add_house newfrm = new houselease.house.add_house();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_listroom_Click(object sender, EventArgs e)
{
room.list_room newfrm = new room.list_room();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_addroom_Click(object sender, EventArgs e)
{
room.add_room newfrm = new room.add_room();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_listlease_Click(object sender, EventArgs e)
{
lease.list_lease newfrm = new lease.list_lease();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_addlease_Click(object sender, EventArgs e)
{
lease.select_room newfrm = new lease.select_room();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_querylease_Click(object sender, EventArgs e)
{
lease.query_list newfrm = new lease.query_list();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_staycard_Click(object sender, EventArgs e)
{
lease.stay_card_expire_list newfrm = new lease.stay_card_expire_list();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_hadfee_Click(object sender, EventArgs e)
{
fee.list_fee newfrm = new fee.list_fee();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_generatefee_Click(object sender, EventArgs e)
{
fee.generate_fee newfrm = new fee.generate_fee();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_notfee_Click(object sender, EventArgs e)
{
fee.list_not_fee newfrm = new fee.list_not_fee();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_queryfee_Click(object sender, EventArgs e)
{
fee.query_fee newfrm = new fee.query_fee();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_accountrp_Click(object sender, EventArgs e)
{
totalreport.account_report newfrm = new totalreport.account_report();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_updateinfo_Click(object sender, EventArgs e)
{
userinfo.update_userinfo newfrm = new userinfo.update_userinfo();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_updatepwd_Click(object sender, EventArgs e)
{
userinfo.update_pwd newfrm = new userinfo.update_pwd();
newfrm.MdiParent = this;
if (!object.Equals(this.MdiChildren, null))
{
foreach (Form frm in this.MdiChildren)
{
if (frm != newfrm)
{
frm.Close();
}
}
}
newfrm.Show();
}

private void t_updateonline_Click(object sender, EventArgs e)
{
try
{
if (sender.ToString() == "停止更新")
{
StopUpdater();
t_updateonline.Text = "在线更新";

}
else
{

updater.ReStartUpdate();
t_updateonline.Text = "停止更新";
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//pupdate newfrm = new pupdate();
//newfrm.MdiParent = this;
//if (!object.Equals(this.MdiChildren, null))
//{
// foreach (Form frm in this.MdiChildren)
// {
// if (frm != newfrm)
// {
// frm.Close();
// }
// }
//}
//newfrm.Show();
}

private void i_exitsystem_Click(object sender, EventArgs e)
{
StopUpdater();
Application.Exit();
//Environment.Exit();
}

}
}

Step #6 在当前解决方法中添加一个空项目(类库)取名为SetupProcessor,然后在该项目中添加一组件类。
该项目的主要作用:重写项目的Install方法,实现安装过程中,根据当前软件的安装路径修改相应的配置文件信息,包括:AppStart.exe.config,YourApp..exe.config。(注:该项目的输出文件在打包即安装布署时一定要添加到自定义的安装项中,否则您以后安装软件将不能改变默认目录,否则不成功,切记啊,偶可是在此吃过大亏,花了几乎整整一天转来转去,精神上也严重受创哩,因为缺乏人指导,只能自己瞎琢磨,想有好惨就多惨……,本来想截些图上来配合一下将会更清楚些,可惜机器重装后找不到PS的安装包了,没有资源,只能将就一下了,实在对不起大众,不明白的可以联系我哦,^_^)

首先在该项目中要添加引用System.Configuration.Install

主要代码如下:
using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;

namespace SetupProcessor
{
/// <summary>
/// SetupControl 的摘要说明。
/// </summary>
[RunInstaller(true)]
public class SetupControl : System.Configuration.Install.Installer
{
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;

public SetupControl()
{
// 该调用是设计器所必需的。
InitializeComponent();

// TODO: 在 InitializeComponent 调用后添加任何初始化
}

/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public override void Install(IDictionary stateSaver)
{
base.Install (stateSaver);
string p=Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
DirectoryInfo dir=new DirectoryInfo(p);
Config.ConfigFile config = new Config.ConfigFile(Path.Combine(p, @"houselease.exe.config"));
config.ChangePath("houselease", dir.Parent.FullName);

}
#region 组件设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

Step #7 创建IIS 虚拟目录
  在你的Web服务器上(测试时使用工作机即可)生成一个目录来存放你的更新文件. 在这个目录中要放两样东西 1) Manifest.xml 文件(Step #8中生成的文件),包含最新版本的一些信息;2) 你的项目最新生成文件也即是你的新版本,我们新建一版本目录,把最新生成文件放在些处×(eg.文件夹1.0.0.0,以后还可以有1.0.0.1,1.0.0.2等,顺便保存一下旧的版本信息哦).
  举例,我们用这两个目录, C:\Inetpub\AppUpdates (虚拟根目录) 和C:\Inetpub\AppUpdates\1.0.0.1 (版本目录)。用 IIS 管理器生成一个虚拟目录指向刚才的实际目录(即C:\Inetpub\AppUpdates ). 记下你的 URL, 以后在线更新都是利用该URL进行文件下载的,(注意:此时一定要设置该目录的权限,至少是可读的,其它根据自己需求来定。

Step #8: 生成你的公钥和私钥以及配置文件Manifest.xml
(1)生成你的公钥(PublicKey.xml )和私钥(PrivateKey.xml)
   运行 "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公钥和私钥. 你需要把这些密钥存放在一个安全的地方,因为在你每发布一个新的更新版本时都要用到它。
(2)生成你的配置文件Manifest.xml
   同样运行 "C:\Program Files\Microsoft Application Blocks for .NET\Updater\Code\CS\Microsoft.ApplicationBlocks.Updater\ManifestUtility\bin\Debug\ManifestUtility.exe"
  在 “Update files folder“ 选择你刚才生成文件的目录,即项目的输出位置(例如:YourApp\bin\release\)
  输入更新位置的 URL(就是刚才配置的IIS虚拟目录的URL) .
输入版本号 1.0.0.0(此位置默认是2.0.0.0)
  还有一个复选框post processor可选可不选,这个主要功能是用来输出安装或更新等过程中的错误信息,具体信息请把鼠标停留在相应位置上面,会出现Tool Tip哦,真的哦,不是骗你的。
其它用默认即可,然后点击CreateManifest再保存就OK了。
  这个清单文件在整个更新过程非常重要。主要保存着你更新是需要下载的文件信息等。

Step #9 配置你的版本 1.0.0.0 的App.config 文件
  首先找找你的项目(YourApp)中有没有App.config 文件,如果没有,就从添加新建项中添加一个,你在选择模板时时,随便点点,如果哪个名字出来是App.config那就对了,如果是App1.config那就说明你的
项目中已经有App.config了,不用再添加了(嘿嘿,其实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>
<polling type="Seconds" value="120" />
<logListener logPath="C:\Program Files\YourApp\UpdaterLog.txt" />
<downloader type="Microsoft.ApplicationBlocks.ApplicationUpdater.Downloaders.BITSDownloader"
assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/>
<validator type="Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator" assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">
<key>
<RSAKeyValue>
<Modulus>[YOUR MODULUS KEY]</Modulus>
<Exponent>[YOUR EXPONENET]</Exponent>
</RSAKeyValue>
</key>
</validator>
<application name="[YOUR APP NAME]" useValidation="true">
<client>
<baseDir>C:\Program Files\YourApp</baseDir>
<xmlFile>C:\Program Files\YourApp\AppStart.exe.config</xmlFile>
<tempDir>C:\Program Files\YourApp\temp</tempDir>
</client>
<server>
<xmlFile>http://[YOUR URL]/ServerManifest.xml</xmlFile>
<xmlFileDest>C:\Program Files\YourApp\ServerManifest.xml</xmlFileDest>
<maxWaitXmlFile>60000</maxWaitXmlFile>
</server>
</application>
</UpdaterConfiguration>
</appUpdater>

Step #10 构建版本 1.0.0.1 生成新版本清单文件(ServerManifest.xml )



  这是最关键的一部分,没设置好,更新是不能顺利进行的。
  首先, 通过更新应用程序的 AssemblyInfo.cs 和 App.config 文件内容来生成修订版本 1.0.0.1, 编译程序, 然后到step #7生成的虚拟目录,我们建一个新的版本文件夹1.0.0.1,同时把文件考到该文件夹下面。
  然后,这个是最后一步(注意:在生成新版本过程中只要对.config文件作了任何修改的话,都必须把下面步骤重来一遍, 做法如下:
  再次运行 ManifestUtility 程序.
(1)update files folder“ 选择所建IIS的绝对路径下的1.0.0.1文件夹
(2)更新位置的 URL :step #7生成的虚拟目录+文件夹1.0.0.1
(3)打开之前生成的 PrivateKey.xml 文件.
(4)选择验证类 “Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”
其它请参考Step #8.2
鼠标点击 CreateManifest, 并保存Manifest.xml 文件到你的虚拟服务器目录的根目录下。

   OK,差不多就这些了,如果更新时出现问题,可以查看安装目录下的日志信息,可以从中分析一些东东出来,再研究一下对策也就差不多了
  剩下的就是布署了,将在另外一篇文章中,如有需要再阅读之。

  在研究的过程中主要是参考有:
  昆明小虫 “使用Updater Application Block实现自动更新特性”http://ynlxc.cnblogs.com/archive/2005/10/31/265252.html(有实例Demo下载哦)
  “用Application Updater Block生成一个自我更新的WinForms 应用http://www.haoad.net/Info/5884.Html
  
  ………………
  
  在此深表感谢啊!

PS:上面的代码没意外的话很多都可以直接用了,也可以根据自己的需要来改的哦
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: