-
Notifications
You must be signed in to change notification settings - Fork 55
Add ExceptionPropertiesSample demonstrating IExceptionPropertiesProvi… #560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
19a8d8d
Add ExceptionPropertiesSample demonstrating IExceptionPropertiesProvi…
YunchuWang 4fd6d46
fix
YunchuWang 262d9fc
Potential fix for pull request finding 'Useless assignment to local v…
YunchuWang 41ee817
Update samples/ExceptionPropertiesSample/README.md
YunchuWang 7320bad
Update samples/ExceptionPropertiesSample/Tasks.cs
YunchuWang 1e8962d
sln
YunchuWang 8c60d5a
Merge branch 'add-exception-properties-sample' of https://github.com/…
YunchuWang 113ed7d
remove appsettings
YunchuWang b5ef75e
Update samples/ExceptionPropertiesSample/README.md
YunchuWang 4d7606c
Update samples/ExceptionPropertiesSample/README.md
YunchuWang d8b0be7
Merge branch 'main' into add-exception-properties-sample
YunchuWang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
samples/ExceptionPropertiesSample/CustomExceptionPropertiesProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Microsoft.DurableTask.Worker; | ||
|
|
||
| namespace ExceptionPropertiesSample; | ||
|
|
||
| /// <summary> | ||
| /// Custom exception properties provider that extracts additional properties from exceptions | ||
| /// to include in TaskFailureDetails for better diagnostics and error handling. | ||
| /// </summary> | ||
| public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider | ||
| { | ||
| /// <summary> | ||
| /// Extracts custom properties from exceptions to enrich failure details. | ||
| /// </summary> | ||
| /// <param name="exception">The exception to extract properties from.</param> | ||
| /// <returns> | ||
| /// A dictionary of custom properties to include in the FailureDetails, | ||
| /// or null if no properties should be added for this exception type. | ||
| /// </returns> | ||
| public IDictionary<string, object?>? GetExceptionProperties(Exception exception) | ||
| { | ||
| return exception switch | ||
| { | ||
| BusinessValidationException businessEx => new Dictionary<string, object?> | ||
| { | ||
| ["ErrorCode"] = businessEx.ErrorCode, | ||
| ["StatusCode"] = businessEx.StatusCode, | ||
| ["Metadata"] = businessEx.Metadata, | ||
| }, | ||
| ArgumentOutOfRangeException argEx => new Dictionary<string, object?> | ||
| { | ||
| ["ParameterName"] = argEx.ParamName ?? string.Empty, | ||
| ["ActualValue"] = argEx.ActualValue?.ToString() ?? string.Empty, | ||
| }, | ||
| ArgumentNullException argNullEx => new Dictionary<string, object?> | ||
| { | ||
| ["ParameterName"] = argNullEx.ParamName ?? string.Empty, | ||
| }, | ||
| _ => null // No custom properties for other exception types | ||
| }; | ||
| } | ||
| } | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace ExceptionPropertiesSample; | ||
|
|
||
| /// <summary> | ||
| /// Custom business exception that includes additional properties for better error diagnostics. | ||
| /// </summary> | ||
| public class BusinessValidationException : Exception | ||
| { | ||
| public BusinessValidationException( | ||
| string message, | ||
| string? errorCode = null, | ||
| int? statusCode = null, | ||
| Dictionary<string, object?>? metadata = null) | ||
| : base(message) | ||
| { | ||
| this.ErrorCode = errorCode; | ||
| this.StatusCode = statusCode; | ||
| this.Metadata = metadata ?? new Dictionary<string, object?>(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets the error code associated with this validation failure. | ||
| /// </summary> | ||
| public string? ErrorCode { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the HTTP status code that should be returned for this error. | ||
| /// </summary> | ||
| public int? StatusCode { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets additional metadata about the validation failure. | ||
| /// </summary> | ||
| public Dictionary<string, object?> Metadata { get; } | ||
| } | ||
|
|
28 changes: 28 additions & 0 deletions
28
samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Hosting" /> | ||
|
|
||
| <!-- Real projects would use package references --> | ||
| <!-- | ||
| <PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" /> | ||
| <PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" /> | ||
| --> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <!-- Using p2p references so we can show latest changes in samples. --> | ||
| <ProjectReference Include="$(SrcRoot)Client/Grpc/Client.Grpc.csproj" /> | ||
| <ProjectReference Include="$(SrcRoot)Client/AzureManaged/Client.AzureManaged.csproj" /> | ||
| <ProjectReference Include="$(SrcRoot)Worker/Grpc/Worker.Grpc.csproj" /> | ||
| <ProjectReference Include="$(SrcRoot)Worker/AzureManaged/Worker.AzureManaged.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| // This sample demonstrates how to use IExceptionPropertiesProvider to enrich | ||
| // TaskFailureDetails with custom exception properties for better diagnostics. | ||
|
|
||
| using ExceptionPropertiesSample; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.DurableTask.Client; | ||
| using Microsoft.DurableTask.Client.AzureManaged; | ||
| using Microsoft.DurableTask.Worker; | ||
| using Microsoft.DurableTask.Worker.AzureManaged; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
| HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); | ||
|
|
||
| string? schedulerConnectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING"); | ||
| bool useScheduler = !string.IsNullOrWhiteSpace(schedulerConnectionString); | ||
|
|
||
| // Register the durable task client | ||
| if (useScheduler) | ||
| { | ||
| builder.Services.AddDurableTaskClient(clientBuilder => clientBuilder.UseDurableTaskScheduler(schedulerConnectionString!)); | ||
| } | ||
| else | ||
| { | ||
| builder.Services.AddDurableTaskClient().UseGrpc(); | ||
| } | ||
|
|
||
| // Register the durable task worker with custom exception properties provider | ||
| if (useScheduler) | ||
| { | ||
| builder.Services.AddDurableTaskWorker(workerBuilder => | ||
| { | ||
| workerBuilder.AddTasks(tasks => | ||
| { | ||
| tasks.AddOrchestrator<ValidationOrchestration>(); | ||
| tasks.AddActivity<ValidateInputActivity>(); | ||
| }); | ||
|
|
||
| workerBuilder.UseDurableTaskScheduler(schedulerConnectionString!); | ||
| }); | ||
| } | ||
| else | ||
| { | ||
| builder.Services.AddDurableTaskWorker() | ||
| .AddTasks(tasks => | ||
| { | ||
| tasks.AddOrchestrator<ValidationOrchestration>(); | ||
| tasks.AddActivity<ValidateInputActivity>(); | ||
| }) | ||
| .UseGrpc(); | ||
| } | ||
|
|
||
| // Register the custom exception properties provider | ||
| // This will automatically extract custom properties from exceptions and include them in TaskFailureDetails | ||
| builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>(); | ||
|
|
||
| IHost host = builder.Build(); | ||
|
|
||
| // Start the worker | ||
| await host.StartAsync(); | ||
|
|
||
| // Get the client to start orchestrations | ||
| DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>(); | ||
|
|
||
| Console.WriteLine("Exception Properties Sample"); | ||
| Console.WriteLine("==========================="); | ||
| Console.WriteLine(); | ||
|
|
||
| Console.WriteLine(useScheduler | ||
| ? "Configured to use Durable Task Scheduler (DTS)." | ||
| : "Configured to use local gRPC. (Set DURABLE_TASK_SCHEDULER_CONNECTION_STRING to use DTS.)"); | ||
| Console.WriteLine(); | ||
|
|
||
| // Test case 1: Valid input (should succeed) | ||
| Console.WriteLine("Test 1: Valid input"); | ||
| string instanceId1 = await client.ScheduleNewOrchestrationInstanceAsync( | ||
| "ValidationOrchestration", | ||
| input: "Hello World"); | ||
|
|
||
| OrchestrationMetadata result1 = await client.WaitForInstanceCompletionAsync( | ||
| instanceId1, | ||
| getInputsAndOutputs: true); | ||
|
|
||
| if (result1.RuntimeStatus == OrchestrationRuntimeStatus.Completed) | ||
| { | ||
| Console.WriteLine($"✓ Orchestration completed successfully"); | ||
| Console.WriteLine($" Output: {result1.ReadOutputAs<string>()}"); | ||
| } | ||
| Console.WriteLine(); | ||
|
|
||
| // Test case 2: Empty input (should fail with custom properties) | ||
| Console.WriteLine("Test 2: Empty input (should fail)"); | ||
| string instanceId2 = await client.ScheduleNewOrchestrationInstanceAsync( | ||
| "ValidationOrchestration", | ||
| input: string.Empty); | ||
|
|
||
| OrchestrationMetadata result2 = await client.WaitForInstanceCompletionAsync( | ||
| instanceId2, | ||
| getInputsAndOutputs: true); | ||
|
|
||
| if (result2.RuntimeStatus == OrchestrationRuntimeStatus.Failed && result2.FailureDetails != null) | ||
| { | ||
| Console.WriteLine($"✗ Orchestration failed as expected"); | ||
| Console.WriteLine($" Error Type: {result2.FailureDetails.ErrorType}"); | ||
| Console.WriteLine($" Error Message: {result2.FailureDetails.ErrorMessage}"); | ||
|
|
||
| // Display custom properties that were extracted by IExceptionPropertiesProvider | ||
| if (result2.FailureDetails.Properties != null && result2.FailureDetails.Properties.Count > 0) | ||
| { | ||
| Console.WriteLine($" Custom Properties:"); | ||
| foreach (var property in result2.FailureDetails.Properties) | ||
| { | ||
| Console.WriteLine($" - {property.Key}: {property.Value}"); | ||
| } | ||
| } | ||
| } | ||
| Console.WriteLine(); | ||
|
|
||
| // Test case 3: Short input (should fail with different custom properties) | ||
| Console.WriteLine("Test 3: Short input (should fail)"); | ||
| string instanceId3 = await client.ScheduleNewOrchestrationInstanceAsync( | ||
| "ValidationOrchestration", | ||
| input: "Hi"); | ||
|
|
||
| OrchestrationMetadata result3 = await client.WaitForInstanceCompletionAsync( | ||
| instanceId3, | ||
| getInputsAndOutputs: true); | ||
|
|
||
| if (result3.RuntimeStatus == OrchestrationRuntimeStatus.Failed && result3.FailureDetails != null) | ||
| { | ||
| Console.WriteLine($"✗ Orchestration failed as expected"); | ||
| Console.WriteLine($" Error Type: {result3.FailureDetails.ErrorType}"); | ||
| Console.WriteLine($" Error Message: {result3.FailureDetails.ErrorMessage}"); | ||
|
|
||
| // Display custom properties | ||
| if (result3.FailureDetails.Properties != null && result3.FailureDetails.Properties.Count > 0) | ||
| { | ||
| Console.WriteLine($" Custom Properties:"); | ||
| foreach (var property in result3.FailureDetails.Properties) | ||
YunchuWang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Console.WriteLine($" - {property.Key}: {property.Value}"); | ||
| } | ||
| } | ||
| } | ||
| Console.WriteLine(); | ||
|
|
||
| Console.WriteLine("Sample completed. Press any key to exit..."); | ||
| Console.ReadKey(); | ||
|
|
||
| await host.StopAsync(); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Exception Properties Sample | ||
|
|
||
| This sample demonstrates how to use `IExceptionPropertiesProvider` to enrich `TaskFailureDetails` with custom exception properties for better diagnostics and error handling. | ||
|
|
||
| ## Overview | ||
|
|
||
| When orchestrations or activities throw exceptions, the Durable Task framework captures failure details. By implementing `IExceptionPropertiesProvider`, you can extract custom properties from exceptions and include them in the `TaskFailureDetails`, making it easier to diagnose issues and handle errors programmatically. | ||
|
|
||
| ## Key Concepts | ||
|
|
||
| 1. **Custom Exception with Properties**: Create exceptions that carry additional context (error codes, metadata, etc.) | ||
| 2. **IExceptionPropertiesProvider**: Implement this interface to extract custom properties from exceptions | ||
| 3. **Automatic Property Extraction**: The framework automatically uses your provider when converting exceptions to `TaskFailureDetails` | ||
| 4. **Retrieving Failure Details**: Use the durable client to retrieve orchestration status and access the enriched failure details | ||
|
|
||
| ## What This Sample Does | ||
|
|
||
| 1. Defines a `BusinessValidationException` with custom properties (ErrorCode, StatusCode, Metadata) | ||
| 2. Implements `CustomExceptionPropertiesProvider` that extracts these properties from exceptions | ||
| 3. Creates a validation orchestration and activity that throws the custom exception | ||
| 4. Demonstrates how to retrieve and display failure details with custom properties using the durable client | ||
|
|
||
| ## Running the Sample | ||
|
|
||
| This sample can run against either: | ||
|
|
||
| 1. **Durable Task Scheduler (DTS)** (recommended): set the `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` environment variable. | ||
| 2. **Local gRPC endpoint**: if the env var is not set, the sample uses the default local gRPC configuration. | ||
|
|
||
| ### DTS | ||
|
|
||
| Set `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` and run the sample. | ||
|
|
||
| ```cmd | ||
| set DURABLE_TASK_SCHEDULER_CONNECTION_STRING=Endpoint=https://...;TaskHub=...;Authentication=...; | ||
| dotnet run --project samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj | ||
| ``` | ||
|
|
||
| ```bash | ||
YunchuWang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| export DURABLE_TASK_SCHEDULER_CONNECTION_STRING="Endpoint=https://...;TaskHub=...;Authentication=...;" | ||
| dotnet run --project samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj | ||
| ``` | ||
|
|
||
| ## Expected Output | ||
|
|
||
| The sample runs three test cases: | ||
| 1. **Valid input**: Orchestration completes successfully | ||
| 2. **Empty input**: Orchestration fails with custom properties (ErrorCode, StatusCode, Metadata) | ||
| 3. **Short input**: Orchestration fails with different custom properties | ||
|
|
||
| For failed orchestrations, you'll see the custom properties extracted by the `IExceptionPropertiesProvider` displayed in the console. | ||
|
|
||
| ## Code Structure | ||
|
|
||
| - `CustomExceptions.cs`: Defines the `BusinessValidationException` with custom properties | ||
| - `CustomExceptionPropertiesProvider.cs`: Implements `IExceptionPropertiesProvider` to extract properties | ||
| - `Tasks.cs`: Contains the orchestration and activity that throw custom exceptions | ||
| - `Program.cs`: Sets up the worker, registers the provider, and demonstrates retrieving failure details | ||
|
|
||
| ## Key Code Snippet | ||
|
|
||
| ```csharp | ||
| // Register the custom exception properties provider | ||
| builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>(); | ||
|
|
||
| // Retrieve failure details with custom properties | ||
| OrchestrationMetadata result = await client.WaitForInstanceCompletionAsync( | ||
| instanceId, | ||
| getInputsAndOutputs: true); // Important: must be true to get failure details | ||
|
|
||
| if (result.FailureDetails?.Properties != null) | ||
| { | ||
| foreach (var property in result.FailureDetails.Properties) | ||
| { | ||
| Console.WriteLine($"{property.Key}: {property.Value}"); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - The `getInputsAndOutputs` parameter must be `true` when calling `GetInstanceAsync` or `WaitForInstanceCompletionAsync` to retrieve failure details | ||
| - Custom properties are only included if the orchestration is in a `Failed` state | ||
| - The `IExceptionPropertiesProvider` is called automatically by the framework when exceptions are caught | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.