遷移桌面程式到MS Store(8)——通過APPX下載Win32Component

樓上那個蜀黍發表於2019-05-24

在上一篇《遷移桌面程式到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

相關文章