diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.csproj b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.csproj
new file mode 100644
index 0000000..93d619e
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.http b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.http
new file mode 100644
index 0000000..03b40b2
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.http
@@ -0,0 +1,88 @@
+### Variables
+@baseUrl = http://localhost:5009
+@jobId = export-job-12345
+
+### Create a new batch export job
+# @name createBatchExportJob
+POST {{baseUrl}}/export-jobs
+Content-Type: application/json
+
+{
+ "jobId": "{{jobId}}",
+ "mode": "Batch",
+ "completedTimeFrom": "2025-10-01T00:00:00Z",
+ "completedTimeTo": "2025-11-06T23:59:59Z",
+ "container": "export-history",
+ "prefix": "batch-exports/",
+ "maxInstancesPerBatch": 1,
+ "runtimeStatus": []
+}
+
+### Create a new continuous export job
+# @name createContinuousExportJob
+POST {{baseUrl}}/export-jobs
+Content-Type: application/json
+
+{
+ "jobId": "export-job-continuous-123",
+ "mode": "Continuous",
+ "completedTimeFrom": "2025-10-01T00:00:00Z",
+ "container": "export-history",
+ "prefix": "continuous-exports/",
+ "maxInstancesPerBatch": 1000
+}
+
+### Create an export job with default storage (no container specified)
+# @name createExportJobWithDefaultStorage
+POST {{baseUrl}}/export-jobs
+Content-Type: application/json
+{
+ "jobId": "export-job-default-storage",
+ "mode": "Batch",
+ "completedTimeFrom": "2024-01-01T00:00:00Z",
+ "completedTimeTo": "2024-12-31T23:59:59Z",
+ "maxInstancesPerBatch": 100
+}
+
+### Get a specific export job by ID
+# Note: This endpoint can be used to verify the export job was created and check its status
+# The ID in the URL should match the jobId used in create request
+GET {{baseUrl}}/export-jobs/{{jobId}}
+
+### List all export jobs
+GET {{baseUrl}}/export-jobs/list
+
+### List export jobs with filters
+### Filter by status
+GET {{baseUrl}}/export-jobs/list?status=Active
+
+### Filter by job ID prefix
+GET {{baseUrl}}/export-jobs/list?jobIdPrefix=export-job-
+
+### Filter by creation time range
+GET {{baseUrl}}/export-jobs/list?createdFrom=2024-01-01T00:00:00Z&createdTo=2024-12-31T23:59:59Z
+
+### Combined filters
+GET {{baseUrl}}/export-jobs/list?status=Completed&jobIdPrefix=export-job-&pageSize=50
+
+### Delete an export job
+# DELETE {{baseUrl}}/export-jobs/{{jobId}}
+
+# Delete a continuous export job
+DELETE {{baseUrl}}/export-jobs/export-job-continuous-123
+
+### Tips:
+# - Replace the baseUrl variable if your application runs on a different port
+# - The jobId variable can be changed to test different export job instances
+# - Export modes:
+# - "Batch": Exports all instances within a time range (requires completedTimeTo)
+# - "Continuous": Continuously exports instances from a start time (completedTimeTo must be null)
+# - Runtime status filters (valid values):
+# - "Completed": Exports only completed orchestrations
+# - "Failed": Exports only failed orchestrations
+# - "Terminated": Exports only terminated orchestrations
+# - Dates are in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ)
+# - You can use the REST Client extension in VS Code to execute these requests
+# - The @name directive allows referencing the response in subsequent requests
+# - Export jobs run asynchronously; use GET to check the status after creation
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportJobController.cs b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportJobController.cs
new file mode 100644
index 0000000..8599504
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportJobController.cs
@@ -0,0 +1,202 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.ExportHistory;
+using ExportHistoryWebApp.Models;
+
+namespace ExportHistoryWebApp.Controllers;
+
+///
+/// Controller for managing export history jobs through a REST API.
+/// Provides endpoints for creating, reading, listing, and deleting export jobs.
+///
+[ApiController]
+[Route("export-jobs")]
+public class ExportJobController : ControllerBase
+{
+ readonly ExportHistoryClient exportHistoryClient;
+ readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Client for managing export history jobs.
+ /// Logger for recording controller operations.
+ public ExportJobController(
+ ExportHistoryClient exportHistoryClient,
+ ILogger logger)
+ {
+ this.exportHistoryClient = exportHistoryClient ?? throw new ArgumentNullException(nameof(exportHistoryClient));
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// Creates a new export job based on the provided configuration.
+ ///
+ /// The export job creation request.
+ /// The created export job description.
+ [HttpPost]
+ public async Task> CreateExportJob([FromBody] CreateExportJobRequest request)
+ {
+ if (request == null)
+ {
+ return this.BadRequest("createExportJobRequest cannot be null");
+ }
+
+ try
+ {
+ ExportDestination? destination = null;
+ if (!string.IsNullOrEmpty(request.Container))
+ {
+ destination = new ExportDestination(request.Container)
+ {
+ Prefix = request.Prefix,
+ };
+ }
+
+ ExportJobCreationOptions creationOptions = new ExportJobCreationOptions(
+ mode: request.Mode,
+ completedTimeFrom: request.CompletedTimeFrom,
+ completedTimeTo: request.CompletedTimeTo,
+ destination: destination,
+ jobId: request.JobId,
+ format: request.Format,
+ runtimeStatus: request.RuntimeStatus,
+ maxInstancesPerBatch: request.MaxInstancesPerBatch);
+
+ ExportHistoryJobClient jobClient = await this.exportHistoryClient.CreateJobAsync(creationOptions);
+ ExportJobDescription description = await jobClient.DescribeAsync();
+
+ this.logger.LogInformation("Created new export job with ID: {JobId}", description.JobId);
+
+ return this.CreatedAtAction(nameof(GetExportJob), new { id = description.JobId }, description);
+ }
+ catch (ArgumentException ex)
+ {
+ this.logger.LogError(ex, "Validation failed while creating export job {JobId}", request.JobId);
+ return this.BadRequest(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Error creating export job {JobId}", request.JobId);
+ return this.StatusCode(500, "An error occurred while creating the export job");
+ }
+ }
+
+ ///
+ /// Retrieves a specific export job by its ID.
+ ///
+ /// The ID of the export job to retrieve.
+ /// The export job description if found.
+ [HttpGet("{id}")]
+ public async Task> GetExportJob(string id)
+ {
+ try
+ {
+ ExportJobDescription? job = await this.exportHistoryClient.GetJobAsync(id);
+ return this.Ok(job);
+ }
+ catch (ExportJobNotFoundException)
+ {
+ return this.NotFound();
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Error retrieving export job {JobId}", id);
+ return this.StatusCode(500, "An error occurred while retrieving the export job");
+ }
+ }
+
+ ///
+ /// Lists all export jobs, optionally filtered by query parameters.
+ ///
+ /// Optional filter by job status.
+ /// Optional filter by job ID prefix.
+ /// Optional filter for jobs created after this time.
+ /// Optional filter for jobs created before this time.
+ /// Optional page size for pagination.
+ /// Optional continuation token for pagination.
+ /// A collection of export job descriptions.
+ [HttpGet("list")]
+ public async Task>> ListExportJobs(
+ [FromQuery] ExportJobStatus? status = null,
+ [FromQuery] string? jobIdPrefix = null,
+ [FromQuery] DateTimeOffset? createdFrom = null,
+ [FromQuery] DateTimeOffset? createdTo = null,
+ [FromQuery] int? pageSize = null,
+ [FromQuery] string? continuationToken = null)
+ {
+ this.logger.LogInformation("GET list endpoint called with method: {Method}", this.HttpContext.Request.Method);
+ try
+ {
+ ExportJobQuery? query = null;
+ if (
+ status.HasValue ||
+ !string.IsNullOrEmpty(jobIdPrefix) ||
+ createdFrom.HasValue ||
+ createdTo.HasValue ||
+ pageSize.HasValue ||
+ !string.IsNullOrEmpty(continuationToken)
+ )
+ {
+ query = new ExportJobQuery
+ {
+ Status = status,
+ JobIdPrefix = jobIdPrefix,
+ CreatedFrom = createdFrom,
+ CreatedTo = createdTo,
+ PageSize = pageSize,
+ ContinuationToken = continuationToken,
+ };
+ }
+
+ AsyncPageable jobs = this.exportHistoryClient.ListJobsAsync(query);
+
+ // Collect all jobs from the async pageable
+ List jobList = new List();
+ await foreach (ExportJobDescription job in jobs)
+ {
+ jobList.Add(job);
+ }
+
+ return this.Ok(jobList);
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Error retrieving export jobs");
+ return this.StatusCode(500, "An error occurred while retrieving export jobs");
+ }
+ }
+
+ ///
+ /// Deletes an export job by its ID.
+ ///
+ /// The ID of the export job to delete.
+ /// No content if successful.
+ [HttpDelete("{id}")]
+ public async Task DeleteExportJob(string id)
+ {
+ this.logger.LogInformation("DELETE endpoint called for job ID: {JobId}", id);
+ try
+ {
+ ExportHistoryJobClient jobClient = this.exportHistoryClient.GetJobClient(id);
+ await jobClient.DeleteAsync();
+ this.logger.LogInformation("Successfully deleted export job {JobId}", id);
+ return this.NoContent();
+ }
+ catch (ExportJobNotFoundException)
+ {
+ this.logger.LogWarning("Export job {JobId} not found for deletion", id);
+ return this.NotFound();
+ }
+ catch (Exception ex)
+ {
+ this.logger.LogError(ex, "Error deleting export job {JobId}", id);
+ return this.StatusCode(500, "An error occurred while deleting the export job");
+ }
+ }
+}
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Models/CreateExportJobRequest.cs b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Models/CreateExportJobRequest.cs
new file mode 100644
index 0000000..1e09ddf
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Models/CreateExportJobRequest.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.ExportHistory;
+
+namespace ExportHistoryWebApp.Models;
+
+///
+/// Represents a request to create a new export job.
+///
+public class CreateExportJobRequest
+{
+ ///
+ /// Gets or sets the unique identifier for the export job. If not provided, a GUID will be generated.
+ ///
+ public string? JobId { get; set; }
+
+ ///
+ /// Gets or sets the export mode (Batch or Continuous).
+ ///
+ public ExportMode Mode { get; set; }
+
+ ///
+ /// Gets or sets the start time for the export based on completion time (inclusive). Required.
+ ///
+ public DateTimeOffset CompletedTimeFrom { get; set; }
+
+ ///
+ /// Gets or sets the end time for the export based on completion time (inclusive). Required for Batch mode, null for Continuous mode.
+ ///
+ public DateTimeOffset? CompletedTimeTo { get; set; }
+
+ ///
+ /// Gets or sets the blob container name where exported data will be stored. Optional if default storage is configured.
+ ///
+ public string? Container { get; set; }
+
+ ///
+ /// Gets or sets an optional prefix for blob paths.
+ ///
+ public string? Prefix { get; set; }
+
+ ///
+ /// Gets or sets the export format settings. Optional, defaults to jsonl-gzip.
+ ///
+ public ExportFormat? Format { get; set; }
+
+ ///
+ /// Gets or sets the orchestration runtime statuses to filter by. Optional.
+ /// Valid statuses are: Completed, Failed, Terminated.
+ ///
+ public List? RuntimeStatus { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of instances to fetch per batch. Optional, defaults to 100.
+ ///
+ public int? MaxInstancesPerBatch { get; set; }
+}
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Program.cs b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Program.cs
new file mode 100644
index 0000000..46ee02f
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Program.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Client.AzureManaged;
+using Microsoft.DurableTask.ExportHistory;
+using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Worker.AzureManaged;
+
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+string connectionString = builder.Configuration.GetValue("DURABLE_TASK_CONNECTION_STRING")
+ ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_CONNECTION_STRING'");
+
+string storageConnectionString = builder.Configuration.GetValue("EXPORT_HISTORY_STORAGE_CONNECTION_STRING")
+ ?? throw new InvalidOperationException("Missing required configuration 'EXPORT_HISTORY_STORAGE_CONNECTION_STRING'");
+
+string containerName = builder.Configuration.GetValue("EXPORT_HISTORY_CONTAINER_NAME")
+ ?? throw new InvalidOperationException("Missing required configuration 'EXPORT_HISTORY_CONTAINER_NAME'");
+
+builder.Services.AddSingleton(sp => sp.GetRequiredService().CreateLogger());
+builder.Services.AddLogging();
+
+// Add Durable Task worker with export history support
+builder.Services.AddDurableTaskWorker(builder =>
+{
+ builder.UseDurableTaskScheduler(connectionString);
+ builder.UseExportHistory();
+});
+
+// Register the client with export history support
+builder.Services.AddDurableTaskClient(clientBuilder =>
+{
+ clientBuilder.UseDurableTaskScheduler(connectionString);
+ clientBuilder.UseExportHistory(options =>
+ {
+ options.ConnectionString = storageConnectionString;
+ options.ContainerName = containerName;
+ options.Prefix = builder.Configuration.GetValue("EXPORT_HISTORY_PREFIX");
+ });
+});
+
+// Configure the HTTP request pipeline
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
+});
+
+// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
+WebApplication app = builder.Build();
+app.MapControllers();
+app.Run();
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Properties/launchSettings.json b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Properties/launchSettings.json
new file mode 100644
index 0000000..4375d42
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/Properties/launchSettings.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:47698",
+ "sslPort": 44372
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "applicationUrl": "http://localhost:5009",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DURABLE_TASK_CONNECTION_STRING": "",
+ "EXPORT_HISTORY_STORAGE_CONNECTION_STRING": "",
+ "EXPORT_HISTORY_CONTAINER_NAME": "export-history",
+ "EXPORT_HISTORY_PREFIX": ""
+ }
+ }
+ }
+}
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/README.md b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/README.md
new file mode 100644
index 0000000..72c847d
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/README.md
@@ -0,0 +1,115 @@
+# Export History Web App Sample
+
+This sample is a small ASP.NET Core web app that exposes a REST API for creating and managing Durable Task **export history jobs**.
+
+It uses:
+
+- **Durable Task Scheduler (Azure Managed)** for listing instance history
+- **Azure Blob Storage** as the export destination for exported orchestration history
+
+## Prerequisites
+
+- A Durable Task Scheduler hub connection string
+- An Azure Storage account connection string (or Azurite/Storage Emulator)
+
+## Configure
+
+The app reads configuration from standard .NET configuration sources (environment variables, `appsettings*.json`, command-line, etc.).
+
+Required settings:
+
+- `DURABLE_TASK_CONNECTION_STRING`
+ - Durable Task Scheduler connection string for your task hub.
+- `EXPORT_HISTORY_STORAGE_CONNECTION_STRING`
+ - Azure Storage connection string used for writing exported history.
+- `EXPORT_HISTORY_CONTAINER_NAME`
+ - Default blob container name for export output.
+
+Optional settings:
+
+- `EXPORT_HISTORY_PREFIX`
+ - Default blob “folder” prefix used when writing blobs.
+
+## Run
+
+From the repo root:
+
+```bash
+dotnet run --project samples/durable-task-sdks/dotnet/ExportHistoryWebApp/ExportHistoryWebApp.csproj
+```
+
+The default `launchSettings.json` profile listens on:
+
+- `http://localhost:5009`
+
+## Interact with the export API
+
+The controller is rooted at `export-jobs` and supports create/get/list/delete.
+
+### Create an export job
+
+`POST /export-jobs`
+
+Request body (see `Models/CreateExportJobRequest.cs`):
+
+- `jobId` (optional): If omitted, a GUID is generated.
+- `mode`: `Batch` or `Continuous`.
+- `completedTimeFrom`: Start of the export time window (inclusive).
+- `completedTimeTo`:
+ - Required for `Batch`
+ - Must be omitted/null for `Continuous`
+- `container` / `prefix` (optional): Overrides the default destination configured in app settings.
+- `runtimeStatus` (optional): Filters exported instances by terminal status.
+ - Allowed values: `Completed`, `Failed`, `Terminated`
+- `maxInstancesPerBatch` (optional): 1–1000 (defaults to 100).
+- `format` (optional): Defaults to JSONL + gzip.
+
+Notes:
+- For `Batch` mode, `completedTimeTo` must be greater than `completedTimeFrom` and cannot be in the future.
+
+### Get a job
+
+`GET /export-jobs/{id}`
+
+Returns an `ExportJobDescription` if the job exists.
+
+### List jobs
+
+`GET /export-jobs/list`
+
+Optional query parameters:
+
+- `status`: `Pending`, `Active`, `Failed`, `Completed`
+- `jobIdPrefix`
+- `createdFrom`, `createdTo`
+- `pageSize`, `continuationToken`
+
+### Delete a job
+
+`DELETE /export-jobs/{id}`
+
+## Where exported data goes
+
+Exported history is written to Azure Blob Storage:
+
+- Container: default from `EXPORT_HISTORY_CONTAINER_NAME` (or per-request `container` override)
+- Blob name: derived from a SHA-256 hash of `(completedTimestamp, instanceId)`
+- File extension:
+ - Default: `.jsonl.gz` (JSON Lines, gzip-compressed)
+ - Optional: `.json` (if configured via `format`)
+
+If a prefix is configured, the blob path becomes:
+
+- `{prefix}/{hash}.{ext}`
+
+## Using the included HTTP file
+
+This sample includes ready-made requests in `ExportHistoryWebApp.http`.
+
+In VS Code:
+
+1. Install the “REST Client” extension (if you don’t already have it).
+2. Open `ExportHistoryWebApp.http`.
+3. Click “Send Request” on any request block.
+
+Adjust the `@baseUrl` variable if you run the app on a different port.
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.Development.json b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.Development.json
new file mode 100644
index 0000000..21b22db
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
+
diff --git a/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.json b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.json
new file mode 100644
index 0000000..fb87850
--- /dev/null
+++ b/samples/durable-task-sdks/dotnet/ExportHistoryWebApp/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
+