共用方式為


撰寫 Windows Device Portal 的自定義外掛程式

瞭解如何撰寫使用 Windows 裝置入口網站 (WDP) 裝載網頁並提供診斷資訊的 UWP 應用程式。

從 Windows 10 Creators Update(版本 1703 組建 15063 開始),您可以使用 Device Portal 來裝載應用程式的診斷介面。 本文涵蓋為您的應用程式建立 DevicePortalProvider 所需的三個部分:修改 應用程式套件指令清單、設定應用程式與 裝置入口網站服務的連線,以及處理傳入的要求。

建立新的 UWP 應用程式專案

在 Microsoft Visual Studio 中,建立新的 UWP 應用程式專案。 移至 [檔案] [新增 專案],然後選取 [C#的 空白應用程式 [Windows 通用],然後按兩下 [下一步]。 在 [設定新專案] 對話框中。 將專案命名為 "DevicePortalProvider",然後點擊 建立。 這會是包含App Service 的應用程式。 您可能需要更新 Visual Studio 或安裝最新的 Windows SDK

將 devicePortalProvider 擴充功能新增至您的應用程式套件指令清單

您需要將一些程式碼添加到 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 Provider,您必須建立一個應用程式服務,以及一個使用此服務的新 Device Portal Provider 擴充功能。 在 Extensions 元素中的 Application 中新增 windows.appService 延伸模組和 windows.devicePortalProvider 延伸模組。 請確定 AppServiceName 每個延伸模組中的屬性相符。 這表示裝置入口網站服務可以啟動此應用程式服務來處理處理程式命名空間上的要求。

...   
<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 命名空間。 裝置入口網站服務接收到在那個命名空間上的任何 HTTP 要求(隱含帶有通配符),都會傳送到您的應用程式進行處理。 在此情況下,任何成功驗證的 HTTP 請求都會傳送至您的應用程式 <ip_address>/MyNamespace/api/*。 處理程式路由之間的衝突會透過「最長路徑優先」原則來解決:選擇符合更多請求的路由,這表示對 "/MyNamespace/api/foo" 的請求會匹配具有 "/MyNamespace/api" 的提供者的路由,而不是具有 "/MyNamespace" 的路由。

這項功能需要兩項新功能。 它們也必須新增至您的 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;
}

應用程式必須處理兩個事件才能完成要求處理迴圈:關閉,每當 Device Portal 服務關閉時,以及 RequestReceived,其會顯示傳入的 HTTP 要求並提供 Device Portal 提供者的主要功能。

處理 RequestReceived 事件

RequestReceived 事件將會針對外掛程式指定處理程式路由上所做的每個 HTTP 要求引發一次。 Device Portal 提供者的要求處理迴圈類似於 NodeJS Express 中的要求處理迴圈:要求和回應物件會與 事件一起提供,而處理程式會填入回應對象來回應。 在 Device Portal 提供者中, RequestReceived 事件及其處理程式會使用 Windows.Web.Http.HttpRequestMessageHttpResponseMessage 物件。

// 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格式建立字串。 這會新增至 Response 物件做為 HttpStringContent 實例。 也允許其他 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 服務位於相同的埠上。 這表示您可以參考裝置入口網站隨附的現有 JS 和 CSS,並在 HTML 中使用簡單的 <link><script> 標籤。 一般而言,我們建議使用 rest.js,其會將所有核心 Device Portal REST API 包裝在方便的 webbRest 物件中,以及 common.css 檔案,這可讓您將內容設定為符合裝置入口網站 UI 其餘部分的樣式。 您可以在範例中包含的 index.html 頁面中看到這個範例。 它會使用 rest.js ,從 Device Portal 擷取裝置名稱和執行中的進程。

裝置入口插件輸出

重要的是,在 webbRest 上使用 HttpPost/DeleteExpect200 方法會自動為您執行 CSRF 處理,這使您的網頁能夠呼叫用於狀態改變的 REST API。

備註

Device Portal 隨附的靜態內容不保證不會發生影響功能的變更。 儘管 API 預期不會經常變更,但它們仍可能會變更,特別是在提供者不應該使用的 common.jscontrols.js 檔案中。

偵錯裝置入口網站連線

若要偵錯背景工作,您必須變更 Visual Studio 執行程式碼的方式。 請遵循下列步驟來偵錯 App Service 連線,以檢查您的提供者如何處理 HTTP 要求:

  1. 從 [偵錯] 功能表中,選取「DevicePortalProvider 屬性」。
  2. 在 [偵錯] 索引標籤的 [啟動動作] 區域中,選擇 [不要啟動,但在開始時偵錯我的程式碼]。
    將外掛程式置於偵錯模式
  3. 在 RequestReceived 處理程式函式中設定斷點。 在 requestreceived 處理程式 斷點

備註

請確定組建架構完全符合目標的架構。 如果您使用 64 位電腦,則必須使用 AMD64 組建進行部署。 4.按 F5 部署您的應用程式 5。關閉 Device Portal,然後重新開啟它,讓它找到您的應用程式(只有在您變更應用程式指令清單時才需要 – 您只需要重新部署和略過此步驟的其餘時間)。 6. 在您的瀏覽器中存取提供者的命名空間,即應該中斷點。