[.NET] Add a Functions-native API for invoking Durable Workflows - #48
Conversation
Starting a durable workflow from inside an Azure Function required building an HttpClient and POSTing to the workflow's generated HTTP route, which coupled function code to that route. IWorkflowClient already existed, but it was only reachable from the console and worker host, and every overload required the Workflow object, which function classes generally don't have a reference to. Add name-based RunAsync and StreamAsync overloads that resolve against the registered workflows, and expose the client to function code through DurableTaskClient.AsWorkflowClient(FunctionContext), mirroring the existing AsDurableAgentProxy pattern. Starting an unregistered name fails fast with the new WorkflowNotRegisteredException. Fixes #24 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a Functions-native way to invoke Durable Workflows without constructing an HttpClient and calling the generated workflow HTTP endpoints. It does so by extending IWorkflowClient with name-based overloads (with fast-fail when a workflow name isn’t registered) and by exposing an AsWorkflowClient(FunctionContext) extension on DurableTaskClient for Azure Functions code that receives the client via [DurableClient].
Changes:
- Add name-based
RunAsync/StreamAsyncoverloads toIWorkflowClient, implemented byDurableWorkflowClientusing the registered workflow registry. - Add
DurableTaskClientExtensions.AsWorkflowClient(FunctionContext)to bridge the[DurableClient]binding into anIWorkflowClientin Azure Functions. - Demonstrate and validate the feature via the
01_SequentialWorkflowsample plus unit and integration tests.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableTaskClientWorkflowExtensionsTests.cs | Adds unit coverage for DurableTaskClientExtensions.AsWorkflowClient and expected error cases. |
| dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/WorkflowSamplesValidation.cs | Extends the existing live-host integration validation to exercise the new “invoke via function code” path. |
| dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Workflows/DurableWorkflowClientTests.cs | Adds unit coverage for name-based IWorkflowClient overload behavior (success, runId, casing, and error cases). |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableTaskClientExtensions.cs | Introduces AsWorkflowClient(context) extension to construct a workflow client from a [DurableClient] binding and DI options. |
| dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/CHANGELOG.md | Documents the new Azure Functions extension API. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/IWorkflowClient.cs | Adds public name-based overloads for running/streaming registered workflows. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/Workflows/DurableWorkflowClient.cs | Implements name-based overloads by resolving workflows from DurableOptions and delegating to existing workflow-object overloads. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/WorkflowNotRegisteredException.cs | Adds a dedicated exception to fail fast on typo’d/unregistered workflow names. |
| dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md | Records the breaking interface change and the new overload surface. |
| dotnet/samples/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/README.md | Documents invoking workflows from function code via AsWorkflowClient (no HttpClient). |
| dotnet/samples/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/Program.cs | Adds explanatory comments pointing to the new sample function usage. |
| dotnet/samples/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/OrderFunctions.cs | Adds sample HTTP-trigger functions that start a registered workflow via AsWorkflowClient. |
| dotnet/samples/DurableWorkflows/AzureFunctions/01_SequentialWorkflow/demo.http | Adds demo requests for the new sample endpoints. |
Comments suppressed due to low confidence (2)
dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableTaskClientWorkflowExtensionsTests.cs:53
CreateServiceProviderWithWorkflows()returns a disposableServiceProvider, but it’s created inline here and never disposed. Prefer creating it in ausingdeclaration so the container is cleaned up after the test.
FunctionContext context = CreateContext(CreateServiceProviderWithWorkflows());
dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableTaskClientWorkflowExtensionsTests.cs:105
- This test creates a disposable
ServiceProviderinline viaCreateServiceProviderWithWorkflows()and never disposes it. Use ausingvariable to ensure the container is disposed at the end of the test.
FunctionContext context = CreateContext(CreateServiceProviderWithWorkflows());
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Azure Functions worker serializes orchestration output with the Durable Task default JsonDataConverter, which applies no naming policy and writes PascalCase. DurableWorkflowJsonContext read it back with a camelCase, case-sensitive source-generated context, so every property silently bound to null and the workflow result was lost. The built-in HTTP endpoint was unaffected because it round-trips through the same converter via ReadOutputAs<T>(). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
.NET-specific fix for #24.
Summary
Azure Functions can now invoke a registered durable workflow using a strongly typed
IWorkflowClient, i.e., without anHttpClientor workflow URL:As shown in the sample above,
AsWorkflowClientconverts aDurableTaskClientbinding parameter to theIWorkflowClientinterface, which definesRunAsyncandStreamAsyncoverloads for invoking registered workflows by name.This PR also fixes Functions-host workflow-result deserialization: host output uses PascalCase, which the source-generated reader previously treated as case-sensitive and could deserialize as null.
Breaking Changes
IWorkflowClientinterface, which is a breaking change for external implementers.nullfirst argument toRunAsyncis now ambiguous; callers will need to cast it toWorkflowwhen needed.