|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +using Microsoft.Azure.Functions.Worker; |
| 5 | +using Microsoft.Azure.Functions.Worker.Http; |
| 6 | +using Microsoft.DurableTask; |
| 7 | +using Microsoft.DurableTask.Client; |
| 8 | +using Microsoft.Extensions.Logging; |
| 9 | + |
| 10 | +namespace AzureFunctionsApp.Approval; |
| 11 | + |
| 12 | +/// <summary> |
| 13 | +/// HTTP-triggered function that starts the <see cref="ApprovalOrchestrator"/> orchestration. |
| 14 | +/// </summary> |
| 15 | +public static class ApprovalOrchestratorStarter |
| 16 | +{ |
| 17 | + [Function(nameof(StartApprovalOrchestrator))] |
| 18 | + public static async Task<HttpResponseData> StartApprovalOrchestrator( |
| 19 | + [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req, |
| 20 | + [DurableClient] DurableTaskClient client, |
| 21 | + FunctionContext executionContext) |
| 22 | + { |
| 23 | + ILogger logger = executionContext.GetLogger(nameof(StartApprovalOrchestrator)); |
| 24 | + |
| 25 | + string? requestName = await req.ReadAsStringAsync(); |
| 26 | + if (string.IsNullOrEmpty(requestName)) |
| 27 | + { |
| 28 | + requestName = "Sample Request"; |
| 29 | + } |
| 30 | + |
| 31 | + // Use the generated type-safe extension method to start the orchestration |
| 32 | + string instanceId = await client.ScheduleNewApprovalOrchestratorInstanceAsync(requestName); |
| 33 | + logger.LogInformation("Started approval orchestration with instance ID = {instanceId}", instanceId); |
| 34 | + |
| 35 | + return client.CreateCheckStatusResponse(req, instanceId); |
| 36 | + } |
| 37 | + |
| 38 | + [Function(nameof(SendApprovalEvent))] |
| 39 | + public static async Task<HttpResponseData> SendApprovalEvent( |
| 40 | + [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "approval/{instanceId}")] HttpRequestData req, |
| 41 | + [DurableClient] DurableTaskClient client, |
| 42 | + string instanceId, |
| 43 | + FunctionContext executionContext) |
| 44 | + { |
| 45 | + ILogger logger = executionContext.GetLogger(nameof(SendApprovalEvent)); |
| 46 | + |
| 47 | + string? approverName = await req.ReadAsStringAsync(); |
| 48 | + bool isApproved = req.Url.Query.Contains("approve=true"); |
| 49 | + |
| 50 | + // Raise the ApprovalEvent |
| 51 | + await client.RaiseEventAsync(instanceId, "ApprovalEvent", new ApprovalEvent(isApproved, approverName)); |
| 52 | + logger.LogInformation("Sent approval event to instance {instanceId}: approved={isApproved}, approver={approverName}", |
| 53 | + instanceId, isApproved, approverName); |
| 54 | + |
| 55 | + var response = req.CreateResponse(System.Net.HttpStatusCode.Accepted); |
| 56 | + await response.WriteStringAsync($"Approval event sent to instance {instanceId}"); |
| 57 | + return response; |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +/// <summary> |
| 62 | +/// Example event type for approval workflows. |
| 63 | +/// The DurableEventAttribute generates a strongly-typed WaitForApprovalEventAsync method. |
| 64 | +/// </summary> |
| 65 | +[DurableEvent(nameof(ApprovalEvent))] |
| 66 | +public sealed record ApprovalEvent(bool Approved, string? Approver); |
| 67 | + |
| 68 | +/// <summary> |
| 69 | +/// Orchestrator that demonstrates strongly-typed external events. |
| 70 | +/// </summary> |
| 71 | +[DurableTask(nameof(ApprovalOrchestrator))] |
| 72 | +public class ApprovalOrchestrator : TaskOrchestrator<string, string> |
| 73 | +{ |
| 74 | + public override async Task<string> RunAsync(TaskOrchestrationContext context, string requestName) |
| 75 | + { |
| 76 | + ILogger logger = context.CreateReplaySafeLogger<ApprovalOrchestrator>(); |
| 77 | + logger.LogInformation("Approval request received for: {requestName}", requestName); |
| 78 | + |
| 79 | + // Send a notification that approval is required |
| 80 | + await context.CallNotifyApprovalRequiredAsync(requestName); |
| 81 | + |
| 82 | + // Wait for approval event using the generated strongly-typed method |
| 83 | + // This method is generated by the source generator from the DurableEventAttribute |
| 84 | + ApprovalEvent approvalEvent = await context.WaitForApprovalEventAsync(); |
| 85 | + |
| 86 | + string result; |
| 87 | + if (approvalEvent.Approved) |
| 88 | + { |
| 89 | + result = $"Request '{requestName}' was approved by {approvalEvent.Approver ?? "unknown"}"; |
| 90 | + logger.LogInformation("Request approved: {result}", result); |
| 91 | + } |
| 92 | + else |
| 93 | + { |
| 94 | + result = $"Request '{requestName}' was rejected by {approvalEvent.Approver ?? "unknown"}"; |
| 95 | + logger.LogInformation("Request rejected: {result}", result); |
| 96 | + } |
| 97 | + |
| 98 | + return result; |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +/// <summary> |
| 103 | +/// Activity that simulates sending an approval notification. |
| 104 | +/// </summary> |
| 105 | +[DurableTask(nameof(NotifyApprovalRequired))] |
| 106 | +public class NotifyApprovalRequired : TaskActivity<string, string> |
| 107 | +{ |
| 108 | + readonly ILogger logger; |
| 109 | + |
| 110 | + public NotifyApprovalRequired(ILogger<NotifyApprovalRequired> logger) |
| 111 | + { |
| 112 | + this.logger = logger; |
| 113 | + } |
| 114 | + |
| 115 | + public override Task<string> RunAsync(TaskActivityContext context, string requestName) |
| 116 | + { |
| 117 | + this.logger.LogInformation("Approval required for: {requestName} (Instance: {instanceId})", |
| 118 | + requestName, context.InstanceId); |
| 119 | + return Task.FromResult("Notification sent"); |
| 120 | + } |
| 121 | +} |
0 commit comments