Menulis titik akhir RESTful untuk penyedia sumber daya kustom

Penyedia sumber daya kustom adalah kontrak antara Azure dan titik akhir. Dengan penyedia sumber daya kustom, Anda dapat menyesuaikan alur kerja di Azure. Tutorial ini menunjukkan cara menulis titik akhir RESTful penyedia sumber daya kustom. Jika Anda tidak terbiasa dengan Azure Custom Resource Providers, lihat gambaran umum penyedia sumber daya kustom.

Catatan

Tutorial ini dibangun pada tutorial Menyiapkan Azure Functions untuk penyedia sumber daya kustom. Beberapa langkah dalam tutorial ini hanya berfungsi jika aplikasi fungsi telah disiapkan di Azure Functions untuk bekerja dengan penyedia sumber daya kustom.

Bekerja dengan tindakan kustom dan sumber daya kustom

Dalam tutorial ini, Anda memperbarui aplikasi fungsi agar berfungsi sebagai titik akhir RESTful untuk penyedia sumber daya kustom Anda. Sumber daya dan tindakan di Azure dimodelkan setelah spesifikasi dasar RESTful berikut ini:

  • PUT: Buat sumber daya baru
  • GET (instance) : Ambil sumber daya yang ada
  • DELETE: Hapus sumber daya yang ada
  • POST: Memicu tindakan
  • GET (koleksi) : Mendaftar semua sumber daya yang ada

Untuk tutorial ini, Anda menggunakan penyimpanan Tabel, tetapi database atau layanan penyimpanan apa pun berfungsi.

Mempartisi sumber daya kustom dalam penyimpanan

Karena Anda membuat layanan RESTful, Anda perlu menyimpan sumber daya yang dibuat. Untuk penyimpanan Azure Table, Anda perlu menghasilkan tombol partisi dan baris untuk data Anda. Untuk penyedia sumber daya kustom, data harus dipartisi ke penyedia sumber daya kustom. Saat permintaan masuk dikirim ke penyedia sumber daya kustom, penyedia sumber daya kustom menambahkan x-ms-customproviders-requestpath header ke permintaan keluar ke titik akhir.

Contoh berikut memperlihatkan x-ms-customproviders-requestpath header untuk sumber daya kustom:

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

Berdasarkan header x-ms-customproviders-requestpath, Anda dapat membuat parameter partitionKey dan rowKey untuk penyimpanan Anda seperti yang diperlihatkan dalam tabel berikut:

Parameter Templat Deskripsi
PartitionKey {subscriptionId}:{resourceGroupName}:{resourceProviderName} Parameter partitionKey menentukan bagaimana data dipartisi. Biasanya data dipartisi oleh instans penyedia sumber daya kustom.
rowKey {myResourceType}:{myResourceName} Parameter rowKey menentukan pengidentifikasi individual untuk data. Biasanya pengidentifikasi adalah nama sumber daya.

Anda juga perlu membuat kelas baru untuk memodelkan sumber daya kustom Anda. Dalam tutorial ini, Anda menambahkan kelas CustomResource berikut ke aplikasi fungsi Anda:

// 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 adalah kelas sederhana dan umum yang menerima data input apa pun. Ini didasarkan pada TableEntity, yang digunakan untuk menyimpan data. Kelas CustomResource mengimplementasikan semua properti dari antarmuka ITableEntity: tanda waktu, eTag, partitionKey, dan rowKey.

Mendukung metode RESTful penyedia sumber daya kustom

Catatan

Jika Anda tidak menyalin kode langsung dari tutorial ini, konten respons harus valid JSON yang mengatur header Content-Type ke application/json .

Sekarang setelah Anda menyiapkan partisi data, buat metode DASAR CRUD dan pemicu untuk sumber daya kustom dan tindakan kustom. Karena penyedia sumber daya kustom bertindak sebagai proksi, titik akhir RESTful harus memodelkan dan menangani permintaan dan respons. Cuplikan kode berikut menunjukkan cara menangani operasi dasar RESTful.

Memicu tindakan kustom

Untuk penyedia sumber daya kustom, tindakan kustom dipicu melalui permintaan POST. Tindakan kustom dapat secara opsional menerima badan permintaan yang berisi sekumpulan parameter input. Tindakan kemudian mengembalikan respons yang menandakan hasil tindakan dan apakah tindakan berhasil atau gagal.

Tambahkan metode TriggerCustomAction berikut ke aplikasi fungsi Anda:

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

Metode TriggerCustomAction menerima permintaan masuk dan hanya mengembalikan respons dengan kode status.

Buat sumber daya kustom

Untuk penyedia sumber daya kustom, sumber daya kustom dibuat melalui permintaan PUT. Penyedia sumber daya kustom menerima isi permintaan JSON, yang berisi sekumpulan properti untuk sumber daya kustom. Sumber daya di Azure mengikuti model RESTful. Anda dapat menggunakan URL permintaan yang sama untuk membuat, mengambil, atau menghapus sumber daya.

Tambahkan metode CreateCustomResource berikut ini untuk membuat sumber daya baru:

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

Metode CreateCustomResource memperbarui permintaan masuk untuk menyertakan bidang khusus Azure id, nama, dan jenis. Bidang ini adalah properti tingkat atas yang digunakan oleh seluruh layanan Azure. Mereka memungkinkan penyedia sumber daya kustom beroperasi dengan layanan lain seperti Azure Policy, templat Azure Resource Manager, dan Log Aktivitas Azure.

Properti Contoh Deskripsi
nama {myCustomResourceName} Nama sumber daya kustom
tipe Microsoft.CustomProviders/resourceProviders/{resourceTypeName} Namespace tipe sumber daya
id /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.CustomProviders/resourceProviders/{resourceProviderName}/
{resourceTypeName}/{myCustomResourceName}
ID sumber daya

Selain menambahkan properti, Anda juga menyimpan dokumen JSON ke penyimpanan Azure Table.

Ambil sumber daya khusus

Untuk penyedia sumber daya kustom, sumber daya kustom diambil melalui permintaan GET. Penyedia sumber daya kustom tidak menerima isi permintaan JSON. Untuk permintaan GET, titik akhir menggunakan header x-ms-customproviders-requestpath untuk mengembalikan sumber daya yang sudah dibuat.

Tambahkan metode RetrieveCustomResource berikut untuk mengambil sumber daya yang ada:

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

Di Azure, sumber daya mengikuti model RESTful. URL permintaan yang membuat sumber daya juga mengembalikan sumber daya jika permintaan GET dilakukan.

Hapus sumber daya kustom

Untuk penyedia sumber daya kustom, sumber daya kustom dihapus melalui permintaan DELETE. Penyedia sumber daya kustom tidak menerima isi permintaan JSON. Untuk permintaan HAPUS, titik akhir menggunakan tajuk x-ms-customproviders-requestpath untuk menghapus sumber daya yang sudah dibuat.

Tambahkan metode RemoveCustomResource berikut untuk menghapus sumber daya yang ada:

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

Di Azure, sumber daya mengikuti model RESTful. URL permintaan yang membuat sumber daya juga menghapus sumber daya jika permintaan HAPUS dilakukan.

Buat daftar semua sumber daya kustom

Untuk penyedia sumber daya kustom, Anda dapat menghitung daftar sumber daya kustom yang ada dengan menggunakan permintaan GET koleksi. Penyedia sumber daya kustom tidak menerima isi permintaan JSON. Untuk kumpulan permintaan GET, titik akhir menggunakan header x-ms-customproviders-requestpath untuk menghitung sumber daya yang sudah dibuat.

Tambahkan metode EnumerateAllCustomResources berikut untuk menghitung sumber daya yang ada:

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

Catatan

RowKey QueryComparisons.GreaterThan dan QueryComparisons.LessThan adalah sintaks penyimpanan Azure Table untuk melakukan kueri "startswith" untuk string.

Untuk mencantumkan semua sumber daya yang ada, buat kueri penyimpanan Azure Table yang memastikan sumber daya ada di bawah partisi penyedia sumber daya kustom Anda. Kueri kemudian memeriksa bahwa kunci baris dimulai dengan nilai {myResourceType} yang sama.

Integrasikan operasi RESTful

Setelah semua metode RESTful ditambahkan ke aplikasi fungsi, perbarui metode Run utama yang memanggil fungsi untuk menangani permintaan REST yang berbeda:

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

Metode Run yang diperbarui sekarang menyertakan pengikat masukan tableClient yang Anda tambahkan untuk penyimpanan Azure Table. Bagian pertama dari metode ini membaca tajuk x-ms-customproviders-requestpathdan menggunakan pustakaMicrosoft.Azure.Management.ResourceManager.Fluent untuk mengurai nilai sebagai ID sumber daya. Header x-ms-customproviders-requestpath dikirim oleh penyedia sumber daya kustom dan menentukan jalur permintaan masuk.

Dengan menggunakan ID sumber daya yang diurai, Anda dapat membuat nilai partitionKey dan rowKey untuk dicari data atau untuk menyimpan sumber daya kustom.

Setelah Anda menambahkan metode dan kelas, Anda perlu memperbarui menggunakan metode untuk aplikasi fungsi. Tambahkan kode berikut ke bagian atas file 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;

Jika Anda tersesat pada titik mana pun dari tutorial ini, Anda dapat menemukan sampel kode lengkap di penyedia sumber daya kustom referensi titik akhir C# RESTful. Setelah Anda menyelesaikan aplikasi fungsi, simpan URL aplikasi fungsi. Ini dapat digunakan untuk memicu aplikasi fungsi di tutorial selanjutnya.

Langkah berikutnya

Dalam artikel ini, Anda menulis titik akhir RESTful untuk bekerja dengan titik akhir Penyedia Sumber Daya Kustom Azure. Untuk mempelajari cara membuat penyedia sumber daya kustom, buka artikel Membuat dan menggunakan penyedia sumber daya kustom.