迁移桌面程序到MS Store(8)——通过APPX下载Win32Component
在上一篇《迁移桌面程序到MS Store(7)——APPX + Service》中,我们提到将desktop application拆分成UI Client+Service两部分。其中UI Client可以通过Desktop Bridge技术Pacakage成APPX,上传到MS Store以供下载,而Service则仍以传统的desktop application安装包形式提供。这样势必造成用户安装时的割裂感。本篇将就这个问题进行一些讨论。
首先我们参照上图的架构创建Sample Solution,其中包括充当UI的WPFClient,升级到.NET Standard的DownloadLib,以及打包用的UWPClientPackaging工程(请先暂时忽略UWPClient工程)。
WPFClient只是一个空的Window,仅仅在Window Load的时候,询问用户是否下载文件。
private async void MainWindow_Loaded(object sender, RoutedEventArgs e) { txtLog.AppendText($"Ask user to download file.\n"); var result = MessageBox.Show("We need to download file.", "Download", MessageBoxButton.YesNo); if (result == MessageBoxResult.Yes) { txtLog.AppendText($"Start downloading.\n"); this.progressBar.Visibility = Visibility.Visible; var downloader = new SimpleDownloader(); var stream = await downloader.RequestHttpContentAsync( ConfigurationManager.AppSettings["uri"], ConfigurationManager.AppSettings["username"], ConfigurationManager.AppSettings["password"]); this.progressBar.Visibility = Visibility.Collapsed; txtLog.AppendText($"Done.\n"); var path = SaveFile(stream); txtLog.AppendText($"File path is {path}.\n"); Process.Start(path); txtLog.AppendText($"Start process {path}.\n"); } }
这里需要注意的是,代码中的uri,username和password均写在配置文件App.config中,调试时记得填写真实的值。
<appSettings> <add key="uri" value=""/> <add key="username" value=""/> <add key="password" value=""/> </appSettings>
DownloadLib工程在这个例子中充当了Class Library的角色,考虑到会被WPFClient和UWPClient同时调用,DownloadLib的项目类型是.NET Standard。该工程的代码也很简单,通过传入的uri,username和password进行http请求下载文件,以Stream的形式返回结果。
public async Task<Stream> RequestHttpContentAsync(string uriString, string userName, string password) { using (HttpClient client = new HttpClient()) { client.BaseAddress = new Uri(uriString); client.DefaultRequestHeaders.Accept.Clear(); var authorization = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($"{userName}:{password}")); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorization); client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = await client.GetAsync(uriString); if (response.IsSuccessStatusCode) { HttpContent content = response.Content; var contentStream = await content.ReadAsStreamAsync(); return contentStream; } else { throw new FileNotFoundException(); } } }
假设我们这里下载的文件是一个.msi的安装文件,这个安装文件即是架构图中Service部分的安装包。在完成下载后,在WPFClient中将Stream保存成文件,然后通过Process.Start(path);运行,接下来就是.msi文件的安装流程了。在安装结束后,整个application就可以正常使用了。
private string SaveFile(Stream stream) { var filePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "installFile.msi"); using (var fileStream = new FileStream(filePath, FileMode.Create)) { byte[] buffer = new byte[2048]; int bytesRead; do { bytesRead = stream.Read(buffer, 0, 2048); fileStream.Write(buffer, 0, bytesRead); } while (bytesRead > 0); } return filePath; }
WPFClient最终是通过UWPClientPackaging工程打包成为APPX,实际对File和Process的操作都是标准的WPF代码。不存在权限的问题。当然如果当前用户没有admin权限,不被允许安装任何软件,这就超出了我们讨论的范围。
接下来我们来看纯UWPClient的情况。UWPClient工程同样添加了对DownloadLib的引用,界面也基本一致。稍有不同之处在于不能使用System.IO.File对象,而是要通过StorageFolder来保存文件。同样也不能够使用Process.Start()方法,而是通过Launcher对象来打开保存文件所在的文件夹。某软出于安全角度,不允许Launcher对象运行exe,msi等类型的可执行文件。所以只能打开文件夹让用户手动点击安装。
https://docs.microsoft.com/en-us/uwp/api/windows.system.launcher.launchfileasync
This API also imposes several restrictions on what types of files it can launch. Many file types that contain executable code, for example .exe, .msi, and .js files, are blocked from launching. This restriction protects users from potentially malicious files that could modify the system.
private async void MainPage_Loaded(object sender, RoutedEventArgs e) { txtLog.Text += $"Ask user to download file.\n"; var dialog = new MessageDialog("Do you want to download installation file?"); dialog.Commands.Add(new UICommand { Label = "Ok", Id = 0 }); dialog.Commands.Add(new UICommand { Label = "Cancel", Id = 1 }); var res = await dialog.ShowAsync(); if ((int)res.Id == 0) { txtLog.Text += $"Start downloading.\n"; this.progressRing.IsActive = true; var downloader = new SimpleDownloader(); var stream = await downloader.RequestHttpContentAsync( "", "", ""); this.progressRing.IsActive = false; var file = await SaveStorageFile(stream); var result = await Launcher.LaunchFolderAsync(ApplicationData.Current.LocalFolder); txtLog.Text += $"Done.\n "; } }
本篇讨论了如何在APPX中下载文件并运行,使用户仅需要在MS Store中进行一次下载即可完成整个application的安装。实际使用中更适合通过Desktop Bridge打包的desktop application。而对纯UWP的客户端并不友好。对纯UWP客户端的处理我们在下一篇中做更进一步讨论。
GitHub:
https://github.com/manupstairs/AppxDownloadWin32Component
- 迁移桌面程序到MS Store(1)——通过Visual Studio创建Packaging工程
- 迁移桌面程序到MS Store(5)——.NET Standard
- 迁移桌面程序到MS Store(7)——APPX + Service
- 迁移桌面程序到MS Store(6)——.NET Portability Analyzer
- 通过一般处理程序实现【文件下载】
- keil 通过JTAG下载程序 报错:error: flash download failed - "cortex-m3"的解决方法
- aduc7026开发板通过串口下载程序的过程
- ios 程序发布成ipa 文件 通过 web 下载和安装。install App via OTA
- C#通过WIN32 API 获取外部程序sysListview的值和TreeView的值
- 关于 通过jlink使用jtag(或swd)下载程序成功后,keil4 uversion停止运行 的解决方法
- 通过minicom,下载程序到Imote2
- 在 .NET 框架程序中通过DllImport使用 Win32 API
- C#通过WIN32 API实现嵌入程序窗体
- 通过php程序进行文件下载
- 特殊密码锁 的通过码是:(请注意,在openjudge上提交了程序并且通过以后,就可以下载到通过码。请注意看公告里关于编程作业的说明)
- 如何通过Keil将程序正确的下载进flash中
- [c#] 通过 WIN32 API 实现嵌入程序窗体
- 通过远程桌面操作程序出现hook cannot be created(SendKeys语句错误)的解决
- 通过内核对象在服务程序和桌面程序之间通信的小问题
- 通过小程序下载APP的方法