.NET: [Feature Branch] Adding support for events & shared state in durable workflows (#4020)

* Adding support for events & shared state in durable workflows.

* PR feedback fixes

* PR feedback fixes.

* Add YieldOutputAsync calls to 05_WorkflowEvents sample executors

The integration test asserts that WorkflowOutputEvent is found in the
stream, but the sample executors only used AddEventAsync for custom
events and never called YieldOutputAsync. Since WorkflowOutputEvent is
only emitted via explicit YieldOutputAsync calls, the assertion would
fail. Added YieldOutputAsync to each executor to match the test
expectation and demonstrate the API in the sample.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix deserialization to use shared serializer options.

* PR feedback updates.

* Sample cleanup

* PR feedback fixes

* Addressing PR review feedback for DurableStreamingWorkflowRun

   - Use -1 instead of 0 for taskId in TaskFailedException when task ID is not relevant.
   - Add [NotNullWhen(true)] to TryParseWorkflowResult out parameter following .NET TryXXX conventions.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Shyju Krishnankutty
2026-02-20 16:49:47 -08:00
committed by GitHub
co-authored by Copilot
parent b62b1f2191
commit 3256baa8b6
37 changed files with 3498 additions and 215 deletions
@@ -312,11 +312,8 @@ public static class ServiceCollectionExtensions
Dictionary<string, ExecutorBinding> executorBindings = workflow.ReflectExecutors();
List<ActivityRegistrationInfo> activities = [];
// Filter out AI agents and subworkflows - they are not registered as activities.
// AI agents use Durable Entities for stateful execution, and subworkflows are
// registered as separate orchestrations via BuildWorkflowRegistrationRecursive.
foreach (KeyValuePair<string, ExecutorBinding> entry in executorBindings
.Where(e => e.Value is not AIAgentBinding and not SubworkflowBinding))
.Where(e => IsActivityBinding(e.Value)))
{
string executorName = WorkflowNamingHelper.GetExecutorName(entry.Key);
string activityName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorName);
@@ -330,6 +327,15 @@ public static class ServiceCollectionExtensions
return new WorkflowRegistrationInfo(orchestrationName, activities);
}
/// <summary>
/// Returns <see langword="true"/> for bindings that should be registered as Durable Task activities.
/// <see cref="AIAgentBinding"/> (Durable Entities) and <see cref="SubworkflowBinding"/> (sub-orchestrations)
/// use specialized dispatch and are excluded.
/// </summary>
private static bool IsActivityBinding(ExecutorBinding binding)
=> binding is not AIAgentBinding
and not SubworkflowBinding;
private static async Task<string> RunWorkflowOrchestrationAsync(
TaskOrchestrationContext context,
DurableWorkflowInput<object> workflowInput,