Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>CustomExceptionPropertyProvider</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.52.0" />
<!-- Custom exception property provider requires Extensions.DurableTask >= 1.9.0 (using latest). -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.18.0" />
<!-- Azure-managed (Durable Task Scheduler) SDK requires >= 1.2.0 (using latest). -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.9.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" OutputItemType="Analyzer" />
<!-- IExceptionPropertiesProvider requires Microsoft.DurableTask.Worker >= 1.16.1 (using latest). -->
<PackageReference Include="Microsoft.DurableTask.Worker" Version="1.24.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;

namespace CustomExceptionPropertyProvider;

// =============================================================================
// Orchestration + activity
// =============================================================================

/// <summary>
/// Demonstrates custom FailureDetails properties. An activity throws a
/// <see cref="BusinessValidationException"/>; the registered
/// <see cref="IExceptionPropertiesProvider"/> attaches its structured fields to
/// the failure. The orchestration catches the propagated
/// <see cref="TaskFailedException"/> and returns its <c>FailureDetails</c>,
/// including the custom <c>Properties</c>.
/// </summary>
public static class CustomExceptionOrchestration
{
[Function(nameof(OrchestrationWithCustomException))]
public static async Task<TaskFailureDetails?> OrchestrationWithCustomException(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
try
{
#pragma warning disable DURABLE2001 // Activity has no input
await context.CallActivityAsync(nameof(BusinessActivity));
#pragma warning restore DURABLE2001
}
catch (TaskFailedException ex)
{
// FailureDetails.Properties contains the values supplied by the provider.
return ex.FailureDetails;
}

// Should never reach here — the activity always throws.
return null;
}

[Function(nameof(BusinessActivity))]
public static void BusinessActivity([ActivityTrigger] TaskActivityContext context)
{
throw new BusinessValidationException(
message: "Business logic validation failed",
stringProperty: "validation-error-123",
intProperty: 100,
longProperty: 999999999L,
dateTimeProperty: new DateTime(2025, 10, 15, 14, 30, 0, DateTimeKind.Utc),
dictionaryProperty: new Dictionary<string, object?>
{
["error_code"] = "VALIDATION_FAILED",
["retry_count"] = 3,
["is_critical"] = true,
},
listProperty: new List<object?> { "error1", "error2", 500, null },
nullProperty: null);
}

[Function(nameof(OrchestrationWithCustomException) + "_Start")]
public static async Task<HttpResponseData> Start(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "orchestrators/customException")] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(OrchestrationWithCustomException));
return client.CreateCheckStatusResponse(req, instanceId);
}
}

// =============================================================================
// Custom exception + provider
// =============================================================================

/// <summary>
/// Business exception carrying structured properties that should be surfaced on
/// <c>FailureDetails.Properties</c>.
/// </summary>
[Serializable]
public class BusinessValidationException : Exception
{
public BusinessValidationException(
string message,
string stringProperty,
int intProperty,
long longProperty,
DateTime dateTimeProperty,
IDictionary<string, object?> dictionaryProperty,
IList<object?> listProperty,
object? nullProperty)
: base(message)
{
this.StringProperty = stringProperty;
this.IntProperty = intProperty;
this.LongProperty = longProperty;
this.DateTimeProperty = dateTimeProperty;
this.DictionaryProperty = dictionaryProperty;
this.ListProperty = listProperty;
this.NullProperty = nullProperty;
}

public BusinessValidationException(string message) : base(message) { }

public string? StringProperty { get; }
public int? IntProperty { get; }
public long? LongProperty { get; }
public DateTime? DateTimeProperty { get; }
public IDictionary<string, object?>? DictionaryProperty { get; }
public IList<object?>? ListProperty { get; }
public object? NullProperty { get; }
}

/// <summary>
/// Maps thrown exceptions to the custom properties attached to FailureDetails.
/// Returning <c>null</c> opts out of adding properties for a given exception.
/// </summary>
public class BusinessExceptionPropertiesProvider : IExceptionPropertiesProvider
{
public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
{
return exception switch
{
BusinessValidationException e => new Dictionary<string, object?>
{
["StringProperty"] = e.StringProperty,
["IntProperty"] = e.IntProperty,
["LongProperty"] = e.LongProperty,
["DateTimeProperty"] = e.DateTimeProperty,
["DictionaryProperty"] = e.DictionaryProperty,
["ListProperty"] = e.ListProperty,
["NullProperty"] = e.NullProperty,
},
_ => null,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using CustomExceptionPropertyProvider;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

FunctionsApplicationBuilder builder = FunctionsApplication.CreateBuilder(args);

// Register the custom exception properties provider. When an activity throws,
// the Durable worker consults this provider to extract structured properties
// from the exception and attaches them to FailureDetails.Properties, which is
// then surfaced to the orchestration that observes the failure.
builder.Services.AddSingleton<IExceptionPropertiesProvider, BusinessExceptionPropertiesProvider>();

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Custom Exception Property Provider with Durable Functions (.NET)

.NET | Durable Functions

## Description

Demonstrates attaching **custom properties to `FailureDetails`** when an activity throws, using the Durable Task Scheduler (DTS) backend.

When an activity throws a `BusinessValidationException`, the registered `IExceptionPropertiesProvider` extracts structured fields (string, int, long, date-time, dictionary, list, null) from the exception. The Durable worker propagates those values onto `FailureDetails.Properties`, which the orchestration observes when it catches the failure — making rich, typed error context available to the orchestrator instead of just a message and stack trace.

## Prerequisites

1. [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
2. [Docker](https://www.docker.com/products/docker-desktop/) (for running the emulator)
3. [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)

> **Note:** The custom exception property provider requires these minimum versions (the sample references the latest available):
> - `Microsoft.Azure.Functions.Worker.Extensions.DurableTask` >= 1.9.0
> - `Microsoft.DurableTask.Worker` >= 1.16.1 (provides `IExceptionPropertiesProvider`)
> - `Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged` >= 1.2.0 (for the Durable Task Scheduler backend)

## Quick Run

1. Start the Durable Task Scheduler emulator:
```bash
docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
```

2. Start the Function app:
```bash
cd samples/durable-functions/dotnet/CustomExceptionPropertyProvider
func start
```

3. Start the orchestration:
```bash
curl -X POST http://localhost:7071/api/orchestrators/customException
```

4. Follow the `statusQueryGetUri` from the response to see the completed output.

5. View in the dashboard: http://localhost:8082

## Expected Output

The orchestration returns the caught `FailureDetails`, whose `Properties` carry the custom values supplied by the provider:

```json
{
"errorType": "CustomExceptionPropertyProvider.BusinessValidationException",
"errorMessage": "Business logic validation failed",
"properties": {
"StringProperty": "validation-error-123",
"IntProperty": 100,
"LongProperty": 999999999,
"DateTimeProperty": "2025-10-15T14:30:00Z",
"DictionaryProperty": {
"error_code": "VALIDATION_FAILED",
"retry_count": 3,
"is_critical": true
},
"ListProperty": ["error1", "error2", 500, null],
"NullProperty": null
}
}
```

## How It Works

- `BusinessExceptionPropertiesProvider` implements `Microsoft.DurableTask.Worker.IExceptionPropertiesProvider` and is registered as a singleton in `Program.cs`.
- `BusinessActivity` throws a `BusinessValidationException` carrying the structured fields.
- `OrchestrationWithCustomException` calls the activity, catches the propagated `TaskFailedException`, and returns `ex.FailureDetails` — including the custom `Properties`.

## Learn More

- [Durable Functions error handling](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-error-handling)
- [Durable Task Scheduler Documentation](https://aka.ms/dts-documentation)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "2.0",
"logging": {
"logLevel": {
"default": "Information",
"DurableTask.Core": "Warning",
"Microsoft.DurableTask": "Information",
"Host.Triggers.DurableTask": "Information"
}
},
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "azureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# =============================================================================
# Custom Exception Property Provider — Durable Functions (.NET isolated) Demo
# =============================================================================
#
# Prerequisites:
# 1. DTS emulator: docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
# 2. App running: func start --port 7071
#
# The orchestration calls an activity that throws BusinessValidationException.
# A registered IExceptionPropertiesProvider attaches structured properties to
# FailureDetails.Properties, which the orchestration returns as its output.
# =============================================================================

@baseUrl = http://localhost:7071/api


### Start the orchestration
# @name customException
POST {{baseUrl}}/orchestrators/customException

### Check status — the output holds FailureDetails, including custom Properties
GET {{customException.response.body.statusQueryGetUri}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Custom Exception Property Provider — Durable Functions JavaScript

JavaScript | Durable Functions

## Description

Demonstrates attaching **custom properties to `FailureDetails`** when an activity throws, using the Node.js v4 programming model and the Durable Task Scheduler (DTS) backend.

An activity throws a `BusinessValidationException` carrying structured fields (string, int, long, date-time, dictionary, list, null). A provider registered via `df.app.setExceptionPropertiesProvider(...)` extracts those fields, and the Durable worker propagates them onto `FailureDetails.Properties`. The orchestration catches the propagated `TaskFailedError` and returns its `failureDetails`, so the rich error context is available to the orchestrator instead of just a message and stack trace.

## Prerequisites

1. [Node.js 18+](https://nodejs.org/) (use Node 22 LTS; the Functions host does not yet support Node 24)
2. [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local)
3. [Docker](https://www.docker.com/products/docker-desktop/) (for the emulator)

> **Note:** The custom exception property provider requires:
> - The `durable-functions` npm package must include `df.app.setExceptionPropertiesProvider`.
> - An extension bundle that ships the feature. Use the **main** bundle `Microsoft.Azure.Functions.ExtensionBundle` >= **4.37.1** (configured in `host.json`), or the **preview** bundle `Microsoft.Azure.Functions.ExtensionBundle.Preview` >= **4.44.0**. Older bundles will **not** surface `Properties`.
> - The Durable Task Scheduler (azure-managed) SDK >= **1.2.0**.

## Quick Run

1. Start the Durable Task Scheduler emulator:
```bash
docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
```

2. Install dependencies and run:
```bash
cd samples/durable-functions/javascript/CustomExceptionPropertyProvider
npm install
func start
```

3. Start the orchestration:
```bash
curl -X POST http://localhost:7071/api/StartCustomException
```

4. Follow the `statusQueryGetUri` from the response to see the completed output.

5. View in the dashboard: http://localhost:8082

## Expected Output

The orchestration returns the caught `failureDetails`, whose `properties` carry the custom values supplied by the provider:

```json
{
"errorType": "BusinessValidationException",
"errorMessage": "Business logic validation failed",
"properties": {
"StringProperty": "validation-error-123",
"IntProperty": 100,
"LongProperty": 999999999,
"DateTimeProperty": "2025-10-15T14:30:00.000Z",
"DictionaryProperty": {
"error_code": "VALIDATION_FAILED",
"retry_count": 3,
"is_critical": true
},
"ListProperty": ["error1", "error2", 500, null],
"NullProperty": null
}
}
```

## How It Works

- `df.app.setExceptionPropertiesProvider({ getExceptionProperties(error) { ... } })` registers a global provider once at app startup.
- `businessActivity` throws a `BusinessValidationException` carrying the structured fields.
- `orchestrationWithCustomException` calls the activity, catches the propagated `TaskFailedError`, and returns `e.failureDetails` — including the custom `properties`.

## Learn More

- [Durable Functions JavaScript API Reference](https://learn.microsoft.com/javascript/api/durable-functions/)
- [Durable Functions error handling](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-error-handling)
- [Durable Task Scheduler Documentation](https://aka.ms/dts-documentation)
Loading
Loading