Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions samples/precompiled/BackupSiteContent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates the Fan-out/Fan-in pattern. In this pattern, multiple activity
* functions are executed in parallel, and then the orchestrator waits for all of them to
* complete before continuing. This is useful when you need to perform the same operation
* on multiple items concurrently, such as backing up multiple files.
*
* Pattern documentation:
* https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-cloud-backup
*
* To run this sample:
* 1. Configure the AzureWebJobsStorage connection string in local.settings.json
* 2. Start the function app locally using `func host start` or run from Visual Studio
* 3. Make an HTTP POST request to: http://localhost:7071/orchestrators/E2_BackupSiteContent
* Optionally include a JSON body with the root directory path to backup
*
* Required app settings:
* - AzureWebJobsStorage: Azure Storage connection string for storing backup blobs
*/
using System.IO;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -13,20 +30,25 @@ namespace VSSample
{
public static class BackupSiteContent
{
// Orchestrator function that demonstrates Fan-out/Fan-in by backing up files in parallel.
// First gets the file list (single activity), then fans out to copy each file concurrently.
[FunctionName("E2_BackupSiteContent")]
public static async Task<long> Run(
[OrchestrationTrigger] IDurableOrchestrationContext backupContext)
{
// Get the root directory from input, or default to the assembly directory
string rootDirectory = backupContext.GetInput<string>()?.Trim();
if (string.IsNullOrEmpty(rootDirectory))
{
rootDirectory = Directory.GetParent(typeof(BackupSiteContent).Assembly.Location).FullName;
}

// Step 1: Get the list of files to backup (single activity call)
string[] files = await backupContext.CallActivityAsync<string[]>(
"E2_GetFileList",
rootDirectory);

// Step 2: Fan-out - start all file copy tasks in parallel (no await yet)
var tasks = new Task<long>[files.Length];
for (int i = 0; i < files.Length; i++)
{
Expand All @@ -35,12 +57,15 @@ public static async Task<long> Run(
files[i]);
}

// Step 3: Fan-in - wait for all parallel tasks to complete
await Task.WhenAll(tasks);

// Aggregate results from all tasks
long totalBytes = tasks.Sum(t => t.Result);
return totalBytes;
}

// Activity function that retrieves the list of files from a directory.
[FunctionName("E2_GetFileList")]
public static string[] GetFileList(
[ActivityTrigger] string rootDirectory,
Expand All @@ -53,6 +78,8 @@ public static string[] GetFileList(
return files;
}

// Activity function that copies a single file to Azure Blob Storage.
// This function is called in parallel for each file (fan-out).
[FunctionName("E2_CopyFileToBlob")]
public static async Task<long> CopyFileToBlob(
[ActivityTrigger] string filePath,
Expand Down
39 changes: 29 additions & 10 deletions samples/precompiled/Counter.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,53 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates the Aggregator (Entity) pattern using Durable Entities.
* Entities are stateful actors that can be created, updated, and queried.
* Unlike orchestrations, entities have stable identities and their state persists
* indefinitely until explicitly deleted.
*
* Pattern documentation:
* https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-entities
*
* To run this sample, use the built-in entity webhooks (no client function needed):
* 1. Start the function app locally using `func host start` or run from Visual Studio
* 2. Reset the counter:
* curl -X POST -H "Content-Length: 0" "http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Reset"
* 3. Add to the counter:
* curl -d "1" -X POST -H "Content-Type: application/json" http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Add
* curl -d "2" -X POST -H "Content-Type: application/json" http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Add
* 4. Read the counter value:
* curl http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter
*
* The result of the final GET operation should be: {"value":3}
*
* No special app settings are required for this sample.
*/
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Newtonsoft.Json;

namespace VSSample
{
// This sample does not include any "client" functions. Instead, you can just use
// the built-in webhooks to create and interact with this entity type.
//
// Examples:
// curl -X POST -H "Content-Length: 0" "http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Reset"
// curl -d "1" -X POST -H "Content-Type: application/json" http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Add
// curl -d "2" -X POST -H "Content-Type: application/json" http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter?op=Add
// curl http://localhost:7071/runtime/webhooks/durabletask/entities/Counter/MyCounter
//
// The result of the final GET operation should be: {"value":3}
// Entity class that maintains a counter value with Add, Reset, and Get operations.
// The class-based syntax provides a cleaner way to define entity behavior.
public class Counter
{
// The current counter value - persisted as entity state
[JsonProperty("value")]
public int CurrentValue { get; set; }

// Operation: Add an amount to the current counter value
public void Add(int amount) => this.CurrentValue += amount;

// Operation: Reset the counter to zero
public void Reset() => this.CurrentValue = 0;

// Operation: Get the current counter value
public int Get() => this.CurrentValue;

// Entity function entry point that dispatches operations to the Counter class
[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<Counter>();
Expand Down
23 changes: 22 additions & 1 deletion samples/precompiled/HelloSequence.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates the Function Chaining pattern. In this pattern, activity functions
* are called sequentially, where the output of one function can be used as input to the next.
* This is the most basic Durable Functions pattern and is useful when you need to execute
* a sequence of operations in a specific order.
*
* Pattern documentation:
* https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-sequence
*
* To run this sample:
* 1. Start the function app locally using `func host start` or run from Visual Studio
* 2. Make an HTTP POST request to: http://localhost:7071/orchestrators/E1_HelloSequence
*
* No special app settings are required for this sample.
*/
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
Expand All @@ -10,12 +24,15 @@ namespace VSSample
{
public static class HelloSequence
{
// Orchestrator function that chains multiple activity function calls sequentially.
// Each activity is awaited before calling the next, demonstrating ordered execution.
[FunctionName("E1_HelloSequence")]
public static async Task<List<string>> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var outputs = new List<string>();

// Call activities sequentially - each call waits for the previous to complete
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello", "Seattle"));
outputs.Add(await context.CallActivityAsync<string>("E1_SayHello_DirectInput", "London"));
Expand All @@ -24,17 +41,21 @@ public static async Task<List<string>> Run(
return outputs;
}

// Activity function that demonstrates getting input via the activity context.
// This approach allows access to additional context properties if needed.
[FunctionName("E1_SayHello")]
public static string SayHello([ActivityTrigger] IDurableActivityContext context)
{
string name = context.GetInput<string>();
return $"Hello {name}!";
}

// Activity function that demonstrates direct input binding.
// This is simpler when you only need the input value.
[FunctionName("E1_SayHello_DirectInput")]
public static string SayHelloDirectInput([ActivityTrigger] string name)
{
return $"Hello {name}!";
}
}
}
}
20 changes: 20 additions & 0 deletions samples/precompiled/HttpStart.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates how to start orchestrations via HTTP.
* The HttpStart function is a generic HTTP trigger that can start any orchestration
* by name. It returns the standard Durable Functions response containing URLs for
* checking status, sending events, and terminating the orchestration.
*
* To run this sample:
* 1. Start the function app locally using `func host start` or run from Visual Studio
* 2. Start an orchestration by posting to:
* curl -i -X POST http://localhost:7071/orchestrators/{functionName} -H "Content-Length: 0"
* Replace {functionName} with the name of the orchestration to start (e.g., E1_HelloSequence)
* 3. Optionally include JSON input in the request body
*
* The response includes a Location header and URLs for managing the orchestration instance.
*
* No special app settings are required for this sample.
*/
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
Expand All @@ -12,6 +28,8 @@ namespace VSSample
{
public static class HttpStart
{
// HTTP trigger function that starts an orchestration by name.
// Uses route parameter to determine which orchestration to start.
[FunctionName("HttpStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
Expand All @@ -25,6 +43,8 @@ public static async Task<HttpResponseMessage> Run(

log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

// CreateCheckStatusResponse returns the standard Durable Functions response
// containing status query URLs and management endpoints
return starter.CreateCheckStatusResponse(req, instanceId);
}
}
Expand Down
25 changes: 25 additions & 0 deletions samples/precompiled/HttpSyncStart.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates synchronous HTTP orchestration starting.
* Unlike HttpStart which returns immediately with status URLs, this function
* waits for the orchestration to complete before returning the result.
* This is useful when orchestrations complete quickly and you want to get
* the result in a single HTTP request.
*
* To run this sample:
* 1. Start the function app locally using `func host start` or run from Visual Studio
* 2. Start an orchestration and wait for completion:
* curl -i -X POST "http://localhost:7071/orchestrators/{functionName}/wait" -H "Content-Length: 0"
* Replace {functionName} with the name of the orchestration to start (e.g., E1_HelloSequence)
* 3. Optional query parameters:
* - timeout: Maximum seconds to wait (default: 30)
* - retryInterval: Seconds between status checks (default: 1)
*
* If the orchestration completes within the timeout, the result is returned directly.
* If it doesn't complete in time, the standard status check URLs are returned instead.
*
* No special app settings are required for this sample.
*/
using System;
using System.Net.Http;
using System.Threading.Tasks;
Expand All @@ -16,6 +36,8 @@ public static class HttpSyncStart
private const string Timeout = "timeout";
private const string RetryInterval = "retryInterval";

// HTTP trigger function that starts an orchestration and waits for it to complete.
// Returns the orchestration result if it completes within the timeout.
[FunctionName("HttpSyncStart")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}/wait")]
Expand All @@ -30,16 +52,19 @@ public static async Task<HttpResponseMessage> Run(

log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

// Parse optional timeout and retry interval from query string
TimeSpan timeout = GetTimeSpan(req, Timeout) ?? TimeSpan.FromSeconds(30);
TimeSpan retryInterval = GetTimeSpan(req, RetryInterval) ?? TimeSpan.FromSeconds(1);

// Wait for the orchestration to complete, or return status URLs if it takes too long
return await starter.WaitForCompletionOrCreateCheckStatusResponseAsync(
req,
instanceId,
timeout,
retryInterval);
}

// Helper method to parse TimeSpan from query string parameters
private static TimeSpan? GetTimeSpan(HttpRequestMessage request, string queryParameterName)
{
string queryParameterStringValue = request.RequestUri.ParseQueryString()[queryParameterName];
Expand Down
33 changes: 33 additions & 0 deletions samples/precompiled/PhoneVerification.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

/* This sample demonstrates the Human Interaction pattern. In this pattern, the orchestrator
* waits for an external event (human input) before continuing. This example implements
* SMS-based phone verification where a code is sent to the user and they must respond
* with the correct code within a timeout period.
*
* Pattern documentation:
* https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-phone-verification
*
* To run this sample:
* 1. Configure the required Twilio app settings (see below)
* 2. Start the function app locally using `func host start` or run from Visual Studio
* 3. Make an HTTP POST request to: http://localhost:7071/orchestrators/E4_SmsPhoneVerification
* Include the phone number as a JSON string in the request body
* 4. After receiving the SMS code, send an event to the orchestration:
* POST to the sendEventPostUri with eventName "SmsChallengeResponse" and the code as body
*
* Required app settings:
* - TwilioAccountSid: Your Twilio account's SID
* - TwilioAuthToken: Your Twilio account's auth token
* - TwilioPhoneNumber: An SMS-capable Twilio phone number
*
* For Twilio trial accounts, you must verify the destination phone number first.
* Twilio: https://www.twilio.com
*/
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -14,6 +38,8 @@ namespace VSSample
{
public static class PhoneVerification
{
// Orchestrator function that waits for external human interaction (SMS verification).
// Demonstrates timeout handling and external event correlation.
[FunctionName("E4_SmsPhoneVerification")]
public static async Task<bool> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
Expand All @@ -26,6 +52,7 @@ public static async Task<bool> Run(
"A phone number input is required.");
}

// Send an SMS with a verification code to the user
int challengeCode = await context.CallActivityAsync<int>(
"E4_SendSmsChallenge",
phoneNumber);
Expand All @@ -37,11 +64,14 @@ public static async Task<bool> Run(
Task timeoutTask = context.CreateTimer(expiration, timeoutCts.Token);

bool authorized = false;
// Allow up to 3 retry attempts for incorrect codes
for (int retryCount = 0; retryCount <= 3; retryCount++)
{
// Wait for the user to send the "SmsChallengeResponse" event with their code
Task<int> challengeResponseTask =
context.WaitForExternalEvent<int>("SmsChallengeResponse");

// Race between user response and timeout
Task winner = await Task.WhenAny(challengeResponseTask, timeoutTask);
if (winner == challengeResponseTask)
{
Expand All @@ -51,6 +81,7 @@ public static async Task<bool> Run(
authorized = true;
break;
}
// Wrong code - continue to next retry iteration
}
else
{
Expand All @@ -69,6 +100,8 @@ public static async Task<bool> Run(
}
}

// Activity function that generates a random code and sends it via SMS using Twilio.
// The code is returned to the orchestrator for verification against user input.
[FunctionName("E4_SendSmsChallenge")]
public static int SendSmsChallenge(
[ActivityTrigger] string phoneNumber,
Expand Down
Loading
Loading