Edit

Quickstart: Create a C# Durable Functions app

Use Durable Functions, a feature of Azure Functions, to write stateful serverless workflows in C#. In this quickstart, you clone and run a sample app that demonstrates the function chaining orchestration pattern:

  • Function chaining: Calls activities sequentially (Tokyo → Seattle → London).

By the end, you'll have the orchestration running locally with the Durable Task Scheduler emulator and be able to view its status in the dashboard.

  • Clone and prepare the Hello Cities sample project.
  • Set up the Durable Task Scheduler emulator and Azurite for local development.
  • Build and run the function app and trigger the orchestration.
  • Review orchestration status and output in the Durable Task Scheduler dashboard.

Prerequisites

Set up the Durable Task Scheduler emulator

The Durable Task Scheduler emulator provides a local development environment so you can test orchestrations without an Azure subscription. The .NET Functions host also requires Azurite for local storage.

Start both containers:

docker run -d --name dtsemulator -p 8080:8080 -p 8082:8082 \
  mcr.microsoft.com/dts/dts-emulator:latest

docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 \
  mcr.microsoft.com/azure-storage/azurite

Tip

Once the emulator is running, you can access the Durable Task Scheduler dashboard at http://localhost:8082 to monitor orchestrations.

Run the quickstart sample

  1. Navigate to the Hello Cities sample directory:

    cd samples/durable-functions/dotnet/HelloCities/http
    
  2. Create a local.settings.json file with the emulator configuration:

    {
      "IsEncrypted": false,
      "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "DURABLE_TASK_SERVICE_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
        "TASKHUB_NAME": "default"
      }
    }
    
  3. Build the project:

    dotnet build
    
  4. Start the function app:

    func start
    
  5. In a separate terminal, trigger the orchestration:

    $response = Invoke-RestMethod -Method POST -Uri http://localhost:7071/api/DurableFunctionsOrchestrationCSharp1_HttpStart
    $response
    

    The response contains status URLs for the orchestration instance. Copy the statusQueryGetUri value and run it to check the result:

    Invoke-RestMethod -Uri $response.statusQueryGetUri
    

Expected output

The POST request returns a JSON response with status URLs. For example:

{
  "id": "<instanceId>",
  "statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/<instanceId>?code=...",
  "sendEventPostUri": "...",
  "terminatePostUri": "...",
  "purgeHistoryDeleteUri": "..."
}

When you query statusQueryGetUri and the orchestration's runtimeStatus is Completed, you can find the greeting results in the output field:

{
  "name": "DurableFunctionsOrchestrationCSharp1",
  "runtimeStatus": "Completed",
  "output": ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
}

Tip

If runtimeStatus shows Running or Pending, wait a moment and query the statusQueryGetUri again.

Open the Durable Task Scheduler dashboard at http://localhost:8082 to view the orchestration status and execution history.

Understand the code

The sample project in DurableFunctionsOrchestrationCSharp1.cs contains all three function types needed for a Durable Functions app.

Activity function

The SayHello activity takes a city name and returns a greeting:

[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("SayHello");
    logger.LogInformation("Saying hello to {name}.", name);
    return $"Hello {name}!";
}

Orchestrator function

The orchestrator calls SayHello sequentially for three cities:

[Function(nameof(DurableFunctionsOrchestrationCSharp1))]
public static async Task<List<string>> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    ILogger logger = context.CreateReplaySafeLogger(nameof(DurableFunctionsOrchestrationCSharp1));
    logger.LogInformation("Saying hello.");
    var outputs = new List<string>();

    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"));
    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"));
    outputs.Add(await context.CallActivityAsync<string>(nameof(SayHello), "London"));

    return outputs;
}

Client function

An HTTP-triggered client function starts the orchestration:

[Function("DurableFunctionsOrchestrationCSharp1_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("DurableFunctionsOrchestrationCSharp1_HttpStart");
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(DurableFunctionsOrchestrationCSharp1));

    logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}

Configuration

The sample uses the Durable Task Scheduler emulator as its storage backend. This is configured in host.json:

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "type": "azureManaged",
        "connectionStringName": "DURABLE_TASK_SERVICE_CONNECTION_STRING"
      },
      "hubName": "%TASKHUB_NAME%"
    }
  }
}

The emulator connection string and task hub name are set in local.settings.json:

{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "DURABLE_TASK_SERVICE_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
    "TASKHUB_NAME": "default"
  }
}

Clean up resources

Stop the emulator containers when you're done:

docker stop dtsemulator azurite && docker rm dtsemulator azurite

Next steps