Skapa en RESTful-slutpunkt för anpassade resursprovidrar

En anpassad resursprovider är ett kontrakt mellan Azure och en slutpunkt. Med anpassade resursprovidrar kan du anpassa arbetsflöden i Azure. Den här självstudien visar hur du skapar en restful-slutpunkt för en anpassad resursprovider. Om du inte är bekant med anpassade Azure-resursprovidrar kan du läsa översikten över anpassade resursprovidrar.

Anteckning

Den här självstudien bygger på självstudien Konfigurera Azure Functions för anpassade resursprovidrar. Vissa av stegen i den här självstudien fungerar bara om en funktionsapp har konfigurerats i Azure Functions för att arbeta med anpassade resursprovidrar.

Arbeta med anpassade åtgärder och anpassade resurser

I den här självstudien uppdaterar du funktionsappen så att den fungerar som en RESTful-slutpunkt för din anpassade resursprovider. Resurser och åtgärder i Azure modelleras efter följande grundläggande RESTful-specifikation:

  • PUT: Skapa en ny resurs
  • GET (instans): Hämta en befintlig resurs
  • TA BORT: Ta bort en befintlig resurs
  • POST: Utlösa en åtgärd
  • GET (samling): Visa en lista över alla befintliga resurser

I den här självstudien använder du Azure Table Storage, men alla databaser eller lagringstjänster fungerar.

Partitionering av anpassade resurser i lagring

Eftersom du skapar en RESTful-tjänst måste du lagra de skapade resurserna. För Azure Table Storage måste du generera partitions- och radnycklar för dina data. För anpassade resursprovidrar ska data partitioneras till den anpassade resursprovidern. När en inkommande begäran skickas till den anpassade resursprovidern lägger x-ms-customproviders-requestpath den anpassade resursprovidern till huvudet i utgående begäranden till slutpunkten.

I följande exempel visas en x-ms-customproviders-requestpath rubrik för en anpassad resurs:

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

Baserat på x-ms-customproviders-requestpath rubriken kan du skapa parametrarna partitionKey och rowKey för din lagring enligt följande tabell:

Parameter Mall Beskrivning
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Parametern partitionKey anger hur data partitioneras. Vanligtvis partitioneras data av den anpassade resursproviderinstansen.
rowKey {myResourceType}:{myResourceName} Parametern rowKey anger den enskilda identifieraren för data. Vanligtvis är identifieraren namnet på resursen.

Du måste också skapa en ny klass för att modellera din anpassade resurs. I den här självstudien lägger du till följande CustomResource-klass i funktionsappen:

// 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 är en enkel, allmän klass som accepterar indata. Den baseras på ITableEntity, som används för att lagra data. Klassen CustomResource implementerar alla egenskaper från gränssnittet ITableEntity: tidsstämpel, eTag, partitionKey och rowKey.

Stöd för RESTful-metoder för anpassad resursprovider

Anteckning

Om du inte kopierar koden direkt från den här självstudien måste svarsinnehållet vara giltigt JSON som anger Content-Type rubriken till application/json.

Nu när du har konfigurerat datapartitionering skapar du de grundläggande CRUD- och utlösarmetoderna för anpassade resurser och anpassade åtgärder. Eftersom anpassade resursprovidrar fungerar som proxyservrar måste RESTful-slutpunkten modellera och hantera begäran och svaret. Följande kodfragment visar hur du hanterar de grundläggande RESTful-åtgärderna.

Utlösa en anpassad åtgärd

För anpassade resursprovidrar utlöses en anpassad åtgärd via POST-begäranden. En anpassad åtgärd kan också acceptera en begärandetext som innehåller en uppsättning indataparametrar. Åtgärden returnerar sedan ett svar som signalerar resultatet av åtgärden och om den lyckades eller misslyckades.

Lägg till följande TriggerCustomAction-metod i funktionsappen:

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

Metoden TriggerCustomAction accepterar en inkommande begäran och upprepar svaret med en statuskod.

Skapa en anpassad resurs

För anpassade resursprovidrar skapas en anpassad resurs via PUT-begäranden. Den anpassade resursprovidern accepterar en JSON-begärandetext som innehåller en uppsättning egenskaper för den anpassade resursen. Resurser i Azure följer en RESTful-modell. Du kan använda samma begärande-URL för att skapa, hämta eller ta bort en resurs.

Lägg till följande CreateCustomResource-metod för att skapa nya resurser:

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

Metoden CreateCustomResource uppdaterar den inkommande begäran så att den innehåller det Azure-specifika fält-ID, namn och typ. De här fälten är egenskaper på den översta nivån som används av tjänster i Azure. De låter den anpassade resursprovidern samverka med andra tjänster som Azure Policy, Azure Resource Manager-mallar och Azure Activity Log.

Egenskap Exempel Beskrivning
Namn {myCustomResourceName} Namnet på den anpassade resursen
typ Microsoft.CustomProviders/resourceProviders/{resourceTypeName} Namnområdet av resurstyp
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
Resurs-ID

Förutom att lägga till egenskaperna sparade du även JSON-dokumentet i Azure Table Storage.

Hämta en anpassad resurs

För anpassade resursprovidrar hämtas en anpassad resurs via GET-begäranden. En anpassad resursprovider accepterar inte en JSON-begärandetext. För GET-begäranden använder x-ms-customproviders-requestpath slutpunkten huvudet för att returnera den redan skapade resursen.

Lägg till följande RetrieveCustomResource-metod för att hämta befintliga resurser:

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

I Azure följer resurser en RESTful-modell. Begärande-URL:en som skapar en resurs returnerar också resursen om en GET-begäran utförs.

Ta bort en anpassad resurs

För anpassade resursprovidrar tas en anpassad resurs bort via DELETE-begäranden. En anpassad resursprovider accepterar inte en JSON-begärandetext. För en DELETE-begäran använder x-ms-customproviders-requestpath slutpunkten huvudet för att ta bort den redan skapade resursen.

Lägg till följande RemoveCustomResource-metod för att ta bort befintliga resurser:

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

I Azure följer resurser en RESTful-modell. Begärande-URL:en som skapar en resurs tar också bort resursen om en DELETE-begäran utförs.

Visa en lista över alla anpassade resurser

För anpassade resursprovidrar kan du räkna upp en lista över befintliga anpassade resurser med hjälp av get-begäranden för samlingen. En anpassad resursprovider accepterar inte en JSON-begärandetext. För en samling GET-begäranden använder x-ms-customproviders-requestpath slutpunkten huvudet för att räkna upp de resurser som redan har skapats.

Lägg till följande enumerateAllCustomResources-metod för att räkna upp befintliga resurser:

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

Anteckning

RowKey QueryComparisons.GreaterThan och QueryComparisons.LessThan är Azure Table Storage-syntax för att utföra en "startswith"-fråga för strängar.

Om du vill visa en lista över alla befintliga resurser genererar du en Azure Table Storage-fråga som säkerställer att resurserna finns under partitionen för din anpassade resursprovider. Frågan kontrollerar sedan att radnyckeln börjar med samma {myResourceType} värde.

Integrera RESTful-åtgärder

När alla RESTful-metoder har lagts till i funktionsappen uppdaterar du den huvudsakliga Run-metoden som anropar funktionerna för att hantera de olika REST-begärandena:

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

Den uppdaterade Körningsmetoden innehåller nu tableClient-indatabindningen som du har lagt till för Azure Table Storage. Den första delen av metoden läser x-ms-customproviders-requestpath huvudet och använder Microsoft.Azure.Management.ResourceManager.Fluent biblioteket för att parsa värdet som ett resurs-ID. Huvudet x-ms-customproviders-requestpath skickas av den anpassade resursprovidern och anger sökvägen till den inkommande begäran.

Med hjälp av det parsade resurs-ID:t kan du generera värdena partitionKey och rowKey så att data kan söka efter eller lagra anpassade resurser.

När du har lagt till metoderna och klasserna måste du uppdatera användningsmetoderna för funktionsappen. Lägg till följande kod överst i C#-filen:

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

Om du tappar bort dig någon gång i den här självstudien hittar du det fullständiga kodexemplet i referensen för den anpassade resursprovidern C# RESTful-slutpunkt. När du är klar med funktionsappen sparar du funktionsappens URL. Den kan användas för att utlösa funktionsappen i senare självstudier.

Nästa steg

I den här artikeln skapade du en RESTful-slutpunkt för att arbeta med en slutpunkt för en anpassad Azure-resursprovider. Om du vill lära dig hur du skapar en anpassad resursprovider går du till artikeln Skapa och använda en anpassad resursprovider.