Scrivere un plug-in personalizzato per il Portale di dispositivi di Windows

Informazioni su come scrivere un'app UWP che usa il Portale di dispositivi di Windows per ospitare una pagina Web e fornire informazioni diagnostiche.

A partire da Windows 10 Creators Update (versione 1703, build 15063), è possibile usare il Portale di dispositivi per ospitare le interfacce di diagnostica dell'app. Questo articolo descrive i tre componenti necessari per creare un progetto DevicePortalProvider per l'app: le modifiche al manifesto del pacchetto dell'applicazione, la configurazione della connessione dell'app al servizio Portale di dispositivi e la gestione di una richiesta in ingresso.

Creare un nuovo progetto di app UWP

Creare un nuovo progetto di app UWP in Microsoft Visual Studio. Passare a File > Nuovo > Progetto e selezionare App vuota (Windows Universal) per C# e quindi fare clic su Avanti. Nella finestra di dialogo Configura nuovo progetto. Denominare il progetto "DevicePortalProvider" e quindi fare clic su Crea. Questa sarà l'app che contiene il servizio app. Può essere necessario aggiornare Visual Studio o installare la versione più recente di Windows SDK.

Aggiungere l'estensione devicePortalProvider al manifesto del pacchetto dell'applicazione

Dovrai aggiungere codice al file package.appxmanifest per fare sì che la tua app funzioni come un plug-in del Portale di dispositivi. Prima di tutto, aggiungi le definizioni di spazio dei nomi seguenti nella parte superiore del file. Aggiungile anche all'attributo 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">
    ...

Per dichiarare che l'app è un provider del Portale di dispositivi, devi creare un servizio app e una nuova estensione Device Portal Provider che lo usa. Aggiungi sia l'estensione windows.appService che l'estensione windows.devicePortalProvider nell'elemento Extensions sotto Application. Assicurati che gli attributi AppServiceName corrispondano in ogni estensione. Ciò indica al servizio Portale di dispositivi che il servizio app può essere avviato per gestire le richieste nello spazio dei nomi del gestore.

...   
<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>
...

L'attributo HandlerRoute fa riferimento allo spazio dei nomi REST richiesto dalla tua app. Qualsiasi richiesta HTTP su tale spazio dei nomi (seguita in modo implicito da un carattere jolly) ricevuta dal servizio Portale di dispositivi verrà inviata alla tua app per essere gestita. In questo caso, qualsiasi richiesta HTTP autenticata correttamente a <ip_address>/MyNamespace/api/* verrà inviata alla tua app. I conflitti tra route dei gestori sono regolati tramite un controllo "longest wins", cioè viene selezionata la route che corrisponde maggiormente alle richieste; ad esempio, una richiesta a "/MyNamespace/api/foo" corrisponde a un provider con "/MyNamespace/api" anziché una con "/MyNamespace".

Due nuove funzionalità sono necessarie per questa funzionalità. Devono anche essere aggiunte al file package.appxmanifest.

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

Nota

La funzionalità "devicePortalProvider" è limitata ("rescap"), pertanto devi ottenere preventiva autorizzazione dallo Store per pubblicare l'app. Tuttavia, questo non ti impedirà di testare l'app in locale tramite sideload. Per altre informazioni sulle funzionalità limitate, vedi Dichiarazioni di funzionalità delle app.

Impostare l'attività in background e il componente WinRT

Per configurare la connessione a Device Portal, l'app deve stabilire una connessione al servizio app dal servizio Portale di dispositivi con l'istanza di Portale di dispositivi in esecuzione all'interno dell'app. A tale scopo, aggiungi un nuovo componente WinRT all'applicazione con una classe che implementa 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;
        //...
    }

Assicurati che il nome corrisponda allo spazio dei nomi e al nome della classe configurati da EntryPoint AppService ("MySampleProvider.SampleProvider"). Quando esegui la prima richiesta al provider di Portale di dispositivi, Portale di dispositivi accantona la richiesta, avvia l'attività in background della tua app, chiama il relativo metodo Run e passa IBackgroundTaskInstance. L'app la usa quindi per impostare un'istanza 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;
}

Ci sono due eventi che devono essere gestiti dall'app per completare il ciclo di gestione delle richieste: Chiuso, per ogni volta che il servizio Portale di dispositivi si arresta e RequestReceived, che consente di ripartire le richieste HTTP in ingresso e fornisce la funzionalità principale del provider di Portale di dispositivi.

Gestire l'evento RequestReceived

L'evento RequestReceived viene generato una volta per ogni richiesta HTTP che viene effettuata sulla route del gestore specificata del plug-in. Il ciclo di gestione delle richieste per i provider di Portale di dispositivi è simile a quello in NodeJS Express: gli oggetti request e response vengono forniti insieme all'evento e il gestore risponde compilando l'oggetto response. Nei provider di Portale di dispositivi, l'evento RequestReceived e i relativi gestori usano gli oggetti Windows.Web.Http.HttpRequestMessage e 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;            
    }
    //...
}

In questo gestore di richieste di esempio, eseguiamo innanzitutto il pull degli oggetti request e response dal parametro args, quindi creiamo una stringa con l'URL della richiesta e qualche elemento di formattazione HTML aggiuntiva. Questa viene aggiunta nell'oggetto Response come istanza HttpStringContent. Sono anche consentite altre classi IHttpContent, ad esempio quelle per "String" e "Buffer".

La risposta viene quindi impostata come risposta HTTP e le viene assegnato un codice di stato 200 (OK). Dovrebbe essere presentata come previsto nel browser che ha effettuato la chiamata originale. Nota che quando il gestore dell'evento RequestReceived restituisce il risultato, il messaggio di risposta viene automaticamente restituito all'agente utente: non è necessario alcun metodo "send" aggiuntivo.

device portal response message

Fornitura di contenuto statico

Il contenuto statico può essere servito direttamente da una cartella all'interno del pacchetto, facilitando l'aggiunta di un'interfaccia utente al provider. Il modo più semplice per servire il contenuto statico consiste nel creare una cartella di contenuto nel progetto che può essere mappata a un URL.

device portal static content folder

Quindi, aggiungi un gestore di route nel gestore dell'evento RequestReceived che rilevi le route di contenuto statico ed esegua il mapping di una richiesta in modo appropriato.

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");
    }
}

Assicurati che tutti i file all'interno della cartella di contenuto siano contrassegnati come "Contenuto" e impostati su "Copia se più recente" o "Copia sempre" nel menu Proprietà di Visual Studio. Ciò garantisce che i file siano all'interno del pacchetto AppX quando viene distribuito.

configure static content file copying

Uso delle risorse e delle API esistenti di Portale di dispositivi

Il contenuto statico fornito da un provider di Portale di dispositivi è servito sulla stessa porta del servizio Portale di dispositivi principale. Ciò significa che puoi fare riferimento ai file JS e CSS esistenti inclusi in Portale di dispositivi con semplici tag <link> e <script> nel codice HTML. In generale, consigliamo l'uso di rest.js, che esegue il wrapping di tutte le API REST di Portale di dispositivi principali in un pratico oggetto webbRest, e del file common.css, che consentirà di applicare uno stile al contenuto in modo da adattarlo al resto dell'interfaccia utente di Portale di dispositivi. Puoi vedere un esempio di questa operazione nella pagina index.html inclusa nell'esempio. Usa rest.js per recuperare il nome del dispositivo e i processi in esecuzione da Portale di dispositivi.

device portal plugin output

Importante da notare, l'uso dei metodi HttpPost/DeleteExpect200 su webbRest effettua automaticamente la gestione CSRF, che consente alla pagina Web di chiamare le API REST di modifica di stato.

Nota

Il contenuto statico incluso in Portale di dispositivi non viene fornito con una garanzia contro le modifiche importanti. Anche se è prevedibile che le API non siano soggette a modifiche frequenti, è possibile che accada, in particolare nei file common.js e controls.js, che il provider non deve usare.

Debug della connessione a Portale di dispositivi

Per eseguire il debug dell'attività in background, devi modificare il modo in cui Visual Studio esegue il codice. Segui i passaggi seguenti per il debug di una connessione al servizio app per controllare come il provider gestisce le richieste HTTP:

  1. Dal menu Debug, seleziona Proprietà DevicePortalProvider.
  2. Nella scheda Debug, nella sezione Azione di avvio, seleziona "Non eseguire il codice utente, ma eseguine il debug all'avvio".
    put plugin in debug mode
  3. Imposta un punto di interruzione nella funzione del gestore di RequestReceived. break point at requestreceived handler

Nota

Assicurati che l'architettura di compilazione corrisponda esattamente all'architettura di destinazione. Se usi un PC a 64 bit, devi eseguire la distribuzione usando una build AMD64. 4. Premi F5 per distribuire l'app 5. Disabilita Portale di dispositivi, quindi abilitalo nuovamente in modo che trovi l'app (azione necessaria solo quando modifichi il manifesto dell'app; altrimenti puoi semplicemente ridistribuire e ignorare questo passaggio). 6. Nel browser, accedi allo spazio dei nomi del provider e il punto di interruzione dovrebbe essere raggiunto.