Reputation: 3736
I have an Orchestrator function as follows:
[FunctionName("O_OrchestratorFunction)]
public async Task<object> Process(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
await context.CallActivityAsync("A_SendApprovalRequestEmail", "email");
var _approvalResult = await context.WaitForExternalEvent<string>("ApprovalResult");
if (_approvalResult == "Approved")
{
await context.CallActivityAsync("A_ResumeJobSync", "email");
}
else
{
await context.CallActivityAsync("A_PauseJobSync", "email");
}
return _approvalResult;
}
}
Here is my A_SendApprovalRequestEmail Activiy function:
[FunctionName("A_SendApprovalRequestEmail")]
public async Task Run([ActivityTrigger] string job, TraceWriter log)
{
var functionAddress = $"http://localhost:7071/api/";
var approvedLink = functionAddress + "approve?id=MYID";
var rejectedLink = functionAddress + "reject?id=MYID";
var content = $"{approvedLink} {rejectedLink}";
await _mailRepository.SendMail("Subject", content, "<recipient-address>");
log.Info($"Requesting approval for {job}");
await Task.Delay(1000);
}
How do I modify this code to connect 2 links to the orchestrator - one for Approve and one for Reject (that returns the result ‘Approved’ or ‘Rejected’)?
UPDATE:
I have added 2 starter functions:
[FunctionName("Approve")]
public static async Task<IActionResult> Approve(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req,
ILogger log,
[DurableClient] IDurableOrchestrationClient client)
{
string orchestratorId = req.Query["id"];
log.LogInformation($"Approval request for {orchestratorId}");
var status = await client.GetStatusAsync(orchestratorId);
if (status == null || status.RuntimeStatus != OrchestrationRuntimeStatus.Running)
return new NotFoundResult();
await client.RaiseEventAsync(orchestratorId, "ApprovalResult", "Approve");
return new OkObjectResult("Approval successfull");
}
[FunctionName("Reject")]
public static async Task<IActionResult> Reject(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req,
ILogger log,
[DurableClient] IDurableOrchestrationClient client)
{
string orchestratorId = req.Query["id"];
log.LogInformation($"Reject request for {orchestratorId}");
var status = await client.GetStatusAsync(orchestratorId);
if (status == null || status.RuntimeStatus != OrchestrationRuntimeStatus.Running)
return new NotFoundResult();
await client.RaiseEventAsync(orchestratorId, "ApprovalResult", "Reject");
return new OkObjectResult("Rejected");
}
This is what I see on clicking Approve/Reject link:
On putting breakpoint at
var status = await client.GetStatusAsync(orchestratorId);
I see status as null.
Upvotes: 1
Views: 2958
Reputation: 1484
You would need to create 2 HTTP functions for reject and approve just as you created your starter function with the IDurableOrchestratorClient. These functions would need to receive some way to get the unique orchestratorid that was used to start the orchestration. This is stored in the context.InstanceId property in the orchestrator. The easiest way is just to send this in the query www.test.com/api/approve?id=myid but there are other solutions where you map the query value to which orchestrator through e.g DB or jwt token. This HTTP triggered function is what link will be in your email. The HTTP triggerd function can then with this id raise events in the orchestrator.
[FunctionName("Approve")]
public static async Task<IActionResult> Approve(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req,
ILogger log,
[DurableClient] IDurableOrchestrationClient client)
{
// Takes the id parameter in the url e.g. www.test.com/api/approve?id=SomeID would return SomeID
string orchestratorId = req.Query["id"];
log.LogInformation($"Approval request for {orchestratorId}");
// Makes sure there is a running instance with this orchestrator
var status = await client.GetStatusAsync(orchestratorId);
if (status == null || status.RuntimeStatus != OrchestrationRuntimeStatus.Running)
return new NotFoundResult();
//Raises event ApprovalResult with data Approve
await client.RaiseEventAsync(orchestratorId, "ApprovalResult", "Approve");
return new OkObjectResult("Approval successfull");
}
[FunctionName("Reject")]
public static async Task<IActionResult> Reject(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
HttpRequest req,
ILogger log,
[DurableClient] IDurableOrchestrationClient client)
{
string orchestratorId = req.Query["id"];
log.LogInformation($"Reject request for {orchestratorId}");
var status = await client.GetStatusAsync(orchestratorId);
if (status == null || status.RuntimeStatus != OrchestrationRuntimeStatus.Running)
return new NotFoundResult();
await client.RaiseEventAsync(orchestratorId, "ApprovalResult", "Reject");
return new OkObjectResult("Rejected");
}
[FunctionName("O_OrchestratorFunction)]
public async Task<object> Process(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger log)
{
TimeSpan timeout = TimeSpan.FromMinutes(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
await context.CallActivityAsync("A_SendApprovalRequestEmail", new Tuple<string,string>("email", context.InstanceId));
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
var approvalTask = context.WaitForExternalEvent<string("ApprovalResult");
Task task = await Task.WhenAny(activityTask, timeoutTask);
if (task == approvalTask)
{
cts.cancel();
if(task.Result == "Approve")
await context.CallActivityAsync("A_ResumeJobSync", "email");
else
await context.CallActivityAsync("A_PauseJobSync", "email");
}
else
{
// Code for timeout scenario (maybe execute A_PauseJobSync)
}
return _approvalResult;
}
}
}
[FunctionName("A_SendApprovalRequestEmail")]
public async Task Run([ActivityTrigger] Tuple<string, string> data, TraceWriter log)
{ // data.Item1 = "email" & data.Item2 = context.InstanceId
var functionAddress = $"http://localhost:7071/api/";
var approvedLink = functionAddress + $"approve?id={data.Item2}";
var rejectedLink = functionAddress + $"reject?id={data.Item2}";
var content = $"{approvedLink} {rejectedLink}";
await _mailRepository.SendMail("Subject", content, "<recipient-address>");
log.Info($"Requesting approval for {job}");
await Task.Delay(1000);
}
Here are the two HTTP triggered functions you can use as approve and reject functions and you would then in your content in the email link to www.myfuncapp.com/api/approve?id=MYID when someone clicks that link the approve function would run and raise event ApprovalResult with data Approve.
Upvotes: 1