说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。
XAML浏览器应用程序一般简写为XBAP(XAML Browser Application),谈到XBAP一定会想到这东西与Silverlight的异同。主要的区别是XBAP属于WPF程序一种,运行于相应版本.NET Framework之上,也就是说虽然表面上看起来XBAP运行于浏览器中(而且当前要求IE为核心的浏览器),但要求系统安装有相应版本的.NET Framework,否则XBAP无法运行。而Silverlight程序运行于安装有Silverlight插件的浏览器中,是否可以运行由所运行浏览器是否支持Silverlight插件(包括了Silverlight框架与运行时)决定。
那XBAP与普通的WPF应用程序又有什么区别呢?最主要的XBAP并不支持WPF与.NET Framework中全部的特性。XBAP中导航被集成到IE7以上版本的浏览器中。XBAP程序有着不同的部署方式。综上可看出XBAP是为充分发挥WPF功能的同时获得浏览器集成体验设计的一种程序。
创建一个XBAP程序也很简单。在Visual Studio中提供了相应的模版。XBAP的程序的运行方式与传统WPF程序比也有很大不同。在项目构成上来看,Visual Studio不会为XBAP程序产生专用的源文件,只是项目文件中有一些不同的设置:
False true Internet
这样项目运行时会启动PresentationHost.exe,在找到可宿主的浏览器后,将生成EXE文件,否则程序会退出。同时还生成两份xml格式的清单文件:一个清单文件用来记录ClickOnce应用程序的文件,另一个扩展名为.xbap的文件是ClickOnce部署清单文件。
功能限制
虽然一个简单的WPF Windows应用程序项目通过更改一些项目设置,重新编译可以成为一个XBAP程序,但可能并不是全部功能在新程序中都可用。XBAP相较于普通WPF程序还是有很多限制,主要还是来自安全方面的问题,由于XBAP运行在部分信任的Internet区域中,所以不是所有的API都有权限调用。如访问本地文件系统的API:
Environment.GetFolderPath(Enviroment.SpecialFolder.MyPictures);
由于这行代码需要FileIOPermission,而在Internet区域默认这是不被授予的,.NET Framework的CAS机制会阻止这个调用,并抛出异常。(虽然可以编码提示用户来请求被授予权限,但让用户决定是否信任一个应用程序也是不被推荐的做法)。下面列出一些Internet区域无权与有权进行的操作
无权进行:
-
访问本地文件系统和注册表
-
与非托管代码进行交互
-
启动新窗口(XBAP的Popup元素只会位于Page范围内)
-
无法使用位图效果
-
无法使用WCF与Web服务器通信
有权进行:
-
显示富文字和媒体文件
-
访问IsolatedStorage进行读写操作(最大512K)
-
打开Web服务器上的任意文件
-
使用浏览器打开文件对话框与本地文件交互(需要用户显式授权,打开对话框可使用Microsoft.Win32.OpenFileDialog,代码段示例)
OpenFileDialog ofd = newOpenFileDialog(); if (ofd.ShowDialog() == true) { using (Stream s = ofd.OpenFile()) { using (StreamReader sr = newStreamReader(s)) { //Get the file content sr.ReadToEnd(); } } }
如果需要在WPF Windows应用程序(完全受信)与XBAP程序(部分受信)间共享代码,可以使用System.Windows.Interop命名空间下BrowserInteropHelper.IsBrowserHosted属性来检测当前应用程序是否运行与浏览器中,从而决定该使用针对哪种类型程序(哪种信任级别)的代码。
下面继续讨论一些关于安全方面的问题。首先是组件调用问题,以下所述对所有.NET组件都是适用的。默认情况下安装在GAC中的程序只能被完全受信的程序调用,而像XBAP这样的部分受信程序是无法调用的。解决方法是把这个放入GAC的程序集标记为AllowPartiallyTrustedCallers,这样非受信程序也可以调用这个程序集(对于XBAP,仍然需要显式申请用户的授权来访问这个程序集)。但一定要谨慎使用AllowPartiallyTrustedCallers,由于允许部分受信程序调用,可能会使这个被标记的程序集称为安全隐患。
如果要在XBAP程序中使用完全受信环境(更高信任级别)的功能,可以将XBAP部署为完全受信。那怎样创建一个完全受信的XBAP程序呢?有两种方式:
-
在ClickOnce部署清单文件(app.manifest)中,在PermissionSet元素中添加Unrestricted="true"。这等效于在Visual Studio项目属性页安全选项卡设置如下选项:
-
将项目文件(.csproj或vbproj)中
Internet 改为Custom 。对应Visual Studio就是更改如下选项:
完成上述修改后,对于这个XBAP程序,在本地计算机区域运行,或者用户将作者列为受信认发行人,或者用证书对ClickOnce部署清单文件进行了签名这几种情况下将被作为完全受信程序来运行,即使XBAP被发布在互联网中。
XBAP程序的导航与传入参数
XBAP程序中,所有页面被隐式放入NavigationWindow中。当使用IE6时,NavigationWindow中的导航工具条会显示出来。大部分情况下这可能是不需要的。将页面的ShowNavigationUI属性设为false可以隐藏此工具条。
在IE7及以后版本,NavigationWindow的导航日志与浏览器的日志合并在一起,单独的导航工具条默认不在出现,使用浏览器的前进后退按钮就可以访问WPF页面的日志记录进行导航。(注意:这个集成导航功能只对直接宿主于浏览器的第一级WPF页面有效,如果有一个WPF页面宿主于位于浏览器中的Html页面中的IFrame,那么导航条仍然会出现在WPF页面上)
对于向XBAP应用程序传入数据,主要有以下两种方式:较简单的方式是向宿主于XBAP的Html页面的URL附加参数,然后在XBAP中调用BrowserInteropHelper.Source来获取包含参数在内的完整Url。另一种方法是把信息作为cookie存在浏览器中,然后在XBAP中通过Application.GetCookie方法得到cookies。
部署
ClickOnce是XBAP的主要部署方式(所以ClickOnce部署清单文件app.manifest默认被继承在项目中),与传统使用ClickOnce部署的WPF程序不同在于在访问程序发布的位置后,相应的应用程序在浏览器中运行。另外XBAP使用ClickOnce发布时,默认且只能选择程序联机使用这项。而通过ClickOnce安装普通WPF程序,既可以选择程序联机使用,也可以选择将程序安装在本地计算机中。完成ClickOnce主要是通过Visual Studio发布向导(位于生成菜单),而发布设置通过项目属性页的发布选项卡完成(Windows SDK中Mage也可完成发布操作)。使用ClickOnce发布的一个很大的好处在于,如果程序不需要特殊权限,从访问Url来安装并运行XBAP的全过程中不会弹出任何安全提示,从而增强用户体验。另外.NET的安全机制能确保用户计算机不会受到恶意XBAP的破坏。
使用ClickOnce部署有一个需要注意的问题 - 缓存。ClickOnce在第一次运行时会被缓存到本地目录中(类似于IsolatedStorage的存储目录,ClickOnce缓存目录也是位于用户Documents目录下的一个隐藏目录),以后再通过ClickOnce运行同一程序时,除非版本号发生变化,否则将会由缓存中来获取数据。庆幸的是,Visual Studio在项目属性页发布选项卡提供了一个选项,可以在每次发布时自增编码与AssemblyInfo.cs中的版本号,且这个选项是默认的。如果不使用自增版本而是用固定的版本号,可以使用如下方法清空缓存。
-
方法1:如过安装有Windows SDK,使用其中的mage.exe并以-cc作为参数即可随时清除缓存。
-
方法2:对于没有安装Windows SDK用户,可以使用这条命令
rundll32 %windir%\system32\dfshim.dll CleanOnlineAppCache。
最后介绍一个ClickOnce提供的强大且实用的功能 – 按需下载。对于特别大的XBAP程序,一次性有网络加载速度可能会比较慢,而按需加载就是为了解决这个问题。当然ClickOnce的按需加载也可以用于其它类型程序的部署。实现方式是通过项目属性页面发布选项卡,其中有一个应用程序文件按钮,打开这个对话框,可以将需要分批按需加载的文件设置在一个文件组中,过后通过代码实现按需加载。实现按需加载这要通过System.Deployment.Application命名空间(System.Deployment.dll中)下相关类。下面的示例代码模拟一个这样的场景:
应用程序启动时运行Page1,Page1加载后,其后置代码开始下载位于"MyGroup"组中的Page2需要使用的文件,当下载完毕后程序会给出提示,而我们可以直接由Page1导航到Page2。这样假设程序中还有类似与Page2的Page3,Page4等,而它们的功能相对独立,我们就可以采用这种按需加载的方式,从而避免一次性加载过多速度过慢的问题。
using System; using System.Windows.Controls; using System.Windows.Threading; using System.Deployment.Application; publicpartialclassPage1 : Page { public Page1() { InitializeComponent(); } protectedoverridevoid OnInitialized(EventArgs e) { base.OnInitialized(e); if (ApplicationDeployment.IsNetworkDeployed) { // 处理文件组下载完毕事件 ApplicationDeployment.CurrentDeployment.DownloadFileGroupCompleted += delegate { // 下载过程在其它线程进行,所以应该在UI线程触发GotoPage2 Dispatcher.BeginInvoke(DispatcherPriority.Send,newDispatcherOperationCallback(GotoPage2), null); }; // 开始异步下载文件组 ApplicationDeployment.CurrentDeployment.DownloadFileGroupAsync("MyGroup"); } else { // 不是运行在网络环境中,不需要按需下载 GotoPage2(null); } } // 当下载完成后导航到Page2 // 此函数需匹配DispatcherOperationCallback委托签名 privateobject GotoPage2(object o) { return NavigationService.Navigate(newUri("Page2.xaml", UriKind.Relative)); } }
注意,按需下载只在运行于网络环境中的程序(程序部署为在线运行)有效,对于本地调试,或程序部署在本地等情况,代码会直接转向Page2。上面代码中粗体部分对是否运行于网络进行了检测。另外给下载完成事件定阅处理函数与开始执行异步下载两句代码的顺序是有先后的,不能弄错。
松散XAML页
这部分内容本身与XBAP没有关系。但同样作为XAML,同样可以再浏览器中运行,我们将它们放在一起介绍一下。当然要让浏览器可以导航到松散XAML,同样需要系统安装有3.0以上版本的.NET Framework。其实与松散XAML更具可比性的是html,相比而言,松散XAML的运行环境要求更高,但有更好的布局,文字,图形的支持,且虽然不能在XMAL使用过程式代码,通过数据绑定仍然可以实现强大的界面功能。
例如,可以在Web服务器上提供2个版本的页面,分别使用XAML与Html实现,检查用户agent,如果包含".NET CLR 3.0"字样,则将用户导航到XAML文件。否则导航到html版本。另外通过在Html中Iframe中宿主一个或多个.xaml文件,可以将这内容混合使用。