UWP DesktopBridge プロジェクト構成

## プロジェクト概要
ローカルサーバーを立てて、適当なレスポンスを返却するアプリケーションです。
UWPだと同一PCからのアクセスは不可能なので、DesktopBridgeを使用してローカルサーバーを外部サービスとして実行する。

https://github.com/Yuki4-dev/fts

## プロジェクト構成
必要最低限のプロジェクト構成は以下のようになる。
```
├─fts           パッケージプロジェクト
├─fts.APP       FullTrustで起動する外部APPプロジェクト
├─fts.Shared    共通のクラスライブラリ
└─fts.UWP       UWPプロジェクト
```
## クラスライブラリ
クラスライブラリプロジェクト（.NetStandard2.0）
UWPプロジェクトと外部APPプロジェクトから参照する共通のライブラリ。
共通で使用する定数や、関数または2つのアプリケーション間の疎通に使用するメッセージクラスなど作成している。
UWPからも参照できるように、.NetStandard2.0を指定している。

```Constant.cs
namespace fts.Shared
{
    public static class Constant
    {
        public static string AppServiceName = "ftsAppService";

        public static string RequestMessage = "RequestMessage";

        public static string ResponseMessage = "ResponseMessage";
    }
}
```
AppServiceNameは後述するマニフェストファイルに追加するAppService名を定義している。
UWP、外部APPからAppService名を使用するのでここに定義。

## 外部APPプロジェクト
コンソールアプリケーションプロジェクト（.Net6.0）
HttpListenerを使用してローカルサーバーを立てる。

### fts.APP.csproj
対象のOSがWindows７や最小のOSバージョンが７だと後述のAppServiceConnectionが使用できないので
以下のように、対象のOSと最小のOSバージョンを指定する。(10以降ならOKのはず)
```fts.APP.csproj.xml
<PropertyGroup>
  <OutputType>WinExe</OutputType>
  <TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
  <ImplicitUsings>enable</ImplicitUsings>
  <Nullable>enable</Nullable>
  <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
</PropertyGroup>
```

### Main
HttpListenerを使用して待ち受けするHttpServerクラスをnewして終了を待つだけ。
```HttpServer.cs
namespace fts.APP
{
    internal class Program
    {
        public static async Task Main(string[] args)
        {
            await new HttpServer().WaitClosed();
        }
    }
}
```

### AppServiceConnection
受信したリクエストをUWPアプリケーションに送信するなど
UWP側とのメッセージのやり取りにはAppServiceConnectionを使用する。
```HttpServer.cs
private readonly AppServiceConnection _connection;

public HttpServer()
{
    _connection = new AppServiceConnection
    {
        // 共通のクラスライブラリの定義から、AppService名を設定する。 
        AppServiceName = Constant.AppServiceName,
        PackageFamilyName = Package.Current.Id.FamilyName 
    }

     // コネクション切断後の処理
    _connection.ServiceClosed += Connection_ServiceClosed;

    // コネクションの確立とサーバーの起動 
    StartAsync()
}

private async void StartAsync()
{
    // コネクション確立
    var status = await _connection.OpenAsync(); 
    if (status != AppServiceConnectionStatus.Success)
    {
        Close();
        return;
    }

    // HttpListenerの起動＆待ち受け処理．．．

   // 受信したリクエストボディをUWP側へ送信して、応答メッセージを受け取る。
   var massage = await SendMessageAsync(requestBody);

    // HttpListenerのレスポンス返却処理．．．
}

private async Task<string> SendMessageAsync(string message)
{
    var valueset = new ValueSet
    {
        { Constant.RequestMessage, message }
    };

    // AppServiceConnectionを使用して、UWP側へ送信する。
    var massage = await _connection.SendMessageAsync(valueset);

    // UWP側からの応答メッセージを取得する。
    return massage.Message[Constant.ResponseMessage]?.ToString() ?? string.Empty;
}

```
メッセージのやり取りに使用されるValueSetは、値として設定できるObjectに指定がある。
基本、プリミティブな型とstringくらい。
なにやらIPropertyValueを継承したらいけそうな気もする。
ただそんなことをするより、メッセージのクラスをJsonにシリアライズしてstringでやり取りするほうが楽そう。

https://docs.microsoft.com/ja-JP/uwp/api/windows.foundation.collections.valueset?view=winrt-22621

## UWPプロジェクト
### Windows Desktop Extensions for the UWPの参照追加
FullTrustを使用したいので、参照を追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688992/868d9ed2-90e8-7eab-28dc-6faa7d2760bd.png)

### App.xaml.cs
FullTrustで、外部AppServiceを起動
```App.xaml.cs
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
```
FullTrustで起動した外部サービスからのメッセージを受信できるように
OnBackgroundActivatedをオーバーライドして、AppServiceConnectionのRequestReceivedにイベントをハンドルする。
```App.xaml.cs
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
    base.OnBackgroundActivated(args);
    if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails details)
    {
        appServiceDeferral = args.TaskInstance.GetDeferral();
        args.TaskInstance.Canceled += OnTaskCanceled;
        
        var appService = details.AppServiceConnection;
        // 外部APPからのメッセージ受信イベント
        appService.RequestReceived += AppService_RequestReceived;
        // 外部APPのサービス終了イベント
        appService.ServiceClosed += AppService_ServiceClosed;
    }
}
```
全体
```App.xaml.cs
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace fts.UWP
{
    /// <summary>
    /// 既定の Application クラスを補完するアプリケーション固有の動作を提供します。
    /// </summary>
    public sealed partial class App : Application
    {
        private BackgroundTaskDeferral appServiceDeferral = null;

        /// <summary>
        /// 外部のAppServiceからのメッセージ受信イベント
        /// </summary>
        public static event Action<AppServiceConnection, AppServiceRequestReceivedEventArgs> RequestReceived;

        /// <summary>
        ///単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの
        ///最初の行であるため、論理的には main() または WinMain() と等価です。
        /// </summary>
        public App()
        {
            Launch();
            InitializeComponent();
            Suspending += OnSuspending;
        }

        private async void Launch()
        {
            await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
        }

        protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
        {
            base.OnBackgroundActivated(args);
            if (args.TaskInstance.TriggerDetails is AppServiceTriggerDetails)
            {
                appServiceDeferral = args.TaskInstance.GetDeferral();
                args.TaskInstance.Canceled += OnTaskCanceled;

                var details = args.TaskInstance.TriggerDetails as AppServiceTriggerDetails;
                var appService = details.AppServiceConnection;
                appService.RequestReceived += AppService_RequestReceived;
                appService.ServiceClosed += AppService_ServiceClosed;
            }
        }

        private void AppService_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            RequestReceived?.Invoke(sender, args);
        }

        private void AppService_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
            if (appServiceDeferral != null)
            {
                appServiceDeferral.Complete();
            }
        }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (appServiceDeferral != null)
            {
                appServiceDeferral.Complete();
            }
        }

        // 以下省略...
}
```

## パッケージプロジェクト
Windowsアプリケーションパッケージプロジェクト
### Package.appxmanifest
クリックで開いて以下のように、宣言にApp Serviceを追加
FullTrustで起動するサービスの名前を設定する。
なんでもいいのでここでは「ftsAppService」とする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688992/525270f6-6a80-b1cf-1063-f785627aa869.png)
ネームスペースに以下を追加
```
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
```
\<Applications\>に以下を追加
```
<desktop:Extension Category="windows.fullTrustProcess" Executable="{外部APPプロジェクト名}\{外部APP名}.exe" />
```
全体
```Package.appxmanifest.xml
<?xml version="1.0" encoding="utf-8"?>

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="uap rescap">

  <Identity
    Name="482aa3a4-eb49-4343-b97b-322c22a6e5cf"
    Publisher="CN=Yuki4"
    Version="1.0.0.0" />

  <Properties>
    <DisplayName>fts</DisplayName>
    <PublisherDisplayName>Yuki4</PublisherDisplayName>
    <Logo>Images\StoreLogo.png</Logo>
  </Properties>

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" />
  </Dependencies>

  <Resources>
    <Resource Language="x-generate"/>
  </Resources>

  <Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="$targetentrypoint$">
      <uap:VisualElements
        DisplayName="fts"
        Description="fts"
        BackgroundColor="transparent"
        Square150x150Logo="Images\Square150x150Logo.png"
        Square44x44Logo="Images\Square44x44Logo.png">
        <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" />
        <uap:SplashScreen Image="Images\SplashScreen.png" />
      </uap:VisualElements>
      <Extensions>
        <desktop:Extension Category="windows.fullTrustProcess" Executable="fts.APP\fts.APP.exe" />
        <uap:Extension Category="windows.appService">
          <uap:AppService Name="ftsAppService"/>
        </uap:Extension>
      </Extensions>
    </Application>
  </Applications>

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>
</Package>
```

### 依存関係
プロジェクト参照の追加から、UWPプロジェクトと外部APPプロジェクトを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688992/6da2c859-2a67-c20f-d07c-7861575c7f37.png)

## ビルド
プラットフォームはx86かx64に、スタートアッププロジェクトはパッケージプロジェクトを指定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/688992/c0117352-a2a1-f1bd-a768-b2b3d24beb79.png)

## まとめ
UWPもDesktopBridgeを使用することで、かなり制限がなくなりました。（いいのか？）
以下の記事を参考になりました。

https://qiita.com/kumaS-kumachan/items/79a2cdf0b156b8650a8c

https://blog.okazuki.jp/entry/2018/02/09/204909