WDP(Windows 디바이스 포털)를 사용하여 웹 페이지를 호스트하고 진단 정보를 제공하는 UWP 앱을 작성하는 방법을 알아봅니다.
Windows 10 크리에이터스 업데이트(버전 1703, 빌드 15063)부터 디바이스 포털을 사용하여 앱의 진단 인터페이스를 호스트할 수 있습니다. 이 문서에서는 앱에 대한 DevicePortalProvider를 만드는 데 필요한 세 가지 부분, 즉 애플리케이션 패키지 매니페스트 변경, 디바이스 포털 서비스에 대한 앱 연결 설정 및 들어오는 요청 처리에 대해 설명합니다.
새 UWP 앱 프로젝트 만들기
Microsoft Visual Studio에서 새 UWP 앱 프로젝트를 만듭니다.
애플리케이션 패키지 매니페스트에 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">
...
앱이 디바이스 포털 공급자임을 선언하려면 앱 서비스 및 앱 서비스를 사용하는 새 디바이스 포털 공급자 확장을 만들어야 합니다.
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"보다는 "/MyNamespace/api"가 있는 공급자와 일치합니다.
이 기능에는 두 가지 새로운 기능이 필요합니다. 또한 package.appxmanifest 파일에 추가해야 합니다.
...
<Capabilities>
...
<Capability Name="privateNetworkClientServer" />
<rescap:Capability Name="devicePortalProvider" />
</Capabilities>
...
비고
기능 "devicePortalProvider"는 제한된("rescap") 기능으로, 스토어에 앱을 게시하기 전에 사전 승인을 받아야 합니다. 그러나 이로 인해 사이드로딩을 통해 로컬에서 앱을 테스트하는 것이 불가능해지지 않습니다. 제한된 기능에 대한 자세한 내용은 앱 기능 선언을 참조하세요.
백그라운드 작업 및 WinRT 구성 요소 설정
디바이스 포털 연결을 설정하려면 앱이 디바이스 포털 서비스의 앱 서비스 연결을 앱 내에서 실행되는 디바이스 포털 인스턴스와 연결해야 합니다. 이렇게 하려면 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에서는 요청을 임시로 저장하고, 앱의 백그라운드 작업을 시작하며, 실행 메서드를 호출하여 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;
}
요청 처리 루프를 완료하기 위해 앱에서 처리해야 하는 두 가지 이벤트가 있습니다: 디바이스 포털 서비스가 종료될 때 발생하는 Closed이벤트와, 들어오는 HTTP 요청을 처리하고 디바이스 포털 공급자의 주요 기능을 제공하는 RequestReceived이벤트입니다.
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 인스턴스로 추가됩니다. "String" 및 "Buffer"와 같은 다른 IHttpContent 클래스도 허용됩니다.
그러면 응답이 HTTP 응답으로 설정되고 200(정상) 상태 코드가 제공됩니다. 원래 호출을 수행한 브라우저에서 예상대로 렌더링되어야 합니다. RequestReceived 이벤트 처리기가 반환되면 응답 메시지가 사용자 에이전트에 자동으로 반환됩니다. 추가 "send" 메서드는 필요하지 않습니다.
정적 콘텐츠 제공
정적 콘텐츠는 패키지 내의 폴더에서 직접 제공되므로 공급자에 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 패키지 내에 있게 됩니다.
기존 디바이스 포털 리소스 및 API 사용
디바이스 포털 공급자가 제공하는 정적 콘텐츠는 핵심 디바이스 포털 서비스와 동일한 포트에서 제공됩니다. 즉, HTML에서 단순 <link> 및 태그를 사용하여 디바이스 포털에 포함된 기존 JS 및 <script> CSS를 참조할 수 있습니다. 일반적으로 우리는 모든 핵심 Device Portal REST API를 편리한 webbRest 개체로 래핑하는 rest.js 사용을 권장하며, 나머지 디바이스 포털 UI에 맞게 콘텐츠의 스타일을 지정할 수 있도록 common.css 파일을 사용합니다. 이 예제는 샘플에 포함된 index.html 페이지에서 확인할 수 있습니다.
rest.js 사용하여 디바이스 포털에서 디바이스 이름 및 실행 중인 프로세스를 검색합니다.
중요한 것은, webRest에서 HttpPost/DeleteExpect200 메서드를 사용하면, CSRF 처리가 자동으로 수행되어 웹 페이지에서 상태를 변경하는 REST API를 호출할 수 있다는 점입니다.
비고
디바이스 포털에 포함된 정적 콘텐츠는 호환성을 깨트리는 변경 사항에 대한 보장을 제공하지 않습니다. API는 자주 변경되지 않을 것으로 예상되지만, 특히 공급자가 사용하지 않아야 하는 common.js 및 controls.js 파일에서 변경될 수 있습니다.
디바이스 포털 연결 디버깅
백그라운드 작업을 디버그하려면 Visual Studio에서 코드를 실행하는 방식을 변경해야 합니다. 다음 단계에 따라 앱 서비스 연결을 디버깅하여 공급자가 HTTP 요청을 처리하는 방법을 검사합니다.
- 디버그 메뉴에서 DevicePortalProvider 속성을 선택합니다.
- 디버깅 탭의 시작 작업 섹션에서 "시작하지 말고 코드가 시작될 때 디버깅 수행"을 선택합니다.
- RequestReceived 처리기 함수에서 중단점을 설정합니다.
비고
빌드 아키텍처가 대상의 아키텍처와 정확히 일치하는지 확인합니다. 64비트 PC를 사용하는 경우 AMD64 빌드를 사용하여 배포해야 합니다. 4. F5 키를 눌러 앱을 배포합니다. 5. 디바이스 포털을 껐다가 다시 켜서 앱을 찾을 수 있도록 합니다(앱 매니페스트를 변경하는 경우에만 필요하며, 그 외에는 이 단계를 건너뛰고 다시 배포할 수 있습니다). 6. 브라우저에서 공급자의 네임스페이스에 액세스하면 중단점이 발동됩니다.