Написание пользовательского подключаемого модуля для портала устройств Windows

Узнайте, как создать приложение UWP, которое использует портал устройств Windows (WDP) для размещения веб-страницы и предоставления диагностических сведений.

Начиная с обновления Windows 10 Creators Update (версии 1703, сборки 15063), вы можете использовать портал устройств для размещения интерфейсов диагностики приложения. В этой статье рассматривается три обязательных элемента для создания DevicePortalProvider для приложения — изменение в манифесте пакета приложения, подключение приложения к службе портала устройств и обработка входящего запроса.

Создание нового проекта приложения UWP

В Microsoft Visual Studio создайте проект приложения UWP. Последовательно выберите Файл > Создать > Проект и выберите Пустое приложение (Универсальное приложение для Windows) для C#, затем нажмите Далее. Откроется диалоговое окно Настроить новый проект. Присвойте проекту имя DevicePortalProvider и щелкните Создать. Это приложение будет содержать службу приложений. Может потребоваться обновить 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">
    ...

Чтобы объявить, что приложение является поставщиком портала устройств, следует создать службу приложений и новое расширение поставщика портала устройств, использующее его. Добавьте расширение windows.appService и расширение windows.devicePortalProvider в элемент Extensions в Application. Убедитесь, что атрибуты 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"), то есть вам нужно получить предварительное разрешение в Store, чтобы опубликовать в нем приложение. Однако это не помешает локально тестировать приложение, загрузив его как неопубликованное. Дополнительные сведения об ограниченных возможностях см. в статье Объявления возможностей приложения.

Настройка фоновой задачи и компонента WinRT

Чтобы настроить подключение портала устройства, приложение необходимо подключить к службе приложения через службу портала устройства с экземпляром портала устройства, запущенного в приложении. Для этого добавьте новый компонент WinRT в приложение с помощью класса, который реализует IBackgroundTask.

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"). Когда вы выполните первый запрос к поставщику портала устройств, портал устройств спрячет этот запрос, затем запустит фоновое задание приложения, вызовет его метод 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;
}

Существует два события, которые должны обрабатываться приложением для завершения цикла обработки запросов: закрыто, когда служба портала устройств завершает работу, и RequestReceived, которая отображает входящие HTTP-запросы и предоставляет основную функциональность поставщика портала устройств.

Обработка события RequestReceived

Событие RequestReceived происходит один раз для каждого HTTP-запроса, выполняемого в маршруте обработчика для подключаемого модуля. Цикл обработки запросов для поставщиков портала устройств выполняется так же, как в 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. Другие классы IHttpContent, например для String и Buffer, также допустимы.

Ответ получает тип ответа HTTP и код состояния 200 (OK). Он должен правильно отображаться в браузере, из которого был сделан исходный вызов. Обратите внимание, что при возврате обработчика события RequestReceived агенту пользователя автоматически возвращается ответное сообщение, то есть дополнительный метод отправки не требуется.

device portal response message

Предоставление статического содержимого

Статическое содержимое может передаваться непосредственно из папки в пакете, что упрощает добавление пользовательского интерфейса для поставщика. Самый простой способ отображения статического содержимого — это создание папки содержимого в проекте, который можно сопоставить с URL-адресом.

device portal static content folder

Затем добавьте обработчик маршрутов в обработчик события 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.

configure static content file copying

Использование ресурсов и API существующего портала устройств

Статическое содержимое, которое обслуживается поставщиком портала устройств, доступно через тот же порт, что и основная служба портала устройств. Это означает, что вы можете ссылаться на существующие JS- и CSS-файлы, относящиеся к порталу устройств, используя простые теги <link> и <script> в HTML-коде. В целом, мы рекомендуем использовать файл rest.js, который объединяет все основные REST API портала устройств в удобном объекте webbRest, и файл common.css, позволяющий создать стиль содержимого в соответствии с интерфейсом пользователя портала устройств. Вариант такой реализации можно изучить на странице index.html, включенной в наш пример. Здесь rest.js используется для получения имени устройства и запуска процессов на портале устройств.

device portal plugin output

Важно, что при использовании методов HttpPost/DeleteExpect200 в webbRest обработка CSRF выполняется автоматически, что позволяет веб-странице вызывать REST API, которые изменяют состояние.

Примечание.

Статическое содержимое, относящиеся к порталу устройств, не имеет гарантий защиты от критических изменений. Интерфейсы API могут изменяться, хотя и не очень часто. Это в особенности относится к файлам common.js и controls.js, которые поставщик не должен использовать.

Отладка подключения к порталу устройств

Для отладки фоновой задачи вам нужно изменить способ запуска кода в Visual Studio. Выполните действия, описанные ниже, для отладки подключения службы приложения, чтобы проверить, как поставщик обрабатывает HTTP-запросы.

  1. В меню "Отладка" выберите "Свойства DevicePortalProvider".
  2. На вкладке "Отладка" в разделе "Действие при запуске" выберите параметр "Не запускать, а отлаживать мой код при открытии".
    put plugin in debug mode
  3. Установите точку останова в функции обработчика RequestReceived. break point at requestreceived handler

Примечание.

Убедитесь, что архитектура сборки точно соответствует архитектуре целевого объекта. Если вы используете 64-разрядный компьютер, развертывание необходимо выполнять с помощью сборки AMD64. 4. Нажмите клавишу F5, чтобы развернуть свое приложение 5. Отключите портал устройств, а затем снова включите его, чтобы он нашел ваше приложение (это необходимо только после изменения манифеста приложения, а в остальных случаях можно просто развертывать приложение, пропустив этот шаг). 6. В браузере перейдите к пространству имен поставщика, это должно привести к срабатыванию точки останова.