Creare un endpoint RESTful per i provider di risorse personalizzati

Un provider di risorse personalizzato è un contratto tra Azure e un endpoint. Con i provider di risorse personalizzati, è possibile personalizzare i flussi di lavoro in Azure. Questa esercitazione illustra come creare un endpoint RESTful del provider di risorse personalizzato. Se non si ha familiarità con provider di risorse personalizzate di Azure, vedere la panoramica sui provider di risorse personalizzati.

Nota

Questa esercitazione si basa sull'esercitazione Configurare Funzioni di Azure per i provider di risorse personalizzati. Alcuni dei passaggi descritti in questa esercitazione funzionano solo se un'app per le funzioni è stata configurata in Funzioni di Azure per usare i provider di risorse personalizzati.

Usare azioni e risorse personalizzate

In questa esercitazione si aggiorna l'app per le funzioni come endpoint RESTful per il provider di risorse personalizzato. In Azure le risorse e le azioni sono modellate in base alla specifica RESTful di base seguente:

  • PUT: Creare una nuova risorsa
  • GET (istanza) : Recuperare una risorsa esistente
  • DELETE: Rimuovere una risorsa esistente
  • POST: Attivare un'azione
  • GET (raccolta) : Elencare tutte le risorse esistenti

Per questa esercitazione si usa Archiviazione tabelle di Azure, ma funziona qualsiasi database o servizio di archiviazione.

Partizionare le risorse personalizzate nell'archiviazione

Poiché si sta creando un servizio RESTful, è necessario archiviare le risorse create. Per archiviazione tabelle di Azure, è necessario generare le chiavi di partizione e di riga per i dati. Per i provider di risorse personalizzati, i dati devono essere partizionati nel provider di risorse personalizzato. Quando viene inviata una richiesta in ingresso al provider di risorse personalizzato, il provider di risorse personalizzato aggiunge l'intestazione x-ms-customproviders-requestpath alle richieste in uscita all'endpoint.

L'esempio seguente mostra un'intestazione x-ms-customproviders-requestpath per una risorsa personalizzata:

X-MS-CustomProviders-RequestPath: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/{myResourceType}/{myResourceName}

In base all'intestazione, è possibile creare i parametri partitionKey e rowKey per l'archiviazionex-ms-customproviders-requestpath, come illustrato nella tabella seguente:

Parametro Modello Descrizione
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Il parametro partitionKey specifica come vengono partizionati i dati. In genere i dati vengono partizionati dall'istanza del provider di risorse personalizzata.
rowKey {myResourceType}:{myResourceName} Il parametro rowKey specifica l'identificatore individuale per i dati. In genere l'identificatore corrisponde al nome della risorsa.

È anche necessario creare una nuova classe per modellare la risorsa personalizzata. In questa esercitazione aggiungere la classe CustomResource seguente all'app per le funzioni:

// Custom Resource Table Entity
public class CustomResource : ITableEntity
{
    public string Data { get; set; }

    public string PartitionKey { get; set; }

    public string RowKey { get; set; }

    public DateTimeOffset? Timestamp { get; set; }

    public ETag ETag { get; set; }
}

CustomResource è una semplice classe generica che accetta qualsiasi dato di input. Si basa su ITableEntity, usato per archiviare i dati. La classe CustomResource implementa tutte le proprietà dell'interfaccia ITableEntity: timestamp, eTag, partitionKey e rowKey.

Supportare i metodi RESTful del provider di risorse personalizzati

Nota

Se il codice non viene copiato direttamente da questa esercitazione, il contenuto della risposta deve essere un codice JSON valido che imposta l'intestazione Content-Type su application/json.

Dopo aver configurato il partizionamento dei dati,creare il CRUD di base e attivare i metodi per le risorse e le azioni personalizzate. Poiché i provider di risorse personalizzati fungono da proxy, l'endpoint RESTful deve modellare e gestire la richiesta e la risposta. I frammenti di codice seguenti mostrano come gestire le operazioni RESTful di base.

Attivare un'azione personalizzata

Per i provider di risorse personalizzati, viene attivata un'azione personalizzata tramite le richieste POST. Un'azione personalizzata può facoltativamente accettare un corpo della richiesta che contiene un set di parametri di input. L'azione restituisce quindi una risposta che ne segnala il risultato e indica se è riuscita o meno.

Aggiungere il metodo TriggerCustomAction seguente all'app per le funzioni:

/// <summary>
/// Triggers a custom action with some side effects.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <returns>The HTTP response result of the custom action.</returns>
public static async Task<HttpResponseMessage> TriggerCustomAction(HttpRequestMessage requestMessage)
{
    var myCustomActionRequest = await requestMessage.Content.ReadAsStringAsync();

    var actionResponse = requestMessage.CreateResponse(HttpStatusCode.OK);
    actionResponse.Content = myCustomActionRequest != string.Empty ? 
        new StringContent(JObject.Parse(myCustomActionRequest).ToString(), System.Text.Encoding.UTF8, "application/json") :
        null;
    return actionResponse;
}

Il metodo TriggerCustomAction accetta una richiesta in ingresso ed esegue l'eco della risposta con un codice di stato.

Creare una risorsa personalizzata

Per i provider di risorse personalizzati, viene creata una risorsa personalizzata tramite le richieste PUT. Il provider di risorse personalizzato accetta un corpo della richiesta JSON che contiene un set di proprietà per la risorsa personalizzata. In Azure le risorse seguono un modello RESTful. È possibile usare lo stesso URL della richiesta per creare, recuperare o eliminare una risorsa.

Aggiungere il metodo CreateCustomResource seguente per creare nuove risorse:

/// <summary>
/// Creates a custom resource and saves it to table storage.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableClient">The client that allows you to interact with Azure Tables hosted in either Azure storage accounts or Azure Cosmos DB table API.</param>
/// <param name="azureResourceId">The parsed Azure resource ID.</param>
/// <param name="partitionKey">The partition key for storage. This is the custom resource provider ID.</param>
/// <param name="rowKey">The row key for storage. This is '{resourceType}:{customResourceName}'.</param>
/// <returns>The HTTP response containing the created custom resource.</returns>
public static async Task<HttpResponseMessage> CreateCustomResource(HttpRequestMessage requestMessage, TableClient tableClient, ResourceId azureResourceId, string partitionKey, string rowKey)
{
    // Adds the Azure top-level properties.
    var myCustomResource = JObject.Parse(await requestMessage.Content.ReadAsStringAsync());
    myCustomResource["name"] = azureResourceId.Name;
    myCustomResource["type"] = azureResourceId.FullResourceType;
    myCustomResource["id"] = azureResourceId.Id;

    // Save the resource into storage.
    var customEntity =  new CustomResource
    {
        PartitionKey = partitionKey,
        RowKey = rowKey,
        Data = myCustomResource.ToString(),
    });
    await tableClient.AddEntity(customEntity);

    var createResponse = requestMessage.CreateResponse(HttpStatusCode.OK);
    createResponse.Content = new StringContent(myCustomResource.ToString(), System.Text.Encoding.UTF8, "application/json");
    return createResponse;
}

Il metodo CreateCustomResource aggiorna la richiesta in ingresso per includere i campi specifici di Azure: id, name e type. Questi campi sono proprietà di primo livello usate da tutti i servizi di Azure. Consentono al provider di risorse personalizzato di interagire con altri servizi come Criteri di Azure, modelli di Azure Resource Manager e Log attività di Azure.

Proprietà Esempio Descrizione
nome {myCustomResourceName} Il nome della risorsa personalizzata
type Microsoft.CustomProviders/resourceProviders/{resourceTypeName} Lo spazio dei nomi del tipo di risorsa
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
L'ID della risorsa

Oltre ad aggiungere le proprietà, viene anche salvato il documento JSON in archiviazione tabelle di Azure.

Recuperare una risorsa personalizzata

Per i provider di risorse personalizzati, una risorsa personalizzata viene recuperata tramite richieste GET. Un provider di risorse personalizzato non accetta un corpo della richiesta JSON. Per le richieste GET, l'endpoint usa l'intestazione x-ms-customproviders-requestpath per restituire la risorsa già creata.

Aggiungere il metodo RetrieveCustomResource seguente per recuperare le risorse esistenti:

/// <summary>
/// Retrieves a custom resource.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableClient">The client that allows you to interact with Azure Tables hosted in either Azure storage accounts or Azure Cosmos DB table API.</param>
/// <param name="partitionKey">The partition key for storage. This is the custom resource provider ID.</param>
/// <param name="rowKey">The row key for storage. This is '{resourceType}:{customResourceName}'.</param>
/// <returns>The HTTP response containing the existing custom resource.</returns>
public static async Task<HttpResponseMessage> RetrieveCustomResource(HttpRequestMessage requestMessage, TableClient tableClient, string partitionKey, string rowKey)
{
    // Attempt to retrieve the Existing Stored Value
    var queryResult = tableClient.GetEntityAsync<CustomResource>(partitionKey, rowKey);
    var existingCustomResource = (CustomResource)queryResult.Result;

    var retrieveResponse = requestMessage.CreateResponse(
        existingCustomResource != null ? HttpStatusCode.OK : HttpStatusCode.NotFound);

    retrieveResponse.Content = existingCustomResource != null ?
            new StringContent(existingCustomResource.Data, System.Text.Encoding.UTF8, "application/json"):
            null;
    return retrieveResponse;
}

In Azure le risorse seguono un modello RESTful. L'URL della richiesta che crea una risorsa è anche quello che la restituisce se viene eseguita una richiesta GET.

Rimuovere una risorsa personalizzata

Per i provider di risorse personalizzati, una risorsa personalizzata viene rimossa tramite richieste DELETE. Un provider di risorse personalizzato non accetta un corpo della richiesta JSON. Per le richieste DELETE, l'endpoint usa l'intestazione x-ms-customproviders-requestpath per eliminare la risorsa già creata.

Aggiungere il metodo RemoveCustomResource seguente per rimuovere le risorse esistenti:

/// <summary>
/// Removes an existing custom resource.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableClient">The client that allows you to interact with Azure Tables hosted in either Azure storage accounts or Azure Cosmos DB table API.</param>
/// <param name="partitionKey">The partition key for storage. This is the custom resource provider ID.</param>
/// <param name="rowKey">The row key for storage. This is '{resourceType}:{customResourceName}'.</param>
/// <returns>The HTTP response containing the result of the deletion.</returns>
public static async Task<HttpResponseMessage> RemoveCustomResource(HttpRequestMessage requestMessage, TableClient tableClient, string partitionKey, string rowKey)
{
    // Attempt to retrieve the Existing Stored Value
    var queryResult = tableClient.GetEntityAsync<CustomResource>(partitionKey, rowKey);
    var existingCustomResource = (CustomResource)queryResult.Result;

    if (existingCustomResource != null) {
        await tableClient.DeleteEntity(deleteEntity.PartitionKey, deleteEntity.RowKey);
    }

    return requestMessage.CreateResponse(
        existingCustomResource != null ? HttpStatusCode.OK : HttpStatusCode.NoContent);
}

In Azure le risorse seguono un modello RESTful. L'URL della richiesta che crea una risorsa è anche quello che la elimina se viene eseguita una richiesta DELETE.

Elenco di tutte le risorse personalizzate

Per i provider di risorse personalizzati, è possibile enumerare un elenco di risorse personalizzate esistenti usando le richieste GET di raccolta. Un provider di risorse personalizzato non accetta un corpo della richiesta JSON. Per una raccolta di richieste GET, l'endpoint usa l'intestazione x-ms-customproviders-requestpath per enumerare le richieste già create.

Aggiungere il metodo EnumerateAllCustomResources seguente per enumerare le risorse esistenti:

/// <summary>
/// Enumerates all the stored custom resources for a given type.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableClient">The client that allows you to interact with Azure Tables hosted in either Azure storage accounts or Azure Cosmos DB table API.</param>
/// <param name="partitionKey">The partition key for storage. This is the custom resource provider ID.</param>
/// <param name="resourceType">The resource type of the enumeration.</param>
/// <returns>The HTTP response containing a list of resources stored under 'value'.</returns>
public static async Task<HttpResponseMessage> EnumerateAllCustomResources(HttpRequestMessage requestMessage, TableClient tableClient, string partitionKey, string resourceType)
{
    // Generate upper bound of the query.
    var rowKeyUpperBound = new StringBuilder(resourceType);
    rowKeyUpperBound[rowKeyUpperBound.Length - 1]++;

    // Create the enumeration query.
    var queryResultsFilter = tableClient.Query<CustomResource>(filter: $"PartitionKey eq '{partitionKey}' and RowKey lt '{rowKeyUpperBound.ToString()}' and RowKey ge '{resourceType}'")
    
    var customResources = await queryResultsFilter.ToList().Select(customResource => JToken.Parse(customResource.Data));

    var enumerationResponse = requestMessage.CreateResponse(HttpStatusCode.OK);
    enumerationResponse.Content = new StringContent(new JObject(new JProperty("value", customResources)).ToString(), System.Text.Encoding.UTF8, "application/json");
    return enumerationResponse;
}

Nota

RowKey QueryComparisons.GreaterThan e QueryComparisons.LessThan rappresentano la sintassi di archiviazione tabelle di Azure per l'esecuzione di una query "startswith" per la ricerca di stringhe.

Per elencare tutte le risorse esistenti, generare una query di archiviazione tabelle di Azure che garantisce che le risorse esistano nella partizione del provider di risorse personalizzata. La query verifica quindi che la chiave di riga inizi con lo stesso valore {myResourceType}.

Integrare operazioni RESTful

Dopo aver aggiunto tutti i metodi RESTful all'app per le funzioni, è possibile aggiornare il metodo Run principale per chiamare le funzioni e gestire le diverse richieste REST:

/// <summary>
/// Entry point for the function app webhook that acts as the service behind a custom resource provider.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="log">The logger.</param>
/// <param name="tableClient">The client that allows you to interact with Azure Tables hosted in either Azure storage accounts or Azure Cosmos DB table API.</param>
/// <returns>The HTTP response for the custom Azure API.</returns>
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log, TableClient tableClient)
{
    // Get the unique Azure request path from request headers.
    var requestPath = req.Headers.GetValues("x-ms-customproviders-requestpath").FirstOrDefault();

    if (requestPath == null)
    {
        var missingHeaderResponse = req.CreateResponse(HttpStatusCode.BadRequest);
        missingHeaderResponse.Content = new StringContent(
            new JObject(new JProperty("error", "missing 'x-ms-customproviders-requestpath' header")).ToString(),
            System.Text.Encoding.UTF8, 
            "application/json");
    }

    log.LogInformation($"The Custom Resource Provider Function received a request '{req.Method}' for resource '{requestPath}'.");

    // Determines if it is a collection level call or action.
    var isResourceRequest = requestPath.Split('/').Length % 2 == 1;
    var azureResourceId = isResourceRequest ? 
        ResourceId.FromString(requestPath) :
        ResourceId.FromString($"{requestPath}/");

    // Create the Partition Key and Row Key
    var partitionKey = $"{azureResourceId.SubscriptionId}:{azureResourceId.ResourceGroupName}:{azureResourceId.Parent.Name}";
    var rowKey = $"{azureResourceId.FullResourceType.Replace('/', ':')}:{azureResourceId.Name}";

    switch (req.Method)
    {
        // Action request for a custom action.
        case HttpMethod m when m == HttpMethod.Post && !isResourceRequest:
            return await TriggerCustomAction(
                requestMessage: req);

        // Enumerate request for all custom resources.
        case HttpMethod m when m == HttpMethod.Get && !isResourceRequest:
            return await EnumerateAllCustomResources(
                requestMessage: req,
                tableClient: tableClient,
                partitionKey: partitionKey,
                resourceType: rowKey);

        // Retrieve request for a custom resource.
        case HttpMethod m when m == HttpMethod.Get && isResourceRequest:
            return await RetrieveCustomResource(
                requestMessage: req,
                tableClient: tableClient,
                partitionKey: partitionKey,
                rowKey: rowKey);

        // Create request for a custom resource.
        case HttpMethod m when m == HttpMethod.Put && isResourceRequest:
            return await CreateCustomResource(
                requestMessage: req,
                tableClient: tableClient,
                azureResourceId: azureResourceId,
                partitionKey: partitionKey,
                rowKey: rowKey);

        // Remove request for a custom resource.
        case HttpMethod m when m == HttpMethod.Delete && isResourceRequest:
            return await RemoveCustomResource(
                requestMessage: req,
                tableClient: tableClient,
                partitionKey: partitionKey,
                rowKey: rowKey);

        // Invalid request received.
        default:
            return req.CreateResponse(HttpStatusCode.BadRequest);
    }
}

Il metodo Run aggiornato include ora l'associazione di input tableClient aggiunta per l'archiviazione tabelle di Azure. La prima parte del metodo legge l'intestazione x-ms-customproviders-requestpath e usa la libreria Microsoft.Azure.Management.ResourceManager.Fluent per analizzare il valore come ID risorsa. L'intestazione x-ms-customproviders-requestpath viene inviata dal provider di risorse personalizzato e specifica il percorso della richiesta in ingresso.

Usando l'ID risorsa analizzato, è ora possibile generare i valori di partitionKey e rowKey per i dati per cercare o archiviare risorse personalizzate.

Dopo aver aggiunto i metodi e le classi, è necessario aggiornare i metodi using per l'app per le funzioni. Aggiungere il codice seguente all'inizio del file C#:

#r "Newtonsoft.Json"
#r "Microsoft.WindowsAzure.Storage"
#r "../bin/Microsoft.Azure.Management.ResourceManager.Fluent"

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Configuration;
using System.Text;
using System.Threading;
using System.Globalization;
using System.Collections.Generic;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Azure.Data.Table;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Se si perde in qualsiasi momento di questa esercitazione, è possibile trovare l'esempio di codice completo nel riferimento all'endpoint RESTful C# del provider di risorse personalizzato. Dopo aver completato l'app per le funzioni, salvare il relativo URL. Può essere usato per attivare l'app per le funzioni nelle esercitazioni successive.

Passaggi successivi

In questo articolo è stato creato un endpoint RESTful per usare un endpoint del provider di risorse personalizzato di Azure. Per informazioni su come creare un provider di risorse personalizzato, vedere l'articolo Creare e usare un provider di risorse personalizzato.