自訂資源提供者 C# RESTful 端點參考
此文章是自訂資源提供者 C# RESTful 端點的基本參考。 如果您不熟悉 Azure 自訂資源提供者,請參閱自訂資源提供者概觀。
Azure Functions RESTful 端點
下列程式碼適用於 Azure 中的函式應用程式。 若要了解如何設定函式應用程式來搭配 Azure 自訂資源提供者使用,請參閱有關為 Azure 自訂資源提供者設定 Azure Functions 的教學課程。
#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 Microsoft.WindowsAzure.Storage.Table;
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
// Custom Resource Table Entity
public class CustomResource : TableEntity
{
public string Data { get; set; }
}
/// <summary>
/// Entry point for the Azure Function 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="tableStorage">The Azure Table storage account.</param>
/// <returns>The HTTP response for the custom Azure API.</returns>
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ILogger log, CloudTable tableStorage)
{
// 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,
tableStorage: tableStorage,
partitionKey: partitionKey,
resourceType: rowKey);
// Retrieve request for a custom resource.
case HttpMethod m when m == HttpMethod.Get && isResourceRequest:
return await RetrieveCustomResource(
requestMessage: req,
tableStorage: tableStorage,
partitionKey: partitionKey,
rowKey: rowKey);
// Create request for a custom resource.
case HttpMethod m when m == HttpMethod.Put && isResourceRequest:
return await CreateCustomResource(
requestMessage: req,
tableStorage: tableStorage,
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,
tableStorage: tableStorage,
partitionKey: partitionKey,
rowKey: rowKey);
// Invalid request received.
default:
return req.CreateResponse(HttpStatusCode.BadRequest);
}
}
/// <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;
}
/// <summary>
/// Enumerates all the stored custom resources for a given type.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableStorage">The Azure Table storage account.</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, CloudTable tableStorage, string partitionKey, string resourceType)
{
// Generate upper bound of the query.
var rowKeyUpperBound = new StringBuilder(resourceType);
rowKeyUpperBound[rowKeyUpperBound.Length - 1]++;
// Create the enumeration query.
var enumerationQuery = new TableQuery<CustomResource>().Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey),
TableOperators.And,
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThan, resourceType),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, rowKeyUpperBound.ToString()))));
var customResources = (await tableStorage.ExecuteQuerySegmentedAsync(enumerationQuery, null))
.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;
}
/// <summary>
/// Retrieves a custom resource.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableStorage">The Azure Table storage account.</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, CloudTable tableStorage, string partitionKey, string rowKey)
{
// Attempt to retrieve the Existing Stored Value
var tableQuery = TableOperation.Retrieve<CustomResource>(partitionKey, rowKey);
var existingCustomResource = (CustomResource)(await tableStorage.ExecuteAsync(tableQuery)).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;
}
/// <summary>
/// Creates a custom resource and saves it to Table storage.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableStorage">The Azure Table storage account.</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, CloudTable tableStorage, ResourceId azureResourceId, string partitionKey, string rowKey)
{
// Constructs the new resource from the request body and adds the Azure Resource Manager fields.
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 insertOperation = TableOperation.InsertOrReplace(
new CustomResource
{
PartitionKey = partitionKey,
RowKey = rowKey,
Data = myCustomResource.ToString(),
});
await tableStorage.ExecuteAsync(insertOperation);
var createResponse = requestMessage.CreateResponse(HttpStatusCode.OK);
createResponse.Content = new StringContent(myCustomResource.ToString(), System.Text.Encoding.UTF8, "application/json");
return createResponse;
}
/// <summary>
/// Removes an existing custom resource.
/// </summary>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="tableStorage">The Azure Table storage account.</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, CloudTable tableStorage, string partitionKey, string rowKey)
{
// Attempt to retrieve the existing stored value
var tableQuery = TableOperation.Retrieve<CustomResource>(partitionKey, rowKey);
var existingCustomResource = (CustomResource)(await tableStorage.ExecuteAsync(tableQuery)).Result;
if (existingCustomResource != null) {
var deleteOperation = TableOperation.Delete(existingCustomResource);
await tableStorage.ExecuteAsync(deleteOperation);
}
return requestMessage.CreateResponse(
existingCustomResource != null ? HttpStatusCode.OK : HttpStatusCode.NoContent);
}