Device Portal 用のカスタム プラグインの作成Write a custom plugin for Device Portal

Windows Device Portal を使用して Web ページをホストし、診断情報を提供する UWP アプリを作成する方法について説明します。Learn how to write a UWP app that uses the Windows Device Portal to host a web page and provide diagnostic information.

Windows 10 Creators Update (バージョン 1703、ビルド 15063) 以降では、Device Portal を使用してアプリの診断インターフェイスをホストすることができます。Starting with Windows 10 Creators Update (version 1703, build 15063), you can use Device Portal to host your app's diagnostic interfaces. この記事では、アプリ用の DevicePortalProvider の作成に必要な 3 つの要素である、アプリケーション パッケージ マニフェストの変更、デバイス ポータル サービスへのアプリの接続の設定、着信要求の処理について説明します。This article covers the three pieces needed to create a DevicePortalProvider for your app – the application package manifest changes, setting up your app’s connection to the Device Portal service, and handling an incoming request.

新しい UWP アプリ プロジェクトを作成するCreate a new UWP app project

Microsoft Visual Studio で、新しい UWP アプリ プロジェクトを作成します。In Microsoft Visual Studio, create a new UWP app project. [ファイル] > [新規] > [プロジェクト] に移動し、 [Blank App (Windows Universal) for C#](C# 用空のアプリ (Windows ユニバーサル)) を選択して [次へ] をクリックします。Go to File > New > Project and select Blank App (Windows Universal) for C#, and then click Next. [新しいプロジェクトの構成] ダイアログ ボックスで、In the Configure your new project dialog box. プロジェクトに "DevicePortalProvider" という名前を指定し、 [作成] をクリックします。Name the project "DevicePortalProvider" and then click Create. これは、アプリ サービスを格納するアプリです。This will be the app that contains the app service. Visual Studio の更新、または最新の Windows SDK のインストールが必要になる場合があります。You may need to update Visual Studio or install the latest Windows SDK.

アプリケーション パッケージ マニフェストに devicePortalProvider 拡張機能を追加するAdd the devicePortalProvider extension to your application package manifest

アプリを Device Portal プラグインとして機能させるために、package.appxmanifest ファイルにコードを追加する必要があります。You will need to add some code to your package.appxmanifest file in order to make your app functional as a Device Portal plugin. 最初に、ファイルの先頭に次の名前空間の定義を追加します。First, add the following namespace definitions at the top of the file. また、これらを IgnorableNamespaces 属性にも追加します。Also add them to the IgnorableNamespaces attribute.

<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 Provider として宣言するには、アプリ サービスと、それを使用する新しい Device Portal Provider 拡張機能を作成する必要があります。In order to declare that your app is a Device Portal Provider, you need to create an app service and a new Device Portal Provider extension that uses it. windows.appService 拡張機能と windows.devicePortalProvider 拡張機能の両方を、Application の下の Extensions 要素に追加します。Add both the windows.appService extension and the windows.devicePortalProvider extension in the Extensions element under Application. 各拡張機能で AppServiceName 属性が一致していることを確認します。Make sure the AppServiceName attributes match in each extension. これにより、このアプリ サービスを起動してハンドラーの名前空間で要求を処理できることを、Device Portal サービスに指示します。This indicates to the Device Portal service that this app service can be launched to handle requests on the handler namespace.

...   
<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 名前空間を参照します。The HandlerRoute attribute references the REST namespace claimed by your app. Device Portal サービスによって受信された、その名前空間 (暗黙的にワイルドカードが続く) のすべての HTTP 要求は、アプリに送信されて処理されます。Any HTTP requests on that namespace (implicitly followed by a wildcard) received by the Device Portal service will be sent to your app to be handled. この場合、<ip_address>/MyNamespace/api/* に対する、正常に認証されたすべての HTTP 要求がアプリに送信されます。In this case, any successfully authenticated HTTP request to <ip_address>/MyNamespace/api/* will be sent to your app. ハンドラー ルート間の競合は "最長一致" のチェックにより解決され、要求により多く一致するルートが選択されます。たとえば、"/MyNamespace/api/foo" に対する要求は、"/MyNamespace" ではなく、"/MyNamespace/api" のプロバイダーと一致することを意味します。Conflicts between handler routes are settled via a "longest wins" check: whichever route matches more of the requests is selected, meaning that a request to "/MyNamespace/api/foo" will match against a provider with "/MyNamespace/api" rather than one with "/MyNamespace".

この機能には、2 つの新しい機能が必要です。Two new capabilities are required for this functionality. また、これらを package.appxmanifest ファイルに追加する必要があります。they must also be added to your package.appxmanifest file.

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

注意

"devicePortalProvider" 機能は制限された機能 ("rescap") であり、ストアでアプリを公開する前に、ストアから事前に承認を受ける必要があります。The capability "devicePortalProvider" is restricted ("rescap"), which means you must get prior approval from the Store before your app can be published there. ただし、これは、サイドローディングによって、アプリをローカルでテストすることを禁止するものではありません。However, this does not prevent you from testing your app locally through sideloading. 制限された機能について詳しくは、「アプリ機能の宣言」をご覧ください。For more information about restricted capabilities, see App capability declarations.

バック グラウンド タスクと WinRT コンポーネントを設定するSet up your background task and WinRT Component

Device Portal の接続を設定するために、アプリでは、アプリ内で実行されている Device Portal のインスタンスを使用して、Device Portal サービスからアプリ サービスの接続をフックする必要があります。In order to set up the Device Portal connection, your app must hook up an app service connection from the Device Portal service with the instance of Device Portal running within your app. これを行うには、IBackgroundTask を実装するクラスを使用して、アプリケーションに新しい WinRT コンポーネントを追加します。To do this, add a new WinRT Component to your application with a class that implements IBackgroundTask.

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

その名前が、AppService EntryPoint ("MySampleProvider.SampleProvider") によって設定された名前空間やクラス名が一致することを確認します。Make sure that its name matches the namespace and class name set up by the AppService EntryPoint ("MySampleProvider.SampleProvider"). Device Portal Provider に対して最初の要求を行うときに、Device Portal は要求を格納し、アプリのバック グラウンド タスクを起動して、その Run メソッドを呼び出し、IBackgroundTaskInstance を渡します。When you make your first request to your Device Portal provider, Device Portal will stash the request, launch your app's background task, call its Run method, and pass in an IBackgroundTaskInstance. アプリはこれを使用して DevicePortalConnection インスタンスを設定します。Your app then uses it to set up a DevicePortalConnection instance.

// 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 つのイベントがアプリによって処理される必要があります。Device Portal サービスがシャットダウンするたびに発生する Closed と、着信 HTTP 要求を処理し、Device Portal プロバイダーのメイン機能を提供する RequestReceived です。There are two events that must be handled by the app to complete the request handling loop: Closed, for whenever the Device Portal service shuts down, and RequestReceived, which surfaces incoming HTTP requests and provides the main functionality of the Device Portal provider.

RequestReceived イベントを処理するHandle the RequestReceived event

RequestReceived イベントは、プラグインの指定されたハンドラー ルートで行われる各 HTTP 要求について 1 回生成されます。The RequestReceived event will be raised once for every HTTP request that is made on your plugin's specified Handler Route. Device Portal プロバイダーの要求処理ループは、NodeJS Express での要求処理ループと似ています。イベントと共に要求と応答のオブジェクトが提供され、ハンドラーは応答オブジェクトを入力することで応答します。The request handling loop for Device Portal providers is similar to that in NodeJS Express: the request and response objects are provided together with the event, and the handler responds by filling in the response object. Device Portal プロバイダーでは、RequestReceived イベントとそのハンドラーが Windows.Web.Http.HttpRequestMessage オブジェクトと HttpResponseMessage オブジェクトを使用します。In Device Portal providers, the RequestReceived event and its handlers use Windows.Web.Http.HttpRequestMessage and HttpResponseMessage objects.

// 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 HttpStringContent(con);
        res.Content.Headers.ContentType = new HttpMediaTypeHeaderValue("text/html");
        res.StatusCode = HttpStatusCode.Ok;            
    }
    //...
}

このサンプル要求ハンドラーでは、まず argsパラメーターから要求と応答のオブジェクトを取り出し、要求 URL やその他の HTML 書式設定を含む文字列を作成します。In this sample request handler, we first pull the request and response objects out of the args parameter, then create a string with the request URL and some additional HTML formatting. これが、HttpStringContent インスタンスとして応答オブジェクトに追加されます。This is added into the Response object as an HttpStringContent instance. その他の IHttpContent クラス ("String" や "Buffer" などのクラス) も使用できます。Other IHttpContent classes, such as those for "String" and "Buffer," are also allowed.

応答は、HTTP 応答として設定され、200 (OK) 状態コードが指定されます。The response is then set as an HTTP response and given a 200 (OK) status code. 元の呼び出しを行ったブラウザーでは、期待どおりにレンダリングされます。It should render as expected in the browser that made the original call. RequestReceived イベント ハンドラーが制御を戻すときに、応答メッセージはユーザー エージェントに自動的に返されます。追加の "send" メソッドは必要ありません。Note that when the RequestReceived event handler returns, the response message is automatically returned to the user agent: no additional "send" method is needed.

Device Portal の応答メッセージ

静的コンテンツを提供するProviding static content

静的コンテンツは、パッケージ内のフォルダーから直接提供することができ、プロバイダーに UI を追加することは非常に簡単です。Static content can be served directly from a folder within your package, making it very easy to add a UI to your provider. 静的コンテンツを提供する最も簡単な方法では、プロジェクト内で、URL にマップできるコンテンツ フォルダーを作成することです。The easiest way to serve static content is to create a content folder in your project that can map to a URL.

Device Portal の静的コンテンツ フォルダー

次に、RequestReceived イベント ハンドラーに、静的コンテンツのルートを検出し、要求を適切にマップするルート ハンドラーを追加します。Then, add a route handler in your RequestReceived event handler that detects static content routes and maps a request appropriately.

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 HttpStringContent(con);
        res.StatusCode = HttpStatusCode.NotFound;
        res.Content.Headers.ContentType = new HttpMediaTypeHeaderValue("text/html");
    }
}

コンテンツのフォルダー内のすべてのファイルが "コンテンツ" としてマークされ、Visual Studio の [プロパティ] メニューで [新しい場合はコピーする] または [常にコピーする] に設定されていることを確認します。Make sure that all files inside of the content folder are marked as "Content" and set to "Copy if newer" or "Copy always" in Visual Studio’s Properties menu. これにより、展開するときに、ファイルが AppX パッケージに含められます。This ensures that the files will be inside your AppX Package when you deploy it.

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

既存の Device Portal のリソースと API を使用するUsing existing Device Portal resources and APIs

Device Portal プロバイダーによって提供される静的コンテンツは、コア Device Portal サービスと同じポートで提供されます。Static content served by a Device Portal provider is served on the same port as the core Device Portal service. つまり、HTML の単純な <link> および <script> タグで、Device Portal に含まれている既存の JS および CSS を参照できます。This means that you can reference the existing JS and CSS included with Device Portal with simple <link> and <script> tags in your HTML. 一般的に、すべてのコア Device Portal の REST API を便利な webbRest オブジェクトにラップする rest.js と、Device Portal の UI の他の部分に合わせてコンテンツのスタイルを設定できる common.css ファイルを使用することをお勧めします。In general, we suggest the use of rest.js, which wraps all the core Device Portal REST APIs in a convenient webbRest object, and the common.css file, which will allow you to style your content to fit with the rest of Device Portal's UI. この例については、サンプルに含まれる index.html ページをご覧ください。You can see an example of this in the index.html page included in the sample. この例では、rest.js を使用して、Device Portal からデバイス名と実行中のプロセスを取得します。It uses rest.js to retrieve the device name and running processes from Device Portal.

Device Portal プラグインの出力

重要な点は、webbRest で HttpPost/DeleteExpect200 メソッドを使用すると、自動的に CSRF 処理 が実行されるため、Web ページで状態が変化する REST API を呼び出すことができます。Importantly, use of the HttpPost/DeleteExpect200 methods on webbRest will automatically do the CSRF handling for you, which allows your webpage to call state-changing REST APIs.

注意

Device Portal に含まれている静的コンテンツは、重大な変更に対する保証は行われません。The static content included with Device Portal does not come with a guarantee against breaking changes. API は頻繁に変更することは想定されていませんが、特に common.jscontrols.js ファイルでは変更されることもあるため、プロバイダーでは使用しないでください。While the APIs are not expected to change often, they may, especially in the common.js and controls.js files, which your provider should not use.

Device Portal の接続をデバッグするDebugging the Device Portal connection

バック グラウンド タスクをデバッグするには、Visual Studio でコードを実行する方法を変更する必要があります。In order to debug your background task, you must change the way Visual Studio runs your code. アプリ サービスの接続をデバッグして、プロバイダーが HTTP 要求を処理する方法を調べるには、次の手順に従います。Follow the steps below for debugging an app service connection to inspect how your provider is handling the HTTP requests:

  1. [デバッグ] メニューから [DevicePortalProvider のプロパティ] を選択します。From the Debug menu, select DevicePortalProvider Properties.
  2. [デバッグ] タブの [開始動作] で、[起動しないが、開始時にマイ コードをデバッグする] を選択します。Under the Debugging tab, in the Start action section, select “Do not launch, but debug my code when it starts”.
    プラグインをデバッグ モードにする
  3. RequestReceived ハンドラー関数にブレークポイントを設定します。Set a breakpoint in your RequestReceived handler function. RequestReceived ハンドラーでのブレークポイントbreak point at requestreceived handler

注意

ビルドのアーキテクチャがターゲットのアーキテクチャと正確に一致することを確認してください。Make sure the build architecture matches the architecture of the target exactly. 64 ビット PC を使用している場合は、AMD64 ビルドを使って展開する必要があります。If you are using a 64-bit PC, you must deploy using an AMD64 build. 4. F5 キーを押してアプリを展開します。Press F5 to deploy your app 5. Device Portal をオフにし、再度オンにしてアプリを検出できるようにします (アプリ マニフェストを変更した場合にのみ必要。その他の場合は、単に再配置し、この手順を省略できます)。Turn Device Portal off, then turn it back on so that it finds your app (only needed when you change your app manifest – the rest of the time you can simply re-deploy and skip this step). 6. ブラウザーで、プロバイダーの名前空間にアクセスすると、ブレークポイントにヒットします。In your browser, access the provider's namespace, and the breakpoint should be hit.