次の方法で共有


Windows Device Portal 用のカスタム プラグインを作成する

Windows デバイス ポータル (WDP) を使用して Web ページをホストし、診断情報を提供する UWP アプリを作成する方法について説明します。

Windows 10 Creators Update (バージョン 1703、ビルド 15063) 以降では、Device Portal を使用してアプリの診断インターフェイスをホストできます。 この記事では、アプリの DevicePortalProvider を作成するために必要な 3 つの要素 (アプリケーション パッケージ マニフェスト 変更、Device Portal サービスへのアプリの接続の設定、受信要求の処理) について説明します。

新しい UWP アプリ プロジェクトを作成する

Microsoft Visual Studio で、新しい UWP アプリ プロジェクトを作成します。 [ファイル] [新しい プロジェクト] に移動し、[C#空のアプリ (Windows ユニバーサル) を選択し、[次] をクリックします。 [新しいプロジェクト の構成] ダイアログ ボックスで、 プロジェクトに「DevicePortalProvider」という名前を付け、作成をクリックします。 これは、アプリ サービスを含むアプリになります。 場合によっては、Visual Studio を更新するか、最新の Windows SDKをインストールする必要があります。

devicePortalProvider 拡張機能をアプリケーション パッケージ マニフェストに追加する

アプリを Device Portal プラグインとして機能させるには、package.appxmanifest ファイルにいくつかのコードを追加する必要があります。 まず、ファイルの先頭に次の名前空間定義を追加します。 IgnorableNamespaces 属性にも追加します。

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

アプリがデバイス ポータル プロバイダーであることを宣言するには、アプリ サービスと、それを使用する新しい Device Portal プロバイダー拡張機能を作成する必要があります。 windows.appService 拡張機能と windows.devicePortalProvider 拡張機能の両方を、ExtensionsApplication 要素に追加します。 各拡張機能で AppServiceName 属性が一致していることを確認します。 これは、このアプリ サービスを起動してハンドラー名前空間の要求を処理できることを Device Portal サービスに示します。

...   
<Application 
    Id="App" 
    Executable="$targetnametoken$.exe"
    EntryPoint="DevicePortalProvider.App">
    ...
    <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="MySampleProvider.SampleProvider">
            <uap:AppService Name="com.sampleProvider.wdp" />
        </uap:Extension>
        <uap4:Extension Category="windows.devicePortalProvider">
            <uap4:DevicePortalProvider 
                DisplayName="My Device Portal Provider Sample App" 
                AppServiceName="com.sampleProvider.wdp" 
                HandlerRoute="/MyNamespace/api/" />
        </uap4:Extension>
    </Extensions>
</Application>
...

HandlerRoute 属性は、アプリによって要求された REST 名前空間を参照します。 Device Portal サービスによって受信されたその名前空間 (暗黙的にワイルドカードが続く) に対する HTTP 要求はすべて、処理対象のアプリに送信されます。 この場合、<ip_address>/MyNamespace/api/* に対して正常に認証された HTTP 要求がアプリに送信されます。 ハンドラー ルート間の競合は、「最も具体的な一致が優先される」という原則で解決されます。つまり、"/MyNamespace/api/foo" へのリクエストは、"/MyNamespace" よりも "/MyNamespace/api" を持つプロバイダーと一致します。

この機能には、2 つの新機能が必要です。 これらは、package.appxmanifest ファイルにも追加する必要があります。

...
<Capabilities>
    ...
    <Capability Name="privateNetworkClientServer" />
    <rescap:Capability Name="devicePortalProvider" />
</Capabilities>
...

機能 "devicePortalProvider" は制限されています ("rescap")。 これは、アプリを公開する前に、ストアから事前の承認を得る必要があることを意味します。 ただし、これにより、サイドローディングによってアプリをローカルでテストできることが妨げられるわけではありません。 制限付き機能の詳細については、「アプリ機能宣言」を参照してください。

バックグラウンド タスクと WinRT コンポーネントを設定する

Device Portal 接続を設定するには、アプリで実行されている Device Portal のインスタンスを使用して、Device Portal サービスからアプリ サービス接続をフックする必要があります。 これを行うには、IBackgroundTaskを実装するクラスを使用して、新しい WinRT コンポーネント アプリケーションに追加します。

using Windows.System.Diagnostics.DevicePortal;
using Windows.ApplicationModel.Background;

namespace MySampleProvider {
    // Implementing a DevicePortalConnection in a background task
    public sealed class SampleProvider : IBackgroundTask {
        BackgroundTaskDeferral taskDeferral;
        DevicePortalConnection devicePortalConnection;
        //...
    }

その名前が、AppService EntryPoint ("MySampleProvider.SampleProvider") によって設定された名前空間とクラス名と一致していることを確認します。 Device Portal プロバイダーに対して最初の要求を行うと、Device Portal によって要求が一時的に保存されるとともに、アプリのバックグラウンド タスクが起動され、Run メソッドが呼び出され、IBackgroundTaskInstanceが渡されます。 その後、アプリはそれを使用して、DevicePortalConnection インスタンスを設定します。

// Implement background task handler with a DevicePortalConnection
public void Run(IBackgroundTaskInstance taskInstance) {
    // Take a deferral to allow the background task to continue executing 
    this.taskDeferral = taskInstance.GetDeferral();
    taskInstance.Canceled += TaskInstance_Canceled;

    // Create a DevicePortal client from an AppServiceConnection 
    var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
    var appServiceConnection = details.AppServiceConnection;
    this.devicePortalConnection = DevicePortalConnection.GetForAppServiceConnection(appServiceConnection);

    // Add Closed, RequestReceived handlers 
    devicePortalConnection.Closed += DevicePortalConnection_Closed;
    devicePortalConnection.RequestReceived += DevicePortalConnection_RequestReceived;
}

要求処理ループを完了するためにアプリで処理する必要があるイベントが2つあります。1つは、Device Portal サービスがシャットダウンするときに発生する 終了です。もう1つは、HTTP受信要求を表示し、Device Portal プロバイダーの主な機能を提供する RequestReceivedです。

RequestReceived イベントを処理する

RequestReceived イベントは、プラグインの指定されたハンドラー ルートで行われる HTTP 要求ごとに 1 回発生します。 Device Portal プロバイダーの要求処理ループは、NodeJS Express の場合と似ています。要求オブジェクトと応答オブジェクトはイベントと共に提供され、ハンドラーは応答オブジェクトを入力して応答します。 デバイス ポータル プロバイダーでは、RequestReceived イベントとそのハンドラーは、Windows.Web.Http.HttpRequestMessage 使用し、HttpResponseMessage オブジェクトを します。

// Sample RequestReceived echo handler: respond with an HTML page including the query and some additional process information. 
private void DevicePortalConnection_RequestReceived(DevicePortalConnection sender, DevicePortalConnectionRequestReceivedEventArgs args)
{
    var req = args.RequestMessage;
    var res = args.ResponseMessage;

    if (req.RequestUri.AbsolutePath.EndsWith("/echo"))
    {
        // construct an html response message
        string con = "<h1>" + req.RequestUri.AbsoluteUri + "</h1><br/>";
        var proc = Windows.System.Diagnostics.ProcessDiagnosticInfo.GetForCurrentProcess();
        con += String.Format("This process is consuming {0} bytes (Working Set)<br/>", proc.MemoryUsage.GetReport().WorkingSetSizeInBytes);
        con += String.Format("The process PID is {0}<br/>", proc.ProcessId);
        con += String.Format("The executable filename is {0}", proc.ExecutableFileName);
        res.Content = new Windows.Web.HttpStringContent(con);
        res.Content.Headers.ContentType = new Windows.Web.Http.Headers.HttpMediaTypeHeaderValue("text/html");
        res.StatusCode = Windows.Web.Http.HttpStatusCode.Ok;            
    }
    //...
}

このサンプル要求ハンドラーでは、まず、args パラメーターから要求オブジェクトと応答オブジェクトをプルしてから、要求 URL といくつかの追加の HTML 書式設定を含む文字列を作成します。 これは、HttpStringContent インスタンス として Response オブジェクトに追加されます。 "String" や "Buffer" などの IHttpContent クラスの他の も許可されます。

その後、応答は HTTP 応答として設定され、200 (OK) 状態コードが指定されます。 元の呼び出しを行ったブラウザーで期待どおりにレンダリングされます。 RequestReceived イベント ハンドラーが返されると、応答メッセージは自動的にユーザー エージェントに返されることに注意してください。追加の "送信" メソッドは必要ありません。

デバイス ポータルの応答メッセージ

静的コンテンツの提供

静的コンテンツはパッケージ内のフォルダーから直接提供できるため、プロバイダーに UI を簡単に追加できます。 静的コンテンツを提供する最も簡単な方法は、URL にマップできるコンテンツ フォルダーをプロジェクトに作成することです。

デバイス ポータルの静的コンテンツ フォルダー

次に、静的コンテンツ ルートを検出し、要求を適切にマップする RequestReceived イベント ハンドラーにルート ハンドラーを追加します。

if (req.RequestUri.LocalPath.ToLower().Contains("/www/")) {
    var filePath = req.RequestUri.AbsolutePath.Replace('/', '\\').ToLower();
    filePath = filePath.Replace("\\backgroundprovider", "")
    try {
        var fileStream = Windows.ApplicationModel.Package.Current.InstalledLocation.OpenStreamForReadAsync(filePath).GetAwaiter().GetResult();
        res.StatusCode = HttpStatusCode.Ok;
        res.Content = new HttpStreamContent(fileStream.AsInputStream());
        res.Content.Headers.ContentType = new HttpMediaTypeHeaderValue("text/html");
    } catch(FileNotFoundException e) {
        string con = String.Format("<h1>{0} - not found</h1>\r\n", filePath);
        con += "Exception: " + e.ToString();
        res.Content = new Windows.Web.Http.HttpStringContent(con);
        res.StatusCode = Windows.Web.Http.HttpStatusCode.NotFound;
        res.Content.Headers.ContentType = new Windows.Web.Http.Headers.HttpMediaTypeHeaderValue("text/html");
    }
}

Visual Studio の [プロパティ] メニューで、コンテンツ フォルダー内のすべてのファイルが "コンテンツ" としてマークされ、"新しい場合はコピー" または "常にコピー" に設定されていることを確認します。 これにより、デプロイ時にファイルが AppX パッケージ内に確実に配置されます。

静的コンテンツ ファイルのコピーを構成

既存の Device Portal リソースと API の使用

Device Portal プロバイダーによって提供される静的コンテンツは、コア Device Portal サービスと同じポートで提供されます。 つまり、HTML 内の単純な <link> タグと <script> タグを使用して、Device Portal に含まれている既存の JS と CSS を参照できます。 一般に、すべての主要な Device Portal REST API を便利な webbRest オブジェクトにラップする rest.jscommon.css ファイルを使用することをお勧めします。これにより、デバイス ポータルの残りの UI に合わせてコンテンツのスタイルを設定できます。 この例は、サンプルに含まれている index.html ページで確認できます。 rest.js を使用して、Device Portal からデバイス名と実行中のプロセスを取得します。

デバイス ポータル プラグインの出力 を する

重要なことに、webbRest で HttpPost/DeleteExpect200 メソッドを使用すると、CSRF 処理 が自動的に行われます。これにより、Web ページで状態変更 REST API を呼び出すことができます。

Device Portal に含まれる静的コンテンツには、重大な変更に対する保証はありません。 API は頻繁に変更されるとは思われませんが、特にプロバイダーが使用すべきでない common.js ファイルや controls.js ファイルでは変更される可能性があります。

Device Portal 接続のデバッグ

バックグラウンド タスクをデバッグするには、Visual Studio でコードを実行する方法を変更する必要があります。 アプリ サービス接続をデバッグして、プロバイダーが HTTP 要求をどのように処理しているかを調べるには、次の手順に従います。

  1. [デバッグ] メニューの [DevicePortalProvider のプロパティ] を選択します。
  2. [デバッグ] タブの [開始アクション] セクションで、[起動しないが、起動時にコードをデバッグする] を選択します。
    デバッグ モードでプラグインを配置
  3. RequestReceived ハンドラー関数にブレークポイントを設定します。 requestreceived ハンドラーでのブレークポイント

ビルド アーキテクチャがターゲットのアーキテクチャと正確に一致していることを確認します。 64 ビット PC を使用している場合は、AMD64 ビルドを使用してデプロイする必要があります。 4. F5 キーを押してアプリ 5 をデプロイします。Device Portal をオフにしてから、アプリを見つけられるようにもう一度オンにします (アプリ マニフェストを変更する場合にのみ必要です。残りの時間は、この手順を再デプロイしてスキップできます)。 6. ブラウザでプロバイダーの名前空間にアクセスすると、ブレークポイントがヒットされます。