Een RESTful-eindpunt maken voor aangepaste resourceproviders

Een aangepaste resourceprovider is een contract tussen Azure en een eindpunt. Met aangepaste resourceproviders kunt u werkstromen in Azure aanpassen. In deze zelfstudie ziet u hoe u een RESTful-eindpunt van een aangepaste resourceprovider kunt maken. Als u niet bekend bent met aangepaste resourceproviders van Azure, raadpleegt u het overzicht over aangepaste resourceproviders.

Notitie

Deze zelfstudie is gebaseerd op de zelfstudie Azure Functions instellen voor aangepaste resourceproviders. Sommige stappen in deze zelfstudie werken alleen als er een functie-app is ingesteld in Azure Functions om te werken met aangepaste resourceproviders.

Werken met aangepaste acties en aangepaste resources

In deze zelfstudie werkt u de functie-app bij zodat deze werkt als een RESTful-eindpunt voor uw aangepaste resourceprovider. Resources en acties in Azure zijn gemodelleerd volgens de volgende standaard-RESTful-specificatie:

  • PUT: Een nieuwe resource maken
  • GET (instantie) : Een bestaande resource ophalen
  • DELETE: Een bestaande resource verwijderen
  • POST: Een actie activeren
  • GET (verzameling) : Alle bestaande resources weergeven

Voor deze zelfstudie gebruikt u Azure Table Storage, maar elke database of opslagservice werkt.

Aangepaste resources in de opslag partitioneren

Omdat u een RESTful-service maakt, moet u de gemaakte resources opslaan. Voor Azure-tabelopslag moet u partitie- en rijsleutels genereren voor uw gegevens. Voor aangepaste resourceproviders moeten gegevens worden gepartitioneerd naar de aangepaste resourceprovider. Wanneer een binnenkomende aanvraag naar de aangepaste resourceprovider wordt verzonden, voegt de aangepaste resourceprovider de x-ms-customproviders-requestpath header toe aan uitgaande aanvragen aan het eindpunt.

In het volgende voorbeeld ziet u een header x-ms-customproviders-requestpath voor een aangepaste resource:

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

Op basis van de x-ms-customproviders-requestpath header kunt u de parameters partitionKey en rowKey voor uw opslag maken, zoals wordt weergegeven in de volgende tabel:

Parameter Template Beschrijving
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Met de parameter partitionKey wordt opgegeven hoe de gegevens worden gepartitioneerd. Meestal worden de gegevens gepartitioneerd door het exemplaar van de aangepaste resourceprovider.
rowKey {myResourceType}:{myResourceName} Met de parameter rowKey wordt de afzonderlijke id voor de gegevens opgegeven. Meestal is de id de naam van de resource.

U moet ook een nieuwe klasse maken om de aangepaste resource te modelleren. In deze zelfstudie voegt u de volgende klasse CustomResource toe aan de functie-app:

// 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 is een eenvoudige, generieke klasse die alle invoergegevens accepteert. Het is gebaseerd op ITableEntity, dat wordt gebruikt om gegevens op te slaan. De klasse CustomResource implementeert alle eigenschappen van interface ITableEntity: timestamp, eTag, partitionKey en rowKey.

Restful-methoden van aangepaste resourceprovider ondersteunen

Notitie

Als u de code niet rechtstreeks vanuit deze zelfstudie kopieert, moet de inhoud van het antwoord een geldige JSON zijn waarmee de header Content-Type wordt ingesteld op application/json.

Nu u het partitioneren van gegevens hebt ingesteld, maakt u de standaard-CRUD-methode en triggermethode voor aangepaste resources en aangepaste acties. Omdat aangepaste resourceproviders fungeren als proxy's, moet het RESTful-eindpunt de aanvraag en het antwoord modelleren en verwerken. De volgende codefragmenten laten zien hoe u de eenvoudige standaard-RESTful-bewerkingen kunt verwerken.

Een aangepaste actie activeren

Voor aangepaste resourceproviders wordt een aangepaste actie geactiveerd via POST-aanvragen. Een aangepaste actie kan optioneel een aanvraagtekst accepteren die een set invoerparameters bevat. De actie retourneert vervolgens een antwoord met het resultaat van de actie en of deze is geslaagd of mislukt.

Voeg de volgende methode TriggerCustomAction toe aan de functie-app:

/// <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;
}

De methode TriggerCustomAction accepteert een binnenkomende aanvraag en echot het antwoord met een statuscode.

Een aangepaste resource maken

Voor aangepaste resourceproviders wordt een aangepaste resource gemaakt via PUT-aanvragen. De aangepaste resourceprovider accepteert een JSON-aanvraagbody, die een set eigenschappen voor de aangepaste resource bevat. Resources in Azure volgen een RESTful-model. U kunt dezelfde aanvraag-URL gebruiken om een resource te maken, op te halen of te verwijderen.

Voeg de volgende methode CreateCustomResource toe om nieuwe resources te maken:

/// <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;
}

Met de methode CreateCustomResource wordt de binnenkomende aanvraag bijgewerkt met de Azure-specifieke velden id, naam en type. Deze velden zijn eigenschappen op het hoogste niveau die worden gebruikt voor services in Azure. Hiermee kan de aangepaste resourceprovider samenwerken met andere services, zoals Azure Policy, Azure Resource Manager-sjablonen en Azure-activiteitenlogboek.

Eigenschap Voorbeeld Beschrijving
name {myCustomResourceName} De naam van de aangepaste resource
type Microsoft.CustomProviders/resourceProviders/{resourceTypeName} De naamruimte resource-type
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
De resource-id

Naast het toevoegen van de eigenschappen hebt u het JSON-document ook opgeslagen in Azure-tabelopslag.

Een aangepaste resource ophalen

Voor aangepaste resourceproviders wordt een aangepaste resource opgehaald via GET-aanvragen. Een aangepaste resourceprovider accepteert geen JSON-aanvraagbody. Voor GET-aanvragen gebruikt het eindpunt de header x-ms-customproviders-requestpath om de al gemaakte resource te retourneren.

Voeg de volgende methode RetrieveCustomResource toe om bestaande resources op te halen:

/// <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;
}

Resources in Azure volgen een RESTful-model. De aanvraag-URL waarmee een resource wordt gemaakt, retourneert ook de resource als een GET-aanvraag wordt uitgevoerd.

Een aangepaste resource verwijderen

Voor aangepaste resourceproviders wordt een aangepaste resource verwijderd via DELETE-aanvragen. Een aangepaste resourceprovider accepteert geen JSON-aanvraagbody. Voor DELETE-aanvragen gebruikt het eindpunt de header x-ms-customproviders-requestpath om de al gemaakte resource te verwijderen.

Voeg de volgende methode RemoveCustomResource toe om bestaande resources te verwijderen:

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

Resources in Azure volgen een RESTful-model. De aanvraag-URL waarmee een resource wordt gemaakt, verwijdert de resource ook als een DELETE-aanvraag wordt uitgevoerd.

Alle aangepaste resources weergeven

Voor aangepaste resourceproviders kunt u een lijst met bestaande aangepaste resources opsommen met behulp van GET-aanvragen voor verzamelingen. Een aangepaste resourceprovider accepteert geen JSON-aanvraagbody. Voor een verzameling GET-aanvragen gebruikt het eindpunt de header x-ms-customproviders-requestpath om de al gemaakte resource op te sommen.

Voeg de volgende methode EnumerateAllCustomResources toe om de bestaande resources op te sommen:

/// <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;
}

Notitie

De RowKey QueryComparisons.GreaterThan en QueryComparisons.LessThan vormen syntaxis voor Azure-tabelopslag voor het uitvoeren van een startswith-query voor tekenreeksen.

Als u alle bestaande resources wilt weergeven, genereert u een Azure Table Storage-query die ervoor zorgt dat de resources aanwezig zijn onder de partitie van uw aangepaste resourceprovider. De query controleert vervolgens of de rij begint met dezelfde waarde {myResourceType}.

RESTful-bewerkingen integreren

Nadat alle RESTful-methoden zijn toegevoegd aan de functie-app, werkt u de methode Run bij waarmee de functies worden aangeroepen voor het verwerken van de verschillende REST-aanvragen:

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

De bijgewerkte Run-methode bevat nu de tableClient-invoerbinding die u hebt toegevoegd voor Azure Table Storage. Met het eerste deel van de methode wordt de header x-ms-customproviders-requestpath gelezen en de bibliotheek Microsoft.Azure.Management.ResourceManager.Fluent gebruikt om de waarde te parseren als een resource-id. De x-ms-customproviders-requestpath header wordt verzonden door de aangepaste resourceprovider en geeft het pad van de binnenkomende aanvraag op.

Door de geparseerde resource-id te gebruiken kunt u de waarden partitionKey en rowKey genereren voor de gegevens, om aangepaste resources te zoeken of op te slaan.

Nadat u de methoden en klassen hebt toegevoegd, moet u de gebruiksmethoden bijwerken voor de functie-app. Voeg de volgende code toe bovenaan het C#-bestand:

#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;

Als u op een bepaald moment van deze zelfstudie verdwaalt, kunt u het volledige codevoorbeeld vinden in de C# RESTful-eindpuntreferentie van de aangepaste resourceprovider. Nadat u de functie-app hebt voltooid, slaat u de URL van de functie-app op. Deze kan worden gebruikt om de functie-app in latere zelfstudies te activeren.

Volgende stappen

In dit artikel hebt u een RESTful-eindpunt gemaakt om te werken met een eindpunt van een aangepaste Azure-resourceprovider. Ga naar het artikel Een aangepaste resourceprovider maken en gebruiken voor meer informatie over het maken van een aangepaste resourceprovider.