在上一篇《迁移桌面程序到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[];
int bytesRead;
do
{
bytesRead = stream.Read(buffer, , );
fileStream.Write(buffer, , bytesRead);
} while (bytesRead > ); } 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 = });
dialog.Commands.Add(new UICommand { Label = "Cancel", Id = });
var res = await dialog.ShowAsync(); if ((int)res.Id == )
{
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(8)——通过APPX下载Win32Component的更多相关文章

  1. 迁移桌面程序到MS Store(5)——.NET Standard

    接下来的几篇,我想讨论下迁移桌面程序到MS Store,可以采用的比较常见.通用性比较强的实施步骤和分层架构. 通常商业项目一般都是不断的迭代,不太可能突然停止更新现有的桌面版本,然后花很长时间从头来 ...

  2. 迁移桌面程序到MS Store(1)——通过Visual Studio创建Packaging工程

    之前跑去做了一年多的iOS开发,被XCode恶心得不行.做人呢,最重要的是开心.所以我就炒了公司鱿鱼,挪了个窝回头去做Windows开发了.        UWP什么的很久没有正儿八经写了,国内的需求 ...

  3. 迁移桌面程序到MS Store(9)——APPX With Desktop Extension

    在<迁移桌面程序到MS Store(8)——通过APPX下载Win32Component>中我们讨论了通过APPX来下载Service部分的安装包.但是纯UWP的客户端并不能自动运行下载的 ...

  4. 迁移桌面程序到MS Store(10)——在Windows S Mode运行

    首先简单介绍Windows 10 S Mode,Windows在该模式下,只能跑MS Store里的软件,不能通过其他方式安装.好处是安全有保障,杜绝一切国产流氓软件.就像iOS一样,APP进商店都需 ...

  5. 迁移桌面程序到MS Store(12)——WPF使用UWP InkToolbar和InkCanvas

    我们在<迁移桌面程序到MS Store(4)——桌面程序调用Win10 API>提到了对Win10 API的调用,但仍存在无法在WPF中使用UWP控件的问题,虽然都是XAML控件,但却是两 ...

  6. 迁移桌面程序到MS Store(13)——动态检查Win10 API是否可用

    假设我们现有一个WPF程序,需要支持1903以前的Windows 10版本.同时在1903以后的版本上,额外多出一个Ink的功能.那么我们就可以通过ApiInformation.IsApiContra ...

  7. 迁移桌面程序到MS Store(14)——APPX嵌入WCF Service以Admin权限运行

    Windows10 1809版本开始,微软又对UWP开放了新的Capability:AllowElevation. 通过这个新的Capability,UWP APP能够在运行时向用户请求Admin权限 ...

  8. 迁移桌面程序到MS Store(2)——Desktop App Converter

    迁移传统桌面程序到MS Store的另一种方式是使用Desktop App Converter工具.虽然本篇标题包含了Desktop App Converter(以下简称DAC),实际上我是来劝你别用 ...

  9. 迁移桌面程序到MS Store(3)——开机自启动

    迁移桌面程序的时候,有可能你会遇到这么个需求——开机自启动.Windows传统桌面程序的传统陋习.不论什么奇葩软件都想要开机自启动,默认就给你打开,一开机哐哐哐什么雷,什么企鹅都蹦出来,也不管你用不用 ...

随机推荐

  1. nodeJS进程管理器pm2

    pm2是一个带有负载均衡功能的Node应用的进程管理器.当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完美的. PM2是开源的基于Nodejs的进程管 ...

  2. POI转换word doc文件为(html,xml,txt)

    在POI中还存在有针对于word doc文件进行格式转换的功能.我们可以将word的内容转换为对应的Html文件,也可以把它转换为底层用来描述doc文档的xml文件,还可以把它转换为底层用来描述doc ...

  3. Python-DB接口规范

    threadsafety 线程安全级别.threadsafety 这是一个整数, 取值范围如下: 0:不支持线程安全, 多个线程不能共享此模块 1:初级线程安全支持: 线程可以共享模块, 但不能共享连 ...

  4. javase(2)_递归&迭代

    一.递归  程序调用自身的编程技巧称为递归( recursion).递归做为一种算法在程序设计语言中广泛应用. 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题 ...

  5. java在线聊天项目 swt可视化窗口Design 重新设计好友列表窗口 增加菜单栏

    增加的菜单栏效果图如下: eclipse 中调整到 swt的design视图下 控件区域选择Menu Controls 将Menu Bar拖动到窗口标题栏 将Cascaded Menu拖动到Menu ...

  6. assign, retain, copy, weak, strong

    一.assign, retain, copy 的区别(引用计数 RC reference count) 参考:IOS基础:retain,copy,assign及autorelease 1. 假设你用m ...

  7. python实现FTP

    原文地址:https://www.cnblogs.com/huangxm/p/6274645.html#undefined FTP服务的主动模式和被动模式 在开始之前,先聊一下FTP的主动模式和被动模 ...

  8. python--内置常用模块

    一 . 简单的了解模块 你写的每一个py文件都是一个模块. 还有一些我们一直在使用的模块. buildins  内置模块. print, input. random  主要是和随机相关的的内容 ran ...

  9. python 学习第二周总复习

    目录 数据类型内置方法 数字类型内置方法 整型 浮点型 字符串类型内置方法 列表类型内置方法 元祖类型内置方法 字典类型内置方法 集合类型内置方法 布尔类型 数据类型总结 拷贝 浅拷贝 深拷贝 053 ...

  10. 数据结构( Pyhon 语言描述 ) — — 第7章:栈

    栈概览 栈是线性集合,遵从后进先出原则( Last - in first - out , LIFO )原则 栈常用的操作包括压入( push ) 和弹出( pop ) 栈的应用 将中缀表达式转换为后缀 ...