Tworzenie punktu końcowego RESTful dla niestandardowych dostawców zasobów

Niestandardowy dostawca zasobów to kontrakt między platformą Azure a punktem końcowym. Za pomocą niestandardowych dostawców zasobów można dostosować przepływy pracy na platformie Azure. W tym samouczku pokazano, jak utworzyć niestandardowy punkt końcowy RESTful dostawcy zasobów. Jeśli nie znasz dostawców zasobów niestandardowych platformy Azure, zapoznaj się z omówieniem dostawców zasobów niestandardowych.

Uwaga

Ten samouczek jest oparty na samouczku Konfigurowanie Azure Functions dla niestandardowych dostawców zasobów. Niektóre kroki opisane w tym samouczku działają tylko wtedy, gdy aplikacja funkcji została skonfigurowana w Azure Functions do pracy z niestandardowymi dostawcami zasobów.

Praca z akcjami niestandardowymi i zasobami niestandardowymi

W tym samouczku zaktualizujesz aplikację funkcji, aby działała jako punkt końcowy RESTful dla niestandardowego dostawcy zasobów. Zasoby i akcje na platformie Azure są modelowane po następującej podstawowej specyfikacji RESTful:

  • PUT: Utwórz nowy zasób
  • GET (wystąpienie): pobieranie istniejącego zasobu
  • DELETE: Usuwanie istniejącego zasobu
  • POST: Wyzwalanie akcji
  • GET (kolekcja): Wyświetlanie listy wszystkich istniejących zasobów

Na potrzeby tego samouczka użyjesz usługi Azure Table Storage, ale działa dowolna baza danych lub usługa magazynu.

Partycjonowanie zasobów niestandardowych w magazynie

Ponieważ tworzysz usługę RESTful, musisz przechowywać utworzone zasoby. W przypadku usługi Azure Table Storage należy wygenerować klucze partycji i wierszy dla danych. W przypadku niestandardowych dostawców zasobów dane powinny być podzielone na partycje do niestandardowego dostawcy zasobów. Po wysłaniu żądania przychodzącego do niestandardowego dostawcy zasobów niestandardowy dostawca zasobów dodaje x-ms-customproviders-requestpath nagłówek do wychodzących żądań do punktu końcowego.

Poniższy przykład przedstawia x-ms-customproviders-requestpath nagłówek zasobu niestandardowego:

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

Na podstawie nagłówka x-ms-customproviders-requestpath można utworzyć parametry partitionKey i rowKey dla magazynu, jak pokazano w poniższej tabeli:

Parametr Template Opis
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Parametr partitionKey określa sposób partycjonowania danych. Zwykle dane są partycjonowane przez wystąpienie niestandardowego dostawcy zasobów.
rowKey {myResourceType}:{myResourceName} Parametr rowKey określa indywidualny identyfikator danych. Zazwyczaj identyfikator jest nazwą zasobu.

Należy również utworzyć nową klasę, aby modelować zasób niestandardowy. W tym samouczku dodasz następującą klasę CustomResource do aplikacji funkcji:

// 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 to prosta, ogólna klasa, która akceptuje wszystkie dane wejściowe. Jest on oparty na interfejsie ITableEntity, który jest używany do przechowywania danych. Klasa CustomResource implementuje wszystkie właściwości interfejsu ITableEntity: sygnatura czasowa, eTag, partitionKey i rowKey.

Obsługa niestandardowych metod RESTful dostawcy zasobów

Uwaga

Jeśli nie kopiujesz kodu bezpośrednio z tego samouczka, zawartość odpowiedzi musi być prawidłową zawartością JSON, która ustawia Content-Type nagłówek na application/jsonwartość .

Po skonfigurowaniu partycjonowania danych utwórz podstawowe metody CRUD i wyzwalacza dla zasobów niestandardowych i akcji niestandardowych. Ponieważ dostawcy zasobów niestandardowych działają jako serwery proxy, punkt końcowy RESTful musi modelować i obsługiwać żądanie i odpowiedź. Poniższe fragmenty kodu pokazują, jak obsługiwać podstawowe operacje RESTful.

Wyzwalanie akcji niestandardowej

W przypadku niestandardowych dostawców zasobów akcja niestandardowa jest wyzwalana za pośrednictwem żądań POST. Akcja niestandardowa może opcjonalnie akceptować treść żądania, która zawiera zestaw parametrów wejściowych. Następnie akcja zwraca odpowiedź, która sygnalizuje wynik akcji i informację, czy zakończyła się powodzeniem, czy niepowodzeniem.

Dodaj następującą metodę TriggerCustomAction do aplikacji funkcji:

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

Metoda TriggerCustomAction akceptuje przychodzące żądanie i zwraca odpowiedź z kodem stanu.

Tworzenie zasobu niestandardowego

W przypadku niestandardowych dostawców zasobów zasób niestandardowy jest tworzony za pośrednictwem żądań PUT. Niestandardowy dostawca zasobów akceptuje treść żądania JSON, która zawiera zestaw właściwości zasobu niestandardowego. Zasoby na platformie Azure są zgodne z modelem RESTful. Możesz użyć tego samego adresu URL żądania, aby utworzyć, pobrać lub usunąć zasób.

Dodaj następującą metodę CreateCustomResource , aby utworzyć nowe zasoby:

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

Metoda CreateCustomResource aktualizuje żądanie przychodzące w celu uwzględnienia identyfikatora, nazwy i typu pól specyficznych dla platformy Azure. Te pola są właściwościami najwyższego poziomu używanymi przez usługi na platformie Azure. Umożliwiają one współdziałanie niestandardowego dostawcy zasobów z innymi usługami, takimi jak Azure Policy, szablony usługi Azure Resource Manager i dziennik aktywności platformy Azure.

Właściwość Przykład Opis
Nazwa {myCustomResourceName} Nazwa zasobu niestandardowego
Typu Microsoft.CustomProviders/resourceProviders/{resourceTypeName} Przestrzeń nazw typu zasobu
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
Identyfikator zasobu

Oprócz dodawania właściwości dokument JSON został również zapisany w usłudze Azure Table Storage.

Pobieranie zasobu niestandardowego

W przypadku niestandardowych dostawców zasobów zasób niestandardowy jest pobierany za pośrednictwem żądań GET. Niestandardowy dostawca zasobów nie akceptuje treści żądania JSON. W przypadku żądań GET punkt końcowy używa nagłówka x-ms-customproviders-requestpath , aby zwrócić już utworzony zasób.

Dodaj następującą metodę RetrieveCustomResource , aby pobrać istniejące zasoby:

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

Na platformie Azure zasoby są zgodne z modelem RESTful. Adres URL żądania, który tworzy zasób, zwraca również zasób w przypadku wykonania żądania GET.

Usuwanie zasobu niestandardowego

W przypadku niestandardowych dostawców zasobów zasób niestandardowy jest usuwany za pośrednictwem żądań DELETE. Niestandardowy dostawca zasobów nie akceptuje treści żądania JSON. W przypadku żądania DELETE punkt końcowy używa nagłówka x-ms-customproviders-requestpath do usunięcia już utworzonego zasobu.

Dodaj następującą metodę RemoveCustomResource , aby usunąć istniejące zasoby:

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

Na platformie Azure zasoby są zgodne z modelem RESTful. Adres URL żądania, który tworzy zasób, usuwa również zasób, jeśli zostanie wykonane żądanie DELETE.

Wyświetlanie listy wszystkich zasobów niestandardowych

W przypadku niestandardowych dostawców zasobów można wyliczyć listę istniejących zasobów niestandardowych przy użyciu żądań GET kolekcji. Niestandardowy dostawca zasobów nie akceptuje treści żądania JSON. W przypadku kolekcji żądań GET punkt końcowy używa nagłówka x-ms-customproviders-requestpath do wyliczania już utworzonych zasobów.

Dodaj następującą metodę EnumerateAllCustomResources , aby wyliczyć istniejące zasoby:

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

Uwaga

RowKey QueryComparisons.GreaterThan i QueryComparisons.LessThan to składnia usługi Azure Table Storage do wykonywania zapytania "startswith" dla ciągów.

Aby wyświetlić listę wszystkich istniejących zasobów, wygeneruj zapytanie usługi Azure Table Storage, które gwarantuje, że zasoby istnieją w partycji niestandardowego dostawcy zasobów. Następnie zapytanie sprawdza, czy klucz wiersza zaczyna się od tej samej {myResourceType} wartości.

Integrowanie operacji RESTful

Po dodaniu wszystkich metod RESTful do aplikacji funkcji zaktualizuj główną metodę Run , która wywołuje funkcje w celu obsługi różnych żądań 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);
    }
}

Zaktualizowana metoda Run zawiera teraz powiązanie wejściowe tableClient dodane dla usługi Azure Table Storage. Pierwsza część metody odczytuje x-ms-customproviders-requestpath nagłówek i używa Microsoft.Azure.Management.ResourceManager.Fluent biblioteki do analizowania wartości jako identyfikatora zasobu. Nagłówek x-ms-customproviders-requestpath jest wysyłany przez niestandardowego dostawcę zasobów i określa ścieżkę żądania przychodzącego.

Za pomocą przeanalizowanego identyfikatora zasobu można wygenerować wartości partitionKey i rowKey dla danych w celu wyszukania lub przechowywania zasobów niestandardowych.

Po dodaniu metod i klas należy zaktualizować metodę using dla aplikacji funkcji. Dodaj następujący kod na początku pliku 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;

Jeśli zgubisz się w dowolnym momencie tego samouczka, możesz znaleźć kompletny przykładowy kod w dokumentacji punktu końcowego RESTful dostawcy zasobów niestandardowych języka C#. Po zakończeniu aplikacji funkcji zapisz adres URL aplikacji funkcji. Może służyć do wyzwalania aplikacji funkcji w kolejnych samouczkach.

Następne kroki

W tym artykule utworzono punkt końcowy RESTful do pracy z punktem końcowym niestandardowego dostawcy zasobów platformy Azure. Aby dowiedzieć się, jak utworzyć niestandardowego dostawcę zasobów, przejdź do artykułu Tworzenie i używanie niestandardowego dostawcy zasobów.