Ø 使用Updater Application Block实现自动更新特性
由于有了Smart Client技术,我们可以很好的将胖客户端和瘦客户端应用的优点结合在一起,适应医院使用实际情况的需要。Smart Client可以自动灵活地进行升级和更新从而简化了系统的维护;Smart Client可以充分的利用本地的计算资源,可以将HIS处理的载荷合理的分配给系统中的每一台计算机,提高了系统的响应和性能。
微软公司提供的Microsoft Application Block为开发具有智能更新功能的.NET应用提供了极大的便利。在HIS Demo中我们重用并扩展了Updater Application Block (UAB)等应用程序模块,实现了符合HIS应用实际需求的自动更新等功能。
使用UAB可以实现对.NET应用智能更新支持,UAB为应用提供了下载,验证和后置处理机制。通过UAB提供的接口,我们可以轻易对UAB根据自己需要进行扩展。在HIS Demo中,我们使用BITS下载机制,保证系统的运作效率。UAB的工作流程如下图所示:
UAB主要有四个模块组成Updater, Downloader, Validate以及Post Processor组成。Updater负责整个更新工作的管理;Downloader实现文件的下载,UAB中采用BITS (Background Intelligence Transfer Service)作为Downloader,UAB提供了IDownloader 接口,实现这个接口,开发者可以开发基于任意协议的下载器;Validator完成对下载文件的校验,UAB中提供KeyValidator和RSAValidator两个类,分别来实现对对称和非对称加密文件的校验;Post Processor则提供完成文件更新后需要进行的各种操作。
Q 如何在应用中实现基本的自动更新功能?
HIS Demo中将自动更新的主要功能封装在SelfUpdater类中(源文件HISDEMO \HISCLINIC \SelfUpdater.cs)
在SelfUpdater类中通过InitUpdater()方法对Updater进行初始化,主要工作包括实例化ApplicationUpdateManager,为更新过程中主要的事件UpdaterAvailable和FilesValidated等事件指定事件处理器
/***********************************************************
private void InitUpdater()
{
updater=new ApplicationUpdateManager();
updater.UpdateAvailable += new UpdaterActionEventHandler(updater_UpdateAvailable);
updater.FilesValidated += new UpdaterActionEventHandler(updater_FilesValidated);
}
/***********************************************************
UpdaterAvailable事件当UAB发现服务器上有新的应用版本时发生,这时通常会询问用户是否要需要更新,如果用户选择需要则开始下载
/***********************************************************
/// <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);
}
/***********************************************************
FilesValidated事件当更新文件成功下载并通过校验后发生,这时时通常会询问用户是否要需要运新更新后的程序,如果用户选择需要是则需要停止当前应用并启动更新后的应用
/***********************************************************
/// <summary>
/// 当更新文件下载完毕时相应的事件方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void updater_FilesValidated(object sender, UpdaterActionEventArgs e)
{
// Ask user if they want to use the new version of the application
DialogResult dialog = MessageBox.Show(
"需要打开新的应用程序吗?",
"打开新版本?", MessageBoxButtons.YesNo );
if( DialogResult.Yes == dialog )
{
// Load this applications configuration file to read the
// UAB information and obtain the basedir
XmlDocument doc = new XmlDocument();
doc.Load( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile );
string baseDir = doc.SelectSingleNode(
"configuration/appUpdater/UpdaterConfiguration/application/client/baseDir"
).InnerText;
// 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 );
}
}
/**********************************************************
使用自动更新是在主表单中实例化SelfUpdater类即可。
/***********************************************************
public frmMainForm()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//Init update obeject
updater=new SelfUpdater();
……
}
/***********************************************************
Q 如何产生和配置清单文件(Manifest.xml)?
清单文件在整个更新过程非常重要。在更新的过程中这个清单会被Downloader从服务器上下载到本地,由Updater进行解析并判断是否有更新。如果有更新,Downloader会根据清单的内容,从服务器上下载相应的文件,Validator会根据清单中的签名对清单进行校验。下面是清单文件的例子,可以看出清单中的信息包括清单本身的签名,可用的应用版本号和位置,文件列表和签名,后处理器的名称等。
注意:如果采用了RSA加密签名,则不可以手动改变此清单。此清单应由相应工具产生并用私有密钥进行签名。
<ServerApplicationInfo signature="hPPVuZQAo95scW8vozlwho0Qny+eEdfbI4vqBbYyqfKaTYnigY5k83XHvkVyrPlLFGcUqIezGiKWkFq7X49ZryiIWyzWi3LEnltTy3xGBTAMvTekRfik0VFjiHVyQ9Fwz5DTKxhRWqmfJqAHiEM3Bj2JGJxTNvynbRFHjw0+RWM=">
<availableVersion>1.0.0.0</availableVersion>
<updateLocation>http://localhost/HISClinicWebDeploy/1.0.0.0/</updateLocation>
<files>
<file name="BLL.dll" signature="eAfVF1W3+k7CWBP67EJddtJd8b02ZE3N3R02QRdGw1VO5iluUEK22aCydC9kxOwEYPo4CWmxW4QMalSSSdv4h4TkPU1QGWB9i7Cs72pmWxMaKBN19WkFIgQr588lf+cCqRCBOdn7NVapXCwK6hLARcEhpWG/EeN3tAf/zSfRr8A=" />
<file name="HISClinic.exe" signature="ojYnuA9kby/hlJOtLlsDglQT30Qn1/i1X3dvZMRjn+fZ0Tr0LjmBISOR0GgHxlHEexOzkyhjgJy3diVbhCbt4ucnxaN+B/ei2oBfW6Xt0wx25ywuGpo9tLuzwaUj9b9eAQ02Sj9eHY2YvKo0pSrOye9yoCYdl/fZhDj2WKhPcs4=" />
<file name="HISClinic.exe.config" signature="aP0sXIqEC0FeDUHzPQ3EiregL9r8Y5CdtMP0es1rEW0Cx++iPPAJK8t1HBdE+FOGmQ2URwgUFZNzlm8sTw5yIM+mwSdo/MD8irICv2H/v980JbwcUY4vaPdl5+14PJPXRK+XVOk6s4tigw44qHbu8hu/geh7YvYrDVCfYOdbFIo=" />
<file name="Microsoft.ApplicationBlocks.ApplicationUpdater.dll" signature="evasZb0Vd4k4AHQgZbsxubVMEDQPgxSn9YznyuPzFV87gMyJNNMzYeaAfJhwQWBga1aWO/CfwF1UectXsgQ4N0kziK3Nk4NvJBZzP5dkxh5zHnTvMgPTeyES3knQoKbUvHSyzEfxKYP4PlmJQXtupBYDzUe72C/sEkQR+obq/EY=" />
.......
<file name="PostProcessor.dll" signature="KsPcHLW/RJW5ohs3RT4FCyALTqoUDBHknhSQguZ8l0A95746qb5bQbbYjYMuNkGsLJPomsh0fytpld7oXhtaTksaCelRRTOnVXNjXY1Ev7BXD8Ph1GHPWopdQrQqHYRGEorCjGWx2rCpLvpu+xgFcP+jLCllCqJrWhz7wMOvY68=" />
</files>
<postProcessor type="PostProcessor.Processor" assembly="PostProcessor, Version=1.0.1796.1348, Culture=neutral, PublicKeyToken=null" name="PostProcessor.dll" />
</ServerApplicationInfo>
清单可用采用UAB自带的工具ManifestUtility.exe产生,如下图所示:
Q 如何产生和配置App.Config文件?
App.Config文件决定了在客户运行的应用版本和位置,其内容如下。其中最关键的appFolderName节点,此路径应为程序的安装路径,此文件应该在客户端初次安装时生成并置于应用的根目录下。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="appStart" type="Microsoft.ApplicationBlocks.ApplicationUpdater.AppStart.ConfigSectionHandler,AppStart" />
</configSections>
<appStart>
<ClientApplicationInfo>
<appFolderName>D:\PROGRA~1\Winarray\HISDemo\HISClient\2.0.0.0</appFolderName>
<appExeName>HISClinic.exe</appExeName>
<installedVersion>2.0.0.0</installedVersion>
<lastUpdated>2004-12-01T01:05:01.6490969+08:00</lastUpdated>
</ClientApplicationInfo>
</appStart>
</configuration>
Q 如何产生和配置HISDemo.exe.config文件?
HISDemo.exe.config文件主要说明了应用使用UAB的情况和配置,对于使用RSA加密签名的文件,公钥存于此文件中。另外,在本应用中Sql的连接字符串也存于此文件中。如Sql服务器配置有改动,只需要改写此文件中的相应信息即可。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="appUpdater" type="Microsoft.ApplicationBlocks.ApplicationUpdater.UpdaterSectionHandler,Microsoft.ApplicationBlocks.ApplicationUpdater" />
<section name="exceptionManagement" type="Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManagerSectionHandler,Microsoft.ApplicationBlocks.ExceptionManagement" />
</configSections>
<appUpdater>
<UpdaterConfiguration>
<polling type="Seconds" value="60" />
<logListener logPath="D:\PROGRA~1\Winarray\HISDemo\HISClient\UpdaterLog.txt" />
<!-- **************** BITS DOWNLOADER **************** -->
<downloader type="Microsoft.ApplicationBlocks.ApplicationUpdater.Downloaders.BITSDownloader" assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" />
<!-- **************** THE RSA KEY HASHING VALIDATOR **************** -->
<validator type="Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator" assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">
<key>
<RSAKeyValue>
<Modulus>oslBz4MuFil9QKi3zkuqgsl8U7ppPQgCShDvb1D5dQTthlyo+dZ7wG7YEysxgm4P1ofr29muQIdjYRwhn7afvH1VgVahOLxVWsQm2rqNWbEZ9OV3UMFTf0P3erFiqxatDMiK65ayLmHXp0YM0zwxpm+ekQ8jm3Zg9ac0HePZsIk=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</key>
</validator>
<application name="HISClinic" useValidation="true">
<client>
<baseDir>D:\PROGRA~1\Winarray\HISDemo\HISClient</baseDir> <xmlFile>D:\PROGRA~1\Winarray\HISDemo\HISClient\AppStart.exe.config</xmlFile>
<tempDir>D:\PROGRA~1\Winarray\HISDemo\HISClient\DownloadFiles</tempDir>
</client>
<server>
<xmlFile>http://localhost/HISClinicWebDeploy/ServerManifest.xml</xmlFile> <xmlFileDest>D:\PROGRA~1\Winarray\HISDemo\HISClient\ServerManifest.xml</xmlFileDest>
<maxWaitXmlFile>60000</maxWaitXmlFile>
</server>
</application>
</UpdaterConfiguration>
</appUpdater>
</configuration>
<exceptionManagement mode="on" />
<appSettings>
<add key="DBConnectionString" value="uid=sa;pwd=;data source=(local);initial catalog=HisClinic" />
</appSettings>
Q 如何将安装路径写入配置文件?
为了能使UAB正常的工作,在配置文件HISClinic.exe.config中需要指明程序安装的路径。由于客户端可能安装在任意的路径下,为此在应用的文件安装或下载以后需要再将安装路径写入配置文件,确保应用的正常工作。
在应用程序的文件完成拷贝和下载后,需要运行如下代码,实现下列任务:通过Reflection.Assembly取得当前运行路径以及更改HISClinic.exe.config配置
参考代码: HISClinic\HISDemo\PostProcessor\Processor.cs
/***********************************************************
public class Processor:IPostProcessor
{
public Processor()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
/// <summary>
/// 实现IPostprocessor.Run接口方法
/// 更改下载程序配置信息
/// </summary>
public void Run()
{
String p=Path.GetDirectoryName
(System.Reflection.Assembly.GetExecutingAssembly().Location);
DirectoryInfo dir=new DirectoryInfo(p);
Config.ConfigFile config=new
Config.ConfigFile(Path.Combine(p,@"HISClinic.exe.config"));
config.ChangePath("HISClinic",dir.Parent.FullName);
}
public void Dispose(){}
}
/***********************************************************
Q 如何设定更新检测的时间?
应用程序如需要设定更新检测时间,可以通过设置HISClinic.exe.config的polling节点属性以及设定UAB的UpdaterConfiguration.Instance来实现
参考代码: HISClinic\HISDemo\HIClinic\SelfUpdater.CS;
HISClinic\HISDemo\ChangePath\Config.CS;
/***********************************************************
/// <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;
//修改UAB实例
UpdaterConfiguration.Instance.Polling.Value=interval.ToString();
UpdaterConfiguration.Instance.Polling.Type=PollingType.Seconds;
}
/// <summary>
/// 获取更新间隔,单位(秒)
/// </summary>
/// <returns></returns>
private int GetPollingInterval()
{
Config.ConfigFile configfile=new
Config.ConfigFile(System.Reflection.Assembly.GetExecutingAssembly().Loca
tion+".config");
return configfile.UpdateInterval;
}
/***********************************************************
Q 如何实现强制的手动即时更新?
应用程序如果需要通过界面控制实现强制的手动即时更新,可以通过以下方式实现。首先调用ApplicationUpdateManager.StopUpdater()停止更新,然后重新实例化ApplicationUpdateManager,并通过调用StartUpdater()实现更新
参考代码: HISClinic\HISDemo\HIClinic\selfUpdater.CS
/***********************************************************
/// <summary>
/// 重新开始更新
/// </summary>
public void ReStartUpdate()
{
updater=null;
InitUpdater();
updater.StartUpdater();
}
/***********************************************************
Q 如何设定更新日志路径以及设定是否需要保留更新日志?
默认情况下,UAB会将更新日志存放在应用安装的根目录下,并且每次运行运用都会产生一个新的日志。这种机制会在客户端留下大量的日志文件。如果需要设定更新日志路径以及设定是否需要保留更新日志,需要更改UAB配置文件和对应实例以及EMAB配置文件
参考代码: HISClinic\HISDemo\HIClinic\SelfUpdater.CS;
HISClinic\HISDemo\ChangePath\Config.CS;
/***********************************************************
/// <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)
{//设置UAB日志标志
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/UpdaterConfigurat
ion").AppendChild(node);
}
node.Attributes["logPath"].Value=path;
}
else
{//设置EMAB日志标志
XmlNode node=doc.SelectSingleNode
("configuration/appUpdater/UpdaterConfiguration/logListener");
if(node!=null)
node.ParentNode.RemoveChild(node);
}
SetEMABLogPulish(path.Length>0,doc);
doc.Save(configfilepath);
}
/***********************************************************
Q 如何实现使用任意协议(http, ftp…)进行下载?
UAB采用Windows自带的BITS服务进行文件的下载,如需要采用任意协议或是自定义的下载器进行下载,开发自定义的下载器,并实现IDownloader接口即可
如何删除客户端操作系统上不必要的较早版本的文件备份
默认情况下UAB会保留较早版本的文件备份,可以通过实现IPostprocessor接口,开发简单代码,在应用更新成功后,对不必要文件进行删除。
Q 如何在应用安装的同时自动的导入数据库?
将要安装的数据库文件.mdf拷贝到[数据库安装路径]通过OSQL命令调用如下的T-SQL 脚本。
EXECUTE sp_attach_db @dbname = N'<database_name, sysname, test_db>',
@filename1 = N'<filename1, nvarchar(260), [数据库安装路径]\*.mdf>',
@filename2 = N'<filename2, nvarchar(260), [数据库安装路径]\*.mdf >'
GO
文件的拷贝和OSQL命令的调用可以通过安装程序进行,从而实现数据库的自动安装。
下载例子