Criar um ponto final RESTful para fornecedores de recursos personalizados

Um fornecedor de recursos personalizado é um contrato entre o Azure e um ponto final. Com os fornecedores de recursos personalizados, pode personalizar fluxos de trabalho no Azure. Este tutorial mostra como criar um ponto final RESTful do fornecedor de recursos personalizado. Se não estiver familiarizado com os Fornecedores de Recursos Personalizados do Azure, veja a descrição geral dos fornecedores de recursos personalizados.

Nota

Este tutorial baseia-se no tutorial Configurar Funções do Azure para fornecedores de recursos personalizados. Alguns dos passos neste tutorial só funcionam se uma aplicação de funções tiver sido configurada no Funções do Azure para trabalhar com fornecedores de recursos personalizados.

Trabalhar com ações personalizadas e recursos personalizados

Neste tutorial, vai atualizar a aplicação de funções para funcionar como um ponto final RESTful para o seu fornecedor de recursos personalizado. Os recursos e ações no Azure são modelados após a seguinte especificação BÁSICA RESTful:

  • PUT: Criar um novo recurso
  • GET (instância): Obter um recurso existente
  • DELETE: Remover um recurso existente
  • POST: Acionar uma ação
  • GET (coleção): Listar todos os recursos existentes

Neste tutorial, vai utilizar o Armazenamento de Tabelas do Azure, mas qualquer base de dados ou serviço de armazenamento funciona.

Recursos personalizados de partição no armazenamento

Uma vez que está a criar um serviço RESTful, tem de armazenar os recursos criados. Para o armazenamento de Tabelas do Azure, tem de gerar chaves de partição e de linha para os seus dados. Para fornecedores de recursos personalizados, os dados devem ser particionados para o fornecedor de recursos personalizado. Quando um pedido recebido é enviado para o fornecedor de recursos personalizado, o fornecedor de recursos personalizado adiciona o x-ms-customproviders-requestpath cabeçalho aos pedidos de envio ao ponto final.

O exemplo seguinte mostra um x-ms-customproviders-requestpath cabeçalho para um recurso personalizado:

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

Com base no x-ms-customproviders-requestpath cabeçalho, pode criar os parâmetros partitionKey e rowKey para o seu armazenamento, conforme mostrado na tabela seguinte:

Parâmetro Modelo Descrição
partitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} O parâmetro partitionKey especifica a forma como os dados são particionados. Normalmente, os dados são particionados pela instância do fornecedor de recursos personalizado.
rowKey {myResourceType}:{myResourceName} O parâmetro rowKey especifica o identificador individual para os dados. Normalmente, o identificador é o nome do recurso.

Também tem de criar uma nova classe para modelar o recurso personalizado. Neste tutorial, vai adicionar a seguinte classe CustomResource à sua aplicação de funções:

// 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 é uma classe simples e genérica que aceita quaisquer dados de entrada. Baseia-se em ITableEntity, que é utilizado para armazenar dados. A classe CustomResource implementa todas as propriedades da interface ITableEntity: carimbo de data/hora, eTag, partitionKey e rowKey.

Suportar métodos RESTful do fornecedor de recursos personalizados

Nota

Se não estiver a copiar o código diretamente deste tutorial, o conteúdo de resposta tem de ser JSON válido que define o Content-Type cabeçalho como application/json.

Agora que configurou a criação de partições de dados, crie o CRUD básico e os métodos de acionador para recursos personalizados e ações personalizadas. Uma vez que os fornecedores de recursos personalizados atuam como proxies, o ponto final RESTful tem de modelar e processar o pedido e a resposta. Os fragmentos de código seguintes mostram como lidar com as operações básicas do RESTful.

Acionar uma ação personalizada

Para fornecedores de recursos personalizados, é acionada uma ação personalizada através de pedidos POST. Opcionalmente, uma ação personalizada pode aceitar um corpo de pedido que contém um conjunto de parâmetros de entrada. Em seguida, a ação devolve uma resposta que assinala o resultado da ação e se foi bem-sucedida ou falhou.

Adicione o seguinte método TriggerCustomAction à sua aplicação de funções:

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

O método TriggerCustomAction aceita um pedido recebido e faz eco da resposta com um código de estado.

Criar um recurso personalizado

Para fornecedores de recursos personalizados, é criado um recurso personalizado através de pedidos PUT. O fornecedor de recursos personalizado aceita um corpo de pedido JSON, que contém um conjunto de propriedades para o recurso personalizado. Os recursos no Azure seguem um modelo RESTful. Pode utilizar o mesmo URL de pedido para criar, obter ou eliminar um recurso.

Adicione o seguinte método CreateCustomResource para criar novos recursos:

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

O método CreateCustomResource atualiza o pedido recebido para incluir o ID, o nome e o tipo de campos específicos do Azure. Estes campos são propriedades de nível superior utilizadas pelos serviços em todo o Azure. Permitem que o fornecedor de recursos personalizado interopera com outros serviços, como Azure Policy, modelos de Resource Manager do Azure e Registo de Atividades do Azure.

Propriedade Exemplo Descrição
nome {myCustomResourceName} O nome do recurso personalizado
tipo Microsoft.CustomProviders/resourceProviders/{resourceTypeName} O espaço de nomes do tipo de recurso
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
O ID do recurso

Além de adicionar as propriedades, também guardou o documento JSON no armazenamento de Tabelas do Azure.

Obter um recurso personalizado

Para fornecedores de recursos personalizados, um recurso personalizado é obtido através de pedidos GET. Um fornecedor de recursos personalizado não aceita um corpo de pedido JSON. Para pedidos GET, o ponto final utiliza o x-ms-customproviders-requestpath cabeçalho para devolver o recurso já criado.

Adicione o seguinte método RetrieveCustomResource para obter recursos existentes:

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

No Azure, os recursos seguem um modelo RESTful. O URL do pedido que cria um recurso também devolve o recurso se for efetuado um pedido GET.

Remover um recurso personalizado

Para fornecedores de recursos personalizados, um recurso personalizado é removido através de pedidos DELETE. Um fornecedor de recursos personalizado não aceita um corpo de pedido JSON. Para um pedido DELETE, o ponto final utiliza o x-ms-customproviders-requestpath cabeçalho para eliminar o recurso já criado.

Adicione o seguinte método RemoveCustomResource para remover recursos existentes:

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

No Azure, os recursos seguem um modelo RESTful. O URL do pedido que cria um recurso também elimina o recurso se for efetuado um pedido DELETE.

Listar todos os recursos personalizados

Para fornecedores de recursos personalizados, pode enumerar uma lista de recursos personalizados existentes com pedidos GET de coleção. Um fornecedor de recursos personalizado não aceita um corpo de pedido JSON. Para uma coleção de pedidos GET, o ponto final utiliza o x-ms-customproviders-requestpath cabeçalho para enumerar os recursos já criados.

Adicione o seguinte método EnumerateAllCustomResources para enumerar os recursos existentes:

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

A RowKey QueryComparisons.GreaterThan e QueryComparisons.LessThan é a sintaxe de armazenamento de Tabelas do Azure para executar uma consulta "startswith" para cadeias.

Para listar todos os recursos existentes, gere uma consulta de armazenamento de Tabelas do Azure que garante que os recursos existem na partição do fornecedor de recursos personalizado. Em seguida, a consulta verifica se a chave de linha começa com o mesmo {myResourceType} valor.

Integrar operações RESTful

Depois de todos os métodos RESTful serem adicionados à aplicação de funções, atualize o método run principal que chama as funções para processar os diferentes pedidos 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);
    }
}

O método Run atualizado inclui agora o enlace de entrada tableClient que adicionou para o armazenamento de Tabelas do Azure. A primeira parte do método lê o x-ms-customproviders-requestpath cabeçalho e utiliza a Microsoft.Azure.Management.ResourceManager.Fluent biblioteca para analisar o valor como um ID de recurso. O x-ms-customproviders-requestpath cabeçalho é enviado pelo fornecedor de recursos personalizado e especifica o caminho do pedido recebido.

Ao utilizar o ID de recurso analisado, pode gerar os valores partitionKey e rowKey para que os dados procurem ou armazenem recursos personalizados.

Depois de adicionar os métodos e classes, tem de atualizar os métodos de utilização da aplicação de funções. Adicione o seguinte código à parte superior do ficheiro 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 se perder em qualquer altura deste tutorial, pode encontrar o exemplo de código completo na referência de ponto final C# RESTful do fornecedor de recursos personalizado. Depois de terminar a aplicação de funções, guarde o URL da aplicação de funções. Pode ser utilizado para acionar a aplicação de funções em tutoriais posteriores.

Passos seguintes

Neste artigo, criou um ponto final RESTful para trabalhar com um ponto final do Fornecedor de Recursos Personalizado do Azure. Para saber como criar um fornecedor de recursos personalizado, aceda ao artigo Criar e utilizar um fornecedor de recursos personalizado.