次の方法で共有


カスタム リソース プロバイダー用の RESTful エンドポイントを作成する

カスタム リソース プロバイダーは、Azure とエンドポイントの間のコントラクトです。 カスタム リソース プロバイダーを使うと、Azure 上のワークフローをカスタマイズできます。 このチュートリアルでは、カスタム リソース プロバイダーの RESTful エンドポイントを作成する方法について説明します。 Azure カスタム リソース プロバイダーについてよくご存じでない場合は、カスタム リソース プロバイダーの概要に関するページを参照してください。

Note

このチュートリアルは、「カスタム リソース プロバイダー用の Azure Functions を設定する」チュートリアルに基づいて作成されています。 このチュートリアルの手順の一部は、Azure Functions の関数アプリがカスタム リソース プロバイダーと連携するよう設定されている場合にのみ機能します。

カスタム アクションとカスタム リソースを使用する

このチュートリアルでは、カスタム リソース プロバイダーの RESTful エンドポイントとして動作するように関数アプリを更新します。 Azure のリソースとアクションは、次の RESTful の基本的な仕様に従ってモデル化されています。

  • PUT: 新しいリソースを作成する
  • GET (インスタンス) : 既存のリソースを取得する
  • DELETE: 既存のリソースを削除する
  • POST: アクションをトリガーする
  • GET (コレクション) : 既存のリソースをすべてリストする

このチュートリアルでは、Azure Table Storage を使用しますが、データベースやストレージ サービスはすべて機能します。

カスタム リソースをパーティション分割してストレージに格納する

RESTful サービスを作成しているため、作成されたリソースを格納する必要があります。 Azure Table Storage では、データのパーティションと行キーを生成する必要があります。 カスタム リソース プロバイダーでは、データをカスタム リソース プロバイダーにパーティション分割する必要があります。 受信要求がカスタム リソース プロバイダーに送信されると、カスタム リソース プロバイダーによってエンドポイントへの送信要求に 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 パラメーターを作成できます。

パラメーター Template 説明
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 クラスは、インターフェイス ITableEntitytimestampeTagpartitionKeyrowKey というすべてのプロパティを実装します。

カスタム リソース プロバイダーの RESTful メソッドをサポートする

Note

このチュートリアルからコードを直接コピーしていない場合、応答の内容は、Content-Type ヘッダーを application/json に設定する有効な 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 メソッドは、受信要求を更新して、Azure 固有のフィールドである idnametype を追加します。 これらのフィールドは、Azure 全体のサービスによって使われる最上位レベルのプロパティです。 カスタム リソース プロバイダーは、これらを通じて、Azure Policy、Azure Resource Manager テンプレート、Azure アクティビティ ログなど、他のサービスと相互運用できるようになります。

プロパティ 説明
name {myCustomResourceName} カスタム リソースの名前
type Microsoft.CustomProviders/resourceProviders/{resourceTypeName} リソースの種類の名前空間
ID /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
リソース ID

プロパティを追加するだけでなく、Azure Table Storage へのドキュメントの保存も行いました。

カスタム リソースの取得

カスタム リソース プロバイダーでは、カスタム リソースが 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 のモデルに従います。 GET 要求を実行する場合、リソースを作成する要求 URL でもリソースが返されます。

カスタム リソースの削除

カスタム リソース プロバイダーでは、カスタム リソースが 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 のモデルに従います。 DELETE 要求を実行する場合、リソースを作成する要求 URL でもリソースが削除されます。

すべてのカスタム リソースの一覧を取得する

カスタム リソース プロバイダーでは、既存のカスタム リソースの一覧をコレクションの 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;
}

Note

RowKey QueryComparisons.GreaterThan と QueryComparisons.LessThan は、文字列の "startswith" クエリを実行するための Azure Table Storage の構文です。

既存のリソースすべてを一覧表示するには、カスタム リソース プロバイダーのパーティションにリソースが存在することを保証する Azure Table Storage クエリを生成します。 その後、このクエリは、行キーが同じ {myResourceType} 値で始まっていることをチェックします。

RESTful の操作を統合する

すべての RESTful メソッドを関数アプリに追加した後は、関数を呼び出して各種の REST 要求を処理するメインの Run メソッドを更新します。

/// <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 メソッドには、Azure Table Storage のために追加した tableClient 入力バインドが含まれます。 メソッドの最初の部分では、x-ms-customproviders-requestpath ヘッダーを読み取り、Microsoft.Azure.Management.ResourceManager.Fluent ライブラリを使って値をリソース ID として解析します。 x-ms-customproviders-requestpath ヘッダーはカスタム リソース プロバイダーによって送信され、受信した要求のパスを指定します。

解析されたリソース ID を使って、カスタム リソースを参照または格納するための、データに対する 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;

このチュートリアルでの作業が途中でわからなくなった場合は、完成したコード サンプルを「カスタム リソース プロバイダーの C# RESTful エンドポイント リファレンス」で確認できます。 関数アプリが完成したら、その URL を保存してください。 後続のチュートリアルでこの関数アプリをトリガーする際に使用できます。

次のステップ

この記事では、Azure のカスタム リソース プロバイダーのエンドポイントで動作する RESTful エンドポイントを作成しました。 カスタム リソース プロバイダーの作成方法については、カスタム リソース プロバイダーの作成と使用に関する記事をご覧ください。