Azure Durable Functions Function Chaining Example in C# — DemoTwo (.NET 8)

An Azure Durable Functions v4 example of the function chaining pattern in C#. An HTTP trigger starts an orchestrator that calls three activity functions in sequence — input validation with a regular expression, processing, and greeting generation — passing each activity’s output to the next one.

Architecture

POST /api/func-message-validator
          │
          ▼
 func-message-validator        (HTTP client trigger)
          │  schedules orchestration
          ▼
 func-message-orchestrator     (orchestrator)
          │
          ├─► func-validate-input   (validates: letters and spaces only)
          │
          ├─► func-process-input    (passes value through; returns empty if blank)
          │
          └─► func-say-hello        (returns "Hello {name}!" or empty string)

Functions

Function Trigger Description
func-message-validator HTTP POST Starts orchestration, waits for result, returns 200 or 400
func-message-orchestrator Orchestration Chains the three activity functions
func-validate-input Activity Accepts only letters and spaces (^[A-Za-z\s]+$); returns empty string if invalid
func-process-input Activity Passes the value through; returns empty string if input is blank
func-say-hello Activity Returns "Hello {name}!" or empty string if name is blank

Validation Rule

The func-validate-input activity enforces the pattern ^[A-Za-z\s]+$. Any name containing digits or special characters will be rejected and an empty string will propagate through the pipeline, resulting in a 400 response.

Requirements

Minimum local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
  }
}

Requires Azurite or a real Azure Storage connection string for durable task state.

NuGet Packages

Package Version
Microsoft.Azure.Functions.Worker 2.0.0
Microsoft.Azure.Functions.Worker.Extensions.DurableTask 1.2.3
Microsoft.Azure.Functions.Worker.Extensions.Http 3.3.0
Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore 2.0.1
Microsoft.Azure.Functions.Worker.Sdk 2.0.2

Running Locally

cd DemoTwo
func start --port 7176

Usage

Valid name:

curl -X POST http://localhost:7176/api/func-message-validator \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice"}'

Intended response: "Hello Alice!" — but see Known issue: the current code returns 400 Bad Request here too.

Invalid name (digits or special characters):

curl -X POST http://localhost:7176/api/func-message-validator \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice123"}'

Response: 400 Bad Request

Project Structure

DemoTwo/
├── Functions/
│   ├── FunctionClient.cs          # HTTP trigger — starts orchestration
│   ├── FunctionOrchestrator.cs    # Orchestrator — chains three activities
│   └── Triggers/
│       ├── ValidateInput.cs       # Activity: regex validation
│       ├── ProcessInput.cs        # Activity: pass-through / blank check
│       └── ModifyMessage.cs       # Activity: greeting generation
├── Properties/
│   └── launchSettings.json        # Default port: 7176
├── Program.cs                     # Host builder entry point
├── host.json
└── DemoTwo.csproj

Known issue

FunctionClient.RunAsync decides whether to return 400 with this check:

if (response.SerializedOutput.Contains(""))

string.Contains("") is always true, so every request — valid or not — returns an empty 400 Bad Request. (The badResponse object that is written with “Request body cannot be processed.” is also never returned.) The orchestration itself runs correctly; only the final status check is wrong.

A fix is to deserialize the output and test for an empty string, for example:

var output = response.ReadOutputAs<string>();
if (string.IsNullOrEmpty(output))
{
    return new BadRequestObjectResult("Request body cannot be processed.");
}
return new OkObjectResult(output);

Read all C# and .NET Code Examples guides online · Project overview · All developer projects