Бөлісу құралы:


Создание конечной точки RESTful для настраиваемых поставщиков ресурсов

Настраиваемый поставщик ресурсов — это контракт между Azure и конечной точкой. С помощью настраиваемых поставщиков ресурсов можно настраивать рабочие процессы в Azure. В этом руководстве показано, как создать конечную точку RESTful пользовательского поставщика ресурсов. Для ознакомления с поставщиками настраиваемых ресурсов Azure см. статью Общие сведения о поставщиках настраиваемых ресурсов.

Примечание

Это руководство основывается на руководстве По настройке Функции Azure для пользовательских поставщиков ресурсов. Некоторые действия, описанные в этом руководстве, выполняются только в том случае, если приложение-функция настроено в Функции Azure для работы с настраиваемыми поставщиками ресурсов.

Работа с настраиваемыми действиями и настраиваемыми ресурсами

В этом руководстве вы обновите приложение-функцию, чтобы оно работало в качестве конечной точки RESTful для пользовательского поставщика ресурсов. Все ресурсы и действия Azure соблюдают базовую спецификацию REST:

  • PUT: создание нового ресурса.
  • GET (экземпляр) : получение существующего ресурса.
  • DELETE: удаление существующего ресурса.
  • POST: активация действия.
  • GET (коллекция) : перечисление всех существующих ресурсов.

В этом руководстве используется хранилище таблиц Azure, но подойдет и любая другая база данных или служба хранилища.

Секционирование настраиваемых ресурсов в хранилище

Так как мы создаем службу RESTful, нам нужно хранить созданные ресурсы. В табличном хранилище Azure нам нужно создать ключи секций и строк для наших данных. Для настраиваемых поставщиков ресурсов данные должны быть секционированы по пользовательскому поставщику ресурсов. Когда входящий запрос отправляется пользовательскому поставщику ресурсов, настраиваемый поставщик ресурсов добавляет x-ms-customproviders-requestpath заголовок в исходящие запросы к конечной точке.

В следующем примере показан заголовок x-ms-customproviders-requestpath для настраиваемого ресурса:

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

Основываясь на приведенном выше заголовке x-ms-customproviders-requestpath, вы можете создать параметры partitionKey и rowKey для используемого хранилища, как показано в следующей таблице.

Параметр Шаблон Описание
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Параметр partitionKey определяет способ секционирования данных. Обычно данные секционируются пользовательским экземпляром поставщика ресурсов.
rowKey {myResourceType}:{myResourceName} Параметр rowKey содержит индивидуальный идентификатор данных. Обычно идентификатором служит имя ресурса.

Вам также нужно создать новый класс для моделирования настраиваемого ресурса. Далее вы добавите в приложение-функцию класс CustomResource.

// 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 — это простой и универсальный класс, который принимает любые входные данные. Он основан на классе ITableEntity, который используется для хранения данных. Класс CustomResource реализует все свойства из интерфейса ITableEntity: timestamp, eTag, partitionKey и rowKey.

Поддержка методов RESTful пользовательского поставщика ресурсов

Примечание

Если вы не копируете код непосредственно из руководства, предоставьте в ответе допустимый объект JSON, который устанавливает для заголовка Content-Type значение application/json.

Итак, вы завершили настройку секционирования данных, и теперь переходите к созданию методов CRUD для настраиваемых ресурсов и триггеров для настраиваемых действий. Так как настраиваемые поставщики ресурсов действуют как прокси-серверы, конечная точка RESTful должна моделировать и обрабатывать запрос и ответ. В приведенном ниже фрагменте кода показано, как выполнять базовые операции RESTful.

Активация настраиваемого действия

Для настраиваемых поставщиков ресурсов настраиваемое действие запускается через запросы POST. Пользовательское действие может дополнительно принимать текст запроса, который содержит набор входных параметров. Затем это действие возвращает ответ с информацией об успешности и результате действия.

Добавьте в приложение-функцию следующий метод TriggerCustomAction:

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

Метод TriggerCustomAction принимает входящий запрос и возвращает в ответе текст запроса с кодом состояния.

Создание настраиваемого ресурса

Для настраиваемых поставщиков ресурсов пользовательский ресурс создается с помощью запросов PUT. Поставщик настраиваемых ресурсов принимает текст запроса JSON, который содержит набор свойств для настраиваемого ресурса. Все ресурсы в Azure соответствуют модели RESTful. Для создания, извлечения или удаления ресурсов можно использовать один и тот же URL-адрес запроса.

Добавьте метод CreateCustomResource для создания ресурсов.

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

Метод CreateCustomResource изменяет входящий запрос, добавляя поля id (идентификатор), name (имя) и type (тип), которые понимает платформа Azure. Эти поля содержат свойства верхнего уровня, которые используются службами в Azure. Они позволяют пользовательскому поставщику ресурсов взаимодействовать с другими службами, такими как Политика Azure, шаблоны Azure Resource Manager и журнал действий Azure.

Свойство Пример Описание
name {имя_настраиваемого_ресурса} Имя настраиваемого ресурса
type Microsoft.CustomProviders/resourceProviders/{имя_типа_ресурса} Пространство имен для типа ресурса
id /subscriptions/{subscriptionId}/resourceGroups/{имя_группы_ресурсов}/
providers/Microsoft.CustomProviders/resourceProviders/{имя поставщика ресурсов}/
{имя_типа_ресурса}/{имя_настраиваемого_ресурса}
Идентификатор ресурса

Вы не только добавили свойства, но и сохранили документ JSON в табличное хранилище Azure.

Извлечение настраиваемого ресурса

Для настраиваемых поставщиков ресурсов пользовательский ресурс извлекается с помощью запросов GET. Настраиваемый поставщик ресурсов не принимает текст запроса JSON. По запросу GET конечная точка возвращает уже созданный ресурс, указанный в заголовке x-ms-customproviders-requestpath.

Добавьте метод RetrieveCustomResource, который будет извлекать существующие ресурсы:

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

В Azure ресурсы соответствуют модели RESTful. URL-адрес запроса, который создает ресурс, также возвращает этот ресурс, если выполняется запрос GET.

Удаление настраиваемого ресурса

Для настраиваемых поставщиков ресурсов пользовательский ресурс удаляется с помощью запросов DELETE. Настраиваемый поставщик ресурсов не принимает текст запроса JSON. По запросу DELETE конечная точка удаляет уже созданный ресурс, указанный в заголовке x-ms-customproviders-requestpath.

Добавьте метод RemoveCustomResource, который будет удалять существующие ресурсы:

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

В Azure ресурсы соответствуют модели RESTful. URL-адрес запроса, который создает ресурс, также удаляет этот ресурс, если выполняется запрос DELETE.

Список всех настраиваемых ресурсов

Для настраиваемых поставщиков ресурсов можно перечислить список существующих настраиваемых ресурсов с помощью запросов GET коллекции. Настраиваемый поставщик ресурсов не принимает текст запроса JSON. По коллекции запросов GET конечная точка перечисляет уже созданные ресурсы, указанные в заголовке x-ms-customproviders-requestpath.

Добавьте следующий метод EnumerateAllCustomResources, который будет перечислять существующие ресурсы:

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

Примечание

Синтаксис RowKey QueryComparisons.GreaterThan и QueryComparisons.LessThan используется в табличном хранилище Azure для выполнения запроса строк startswith.

Чтобы получить список всех существующих ресурсов, создайте запрос к хранилищу таблиц Azure, который гарантирует, что ресурсы существуют в пользовательской секции поставщика ресурсов. Затем этот запрос проверяет, начинается ли ключ строки с того же значения {myResourceType}.

Интеграция операций RESTful

Добавив все методы REST в приложение-функцию, обновите основной метод Run, чтобы вызвать функции для обработки разных запросов 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);
    }
}

Обновленный метод Run теперь содержит входную привязку tableClient, которую вы добавили для Хранилища таблиц Azure. Первая часть метода считывает заголовок x-ms-customproviders-requestpath и применяет библиотеку Microsoft.Azure.Management.ResourceManager.Fluent для анализа значения в качестве идентификатора ресурса. Заголовок x-ms-customproviders-requestpath отправляется настраиваемым поставщиком ресурсов и указывает путь к входящему запросу.

Используя полученный идентификатор ресурса, вы можете создать значения partitionKey и rowKey для данных, чтобы искать или сохранять настраиваемые ресурсы.

После добавления методов и классов обновите методы using в приложении-функции. Добавьте следующий код в начало файла на 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;

Если вы потеряетесь на каком-либо этапе работы с этим руководством, полный пример кода можно найти в справочнике по конечной точке RESTful пользовательского поставщика ресурсов C#. Завершив работу с приложением-функцией, сохраните URL-адрес приложения-функции. Он потребуется для активации приложения-функции в следующих руководствах.

Дальнейшие действия

В этой статье вы создали конечную точку RESTful для работы с конечной точкой пользовательского поставщика ресурсов Azure. Сведения о создании настраиваемого поставщика ресурсов см. в статье Создание и использование настраиваемого поставщика ресурсов.