From 5e062c8a7f2a319243eeabbc397e37d1515e414d Mon Sep 17 00:00:00 2001 From: flippedcoder Date: Thu, 21 May 2026 08:42:42 -0500 Subject: [PATCH 1/4] chore(interceptors): increased heading level on other pages --- docs/develop/python/workers/interceptors.mdx | 2 +- docs/develop/typescript/workers/interceptors.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/develop/python/workers/interceptors.mdx b/docs/develop/python/workers/interceptors.mdx index 8de605ea1e..e5616678d8 100644 --- a/docs/develop/python/workers/interceptors.mdx +++ b/docs/develop/python/workers/interceptors.mdx @@ -5,7 +5,7 @@ sidebar_label: Interceptors description: Implement Interceptors in the Temporal Python SDK to manage inbound and outbound SDK calls, enhance tracing, and add authorization to your Workflows and Activities. -toc_max_heading_level: 2 +toc_max_heading_level: 3 keywords: - interceptors tags: diff --git a/docs/develop/typescript/workers/interceptors.mdx b/docs/develop/typescript/workers/interceptors.mdx index c9383d3c2a..3c1d8e8351 100644 --- a/docs/develop/typescript/workers/interceptors.mdx +++ b/docs/develop/typescript/workers/interceptors.mdx @@ -2,7 +2,7 @@ id: interceptors title: Manage Interceptors - TypeScript SDK sidebar_label: Interceptors -toc_max_heading_level: 2 +toc_max_heading_level: 3 keywords: - interceptors tags: From ad80ca6655f29e30a746ed1b70c31d09f70df7a3 Mon Sep 17 00:00:00 2001 From: flippedcoder Date: Thu, 21 May 2026 08:43:00 -0500 Subject: [PATCH 2/4] feat(interceptors): added page for dotnet --- docs/develop/dotnet/index.mdx | 1 + docs/develop/dotnet/workers/index.mdx | 1 + docs/develop/dotnet/workers/interceptors.mdx | 206 +++++++++++++++++++ sidebars.js | 1 + 4 files changed, 209 insertions(+) create mode 100644 docs/develop/dotnet/workers/interceptors.mdx diff --git a/docs/develop/dotnet/index.mdx b/docs/develop/dotnet/index.mdx index 424d86db23..2b6bd32954 100644 --- a/docs/develop/dotnet/index.mdx +++ b/docs/develop/dotnet/index.mdx @@ -59,6 +59,7 @@ From there, you can dive deeper into any of the Temporal primitives to start bui ## [Workers](/develop/dotnet/workers) - [Worker processes](/develop/dotnet/workers/run-worker-process) +- [Interceptors](/develop/dotnet/workers/interceptors) ## [Temporal Client](/develop/dotnet/client) diff --git a/docs/develop/dotnet/workers/index.mdx b/docs/develop/dotnet/workers/index.mdx index c0c4f40177..b6fee1716f 100644 --- a/docs/develop/dotnet/workers/index.mdx +++ b/docs/develop/dotnet/workers/index.mdx @@ -18,3 +18,4 @@ import * as Components from '@site/src/components'; ## Workers - [Worker processes](/develop/dotnet/workers/run-worker-process) +- [Interceptors](/develop/dotnet/workers/interceptors) diff --git a/docs/develop/dotnet/workers/interceptors.mdx b/docs/develop/dotnet/workers/interceptors.mdx new file mode 100644 index 0000000000..b3f5c40873 --- /dev/null +++ b/docs/develop/dotnet/workers/interceptors.mdx @@ -0,0 +1,206 @@ +--- +id: interceptors +title: Interceptors - .NET SDK +sidebar_label: Interceptors +description: + Implement Interceptors in the Temporal .NET SDK to manage inbound and outbound SDK calls, enhance tracing, and add + authorization to your Workflows and Activities. +toc_max_heading_level: 3 +keywords: + - interceptors +tags: + - Interceptors + - .NET SDK + - Temporal SDKs +--- + +Interceptors are SDK hooks that let you intercept inbound and outbound Temporal calls. You use them to apply shared +behavior across many calls, such as tracing and authorization, before calls reach the application code and after they return. +This is similar to middleware in other frameworks. + +There are two main types of interceptors: inbound and outbound. + +* Outbound interceptors wrap network calls, running before they reach the network and after they return. +* Inbound interceptors run after the network hop, wrapping application code and running before it starts and after it returns. + +Those further break down into different interceptor types. + +:::warning Workflow interceptors and replay + +Workflow inbound and outbound interceptor methods also execute during [replay](/develop/dotnet/best-practices/testing-suite#replay). Use replay-safe APIs for logging, randomness, and time in these interceptors. +See [Develop Workflow logic](/develop/dotnet/workflows/basics#workflow-logic-requirements) for details. + +If you want to write generic code shared by all inbound Workflow call handlers but want to skip read-only operations, check `workflow.unsafe.is_read_only()`. + +Activity and Client interceptors are not affected by replay. + +::: + +## Register an Interceptor {#register} + +Registering an interceptor means supplying an interceptor instance to the SDK so Temporal can invoke it when matching +Client or Worker calls occur. Once registered, the interceptor runs as part of the call path and can observe or modify +request and response data. + +### Register on the Client + +Pass interceptors in the `Interceptors` argument of `TemporalClient.ConnectAsync`. Client interceptors modify outbound calls such +as starting and signaling Workflows. + +```dotnet +var interceptor = new MyCounterInterceptor(); + +var client = await TemporalClient.ConnectAsync(new() +{ + TargetHost = "localhost:7233", + Interceptors = [interceptor], +}); +``` + +The `Interceptors` list can contain multiple interceptors. +In this case they form a chain: a method implemented on an interceptor instance in the list can perform side effects, and modify the data, before passing it on to the corresponding method on the next interceptor in the list. + +### Register via a Plugin + +If you're building a reusable library or want to bundle interceptors with other primitives, you can register them through a [Plugin](/develop/plugins-guide#interceptors). + +### Register on the Worker only + +If your interceptor doesn't affect the Client, you can pass interceptors in the `interceptors` argument of `Worker()`. +Worker interceptors modify inbound and outbound Workflow and Activity calls. + +```dotnet +using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions("my-task-queue") + { + Interceptors = new IWorkerInterceptor[] + { + interceptor + } + } + .AddActivity(activities.SayHello) + .AddWorkflow() +); +``` + +## How to implement Interceptors + +Interceptors run as a chain. Each interceptor wraps the entire inner call: your code runs before the call, invokes `next` to execute the rest of the chain, and then runs after the call completes. This means you can inspect or modify both the `input` and the result, handle errors, and perform side effects at either stage. + +### Implementing Client call Interceptors + +To modify outbound Client calls, define a class implementing [`IClientInterceptor`](https://dotnet.temporal.io/api/Temporalio.Client.Interceptors.IClientInterceptor.html). Implement `InterceptClient()` to return a [`ClientOutboundInterceptor`](https://dotnet.temporal.io/api/Temporalio.Client.Interceptors.ClientOutboundInterceptor.html), overriding the outbound Client calls you want to modify. `IClientInterceptor.InterceptClient` receives the next `ClientOutboundInterceptor` in the chain and returns the created interceptor. + +This example implements an Interceptor on outbound Client calls that sets a certain key in the outbound `headers` field. +A User ID is context-propagated by being sent in a header field with outbound requests: + +```dotnet +using Google.Protobuf; +using Temporalio.Api.Common.V1; +using Temporalio.Client; +using Temporalio.Client.Interceptors; + +public class ContextPropagationInterceptor : IClientInterceptor +{ + public ClientOutboundInterceptor InterceptClient( + ClientOutboundInterceptor nextInterceptor) => + new ContextPropagationClientOutboundInterceptor(nextInterceptor); +} + +public class ContextPropagationClientOutboundInterceptor( + ClientOutboundInterceptor next) + : ClientOutboundInterceptor(next) +{ + public override Task> + StartWorkflowAsync(StartWorkflowInput input) + { + input.Headers["user-id"] = new Payload + { + Metadata = { ["encoding"] = ByteString.CopyFromUtf8("plain/text") }, + Data = ByteString.CopyFromUtf8(UserContext.UserId), + }; + + return base.StartWorkflowAsync(input); + } +} +``` + +You can then [register](#register) this interceptor in your client/starter code. + +Your interceptor classes don't need to implement every method. The default implementation is always to pass the data on to the next method in the interceptor chain. +During execution, when the SDK encounters an Inbound Activity call, it will look to the first Interceptor instance, get hold of the appropriate intercepted method, and call it. +The intercepted method will perform its function then call the same method on the next Interceptor in the chain. +At the end of the chain the SDK will call the "real" SDK method. + +### Implementing Worker call Interceptors + +To modify inbound Workflow and Activity calls, define a class implementing [`IWorkerInterceptor`](https://dotnet.temporal.io/api/Temporalio.Worker.Interceptors.IWorkerInterceptor.html). It provides `InterceptActivity()`, `InterceptWorkflow()`, and `InterceptNexusOperation()` methods for Activity, Workflow, and Nexus interception. + +This example demonstrates using an interceptor to measure [Schedule-To-Start](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout) and Schedule-To-Close latency. +Notice how the interceptor wraps the call. It records Schedule-To-Start before `ExecuteActivityAsync`, then records Schedule-To-Close after it completes: + +```dotnet +using Temporalio.Activities; +using Temporalio.Worker; +using Temporalio.Worker.Interceptors; + +public class SimpleWorkerInterceptor : IWorkerInterceptor +{ + public ActivityInboundInterceptor InterceptActivity( + ActivityInboundInterceptor nextInterceptor) => + new ActivityMetricsInterceptor(nextInterceptor); + + public WorkflowInboundInterceptor InterceptWorkflow( + WorkflowInboundInterceptor nextInterceptor) => + nextInterceptor; +} + +public class ActivityMetricsInterceptor(ActivityInboundInterceptor next) + : ActivityInboundInterceptor(next) +{ + public override async Task ExecuteActivityAsync( + ExecuteActivityInput input) + { + var info = ActivityExecutionContext.Current.Info; + var started = DateTimeOffset.UtcNow; + + // Before the activity executes + var scheduleToStart = + started - info.CurrentAttemptScheduledTime; + + Console.WriteLine( + $"Schedule-To-Start latency: {scheduleToStart}"); + + // Execute the activity + var result = await base.ExecuteActivityAsync(input); + + // After the activity completes + var scheduleToClose = + DateTimeOffset.UtcNow - info.CurrentAttemptScheduledTime; + + Console.WriteLine( + $"Schedule-To-Close latency: {scheduleToClose}"); + + return result; + } +} +``` + +Register it on the Worker: + +```dotnet +using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions("my-task-queue") + { + Interceptors = new IWorkerInterceptor[] + { + new SimpleWorkerInterceptor(), + }, + } + .AddActivity(activities.SayHello) + .AddWorkflow()); + +await worker.ExecuteAsync(); +``` diff --git a/sidebars.js b/sidebars.js index e606b87475..4fd77d07d9 100644 --- a/sidebars.js +++ b/sidebars.js @@ -823,6 +823,7 @@ module.exports = { }, items: [ 'develop/dotnet/workers/run-worker-process', + 'develop/dotnet/workers/interceptors', ] }, { From 84f111f9a2f81fca31543c1cb810c7a868101ff7 Mon Sep 17 00:00:00 2001 From: flippedcoder Date: Thu, 21 May 2026 14:18:58 -0500 Subject: [PATCH 3/4] chore(interceptors): updated based on feedback --- docs/develop/dotnet/workers/interceptors.mdx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/develop/dotnet/workers/interceptors.mdx b/docs/develop/dotnet/workers/interceptors.mdx index b3f5c40873..1d50be0820 100644 --- a/docs/develop/dotnet/workers/interceptors.mdx +++ b/docs/develop/dotnet/workers/interceptors.mdx @@ -30,8 +30,6 @@ Those further break down into different interceptor types. Workflow inbound and outbound interceptor methods also execute during [replay](/develop/dotnet/best-practices/testing-suite#replay). Use replay-safe APIs for logging, randomness, and time in these interceptors. See [Develop Workflow logic](/develop/dotnet/workflows/basics#workflow-logic-requirements) for details. -If you want to write generic code shared by all inbound Workflow call handlers but want to skip read-only operations, check `workflow.unsafe.is_read_only()`. - Activity and Client interceptors are not affected by replay. ::: @@ -58,7 +56,7 @@ var client = await TemporalClient.ConnectAsync(new() ``` The `Interceptors` list can contain multiple interceptors. -In this case they form a chain: a method implemented on an interceptor instance in the list can perform side effects, and modify the data, before passing it on to the corresponding method on the next interceptor in the list. +The default behavior for interceptors is to form a chain. A method implemented on an interceptor instance in the list can perform side effects, and modify the data, before passing it on to the corresponding method on the next interceptor in the list. ### Register via a Plugin @@ -66,7 +64,7 @@ If you're building a reusable library or want to bundle interceptors with other ### Register on the Worker only -If your interceptor doesn't affect the Client, you can pass interceptors in the `interceptors` argument of `Worker()`. +If your interceptor doesn't affect the Client, you can pass interceptors in the `Interceptors` argument of `TemporalWorker()`. Worker interceptors modify inbound and outbound Workflow and Activity calls. ```dotnet From ef23af7cf52713c1e18cc0aa767f56e89fa6c961 Mon Sep 17 00:00:00 2001 From: flippedcoder Date: Fri, 22 May 2026 09:36:23 -0500 Subject: [PATCH 4/4] chore(interceptors): updated based on tech review feedback --- docs/develop/dotnet/workers/interceptors.mdx | 41 +++++++++++++------- docs/encyclopedia/interceptors.mdx | 1 + 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/docs/develop/dotnet/workers/interceptors.mdx b/docs/develop/dotnet/workers/interceptors.mdx index 1d50be0820..1918b9fdf0 100644 --- a/docs/develop/dotnet/workers/interceptors.mdx +++ b/docs/develop/dotnet/workers/interceptors.mdx @@ -16,20 +16,28 @@ tags: Interceptors are SDK hooks that let you intercept inbound and outbound Temporal calls. You use them to apply shared behavior across many calls, such as tracing and authorization, before calls reach the application code and after they return. -This is similar to middleware in other frameworks. +This is similar to middleware in other frameworks, like [ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware). There are two main types of interceptors: inbound and outbound. * Outbound interceptors wrap network calls, running before they reach the network and after they return. * Inbound interceptors run after the network hop, wrapping application code and running before it starts and after it returns. -Those further break down into different interceptor types. +Concretely, there are five categories of inbound and outbound calls that you can modify in this way: + +| | [Outbound Client](https://dotnet.temporal.io/api/Temporalio.Client.Interceptors.ClientOutboundInterceptor.html) | [Inbound Workflow](https://dotnet.temporal.io/api/Temporalio.Worker.Interceptors.WorkflowInboundInterceptor.html) | [Outbound Workflow](https://dotnet.temporal.io/api/Temporalio.Worker.Interceptors.WorkflowOutboundInterceptor.html) | [Inbound Activity](https://dotnet.temporal.io/api/Temporalio.Worker.Interceptors.ActivityInboundInterceptor.html) | [Outbound Activity](https://dotnet.temporal.io/api/Temporalio.Worker.Interceptors.ActivityOutboundInterceptor.html) | +| --- | --- | --- | --- | --- | --- | +| **Description** | Wraps calls from your application to the Temporal Client to start a Workflow or send [Messages](/encyclopedia/workflow-message-passing/) to it | Wraps calls arriving into a [Workflow Execution](/workflow-execution), such as executing the Workflow, handling [Messages](/encyclopedia/workflow-message-passing/) | Wraps calls a [Workflow](/workflow-definition) makes to the SDK, such as scheduling [Activities](/activities), starting [Child Workflows](/child-workflows), and invoking [Nexus Operations](/nexus) | Wraps calls arriving into an [Activity Execution](/activity-execution) | Wraps calls an [Activity](/activities) makes to the SDK, such as sending [Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat) and reading Activity info | +| **Runs on** | Client | Worker (Workflow sandbox) | Worker (Workflow sandbox) | Worker (Activity context) | Worker (Activity context) | +| **Example methods** | `StartWorkflowAsync()`, `WorkflowHandle.SignalAsync()`, `ListWorkflowsAsync()` | `ExecuteWorkflowAsync()`, `WorkflowHandle.QueryAsync()`, `WorkflowHandle.SignalAsync ()`, `WorkflowHandle.ExecuteUpdateAsync()` | `StartActivityAsync()`, `StartChildWorkflowAsync()`, `ChildWorkflowHandle.SignalAsync()`, `StartNexusOperationAsync()` | `ExecuteActivityAsync()` | `Info()`, `Heartbeat()` | :::warning Workflow interceptors and replay Workflow inbound and outbound interceptor methods also execute during [replay](/develop/dotnet/best-practices/testing-suite#replay). Use replay-safe APIs for logging, randomness, and time in these interceptors. See [Develop Workflow logic](/develop/dotnet/workflows/basics#workflow-logic-requirements) for details. +If you want to write generic code shared by all inbound Workflow call handlers but want to skip read-only operations, check [`Workflow.Unsafe.IsReplaying`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.Unsafe.html#Temporalio_Workflows_Workflow_Unsafe_IsReplaying). + Activity and Client interceptors are not affected by replay. ::: @@ -42,11 +50,13 @@ request and response data. ### Register on the Client -Pass interceptors in the `Interceptors` argument of `TemporalClient.ConnectAsync`. Client interceptors modify outbound calls such -as starting and signaling Workflows. +Pass interceptors in the `Interceptors` argument of [`TemporalClientConnectOptions`](https://dotnet.temporal.io/api/Temporalio.Client.TemporalClientConnectOptions.html). Client interceptors modify outbound calls such +as starting and signaling Workflows. One example is [setting up tracing](/develop/dotnet/platform/observability) to see your call graph of a Workflow. -```dotnet -var interceptor = new MyCounterInterceptor(); +```csharp +using Temporalio.Extensions.OpenTelemetry; + +var interceptor = new TracingInterceptor(); var client = await TemporalClient.ConnectAsync(new() { @@ -64,18 +74,15 @@ If you're building a reusable library or want to bundle interceptors with other ### Register on the Worker only -If your interceptor doesn't affect the Client, you can pass interceptors in the `Interceptors` argument of `TemporalWorker()`. +If your interceptor doesn't affect the Client, you can pass interceptors in the `Interceptors` argument of `TemporalWorkerOptions`. Worker interceptors modify inbound and outbound Workflow and Activity calls. -```dotnet +```csharp using var worker = new TemporalWorker( client, new TemporalWorkerOptions("my-task-queue") { - Interceptors = new IWorkerInterceptor[] - { - interceptor - } + Interceptors = [interceptor] } .AddActivity(activities.SayHello) .AddWorkflow() @@ -93,7 +100,7 @@ To modify outbound Client calls, define a class implementing [`IClientIntercepto This example implements an Interceptor on outbound Client calls that sets a certain key in the outbound `headers` field. A User ID is context-propagated by being sent in a header field with outbound requests: -```dotnet +```csharp using Google.Protobuf; using Temporalio.Api.Common.V1; using Temporalio.Client; @@ -138,7 +145,7 @@ To modify inbound Workflow and Activity calls, define a class implementing [`IWo This example demonstrates using an interceptor to measure [Schedule-To-Start](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout) and Schedule-To-Close latency. Notice how the interceptor wraps the call. It records Schedule-To-Start before `ExecuteActivityAsync`, then records Schedule-To-Close after it completes: -```dotnet +```csharp using Temporalio.Activities; using Temporalio.Worker; using Temporalio.Worker.Interceptors; @@ -152,6 +159,10 @@ public class SimpleWorkerInterceptor : IWorkerInterceptor public WorkflowInboundInterceptor InterceptWorkflow( WorkflowInboundInterceptor nextInterceptor) => nextInterceptor; + + public NexusOperationInboundInterceptor InterceptNexusOperation( + NexusOperationInboundInterceptor nextInterceptor) => + nextInterceptor; } public class ActivityMetricsInterceptor(ActivityInboundInterceptor next) @@ -187,7 +198,7 @@ public class ActivityMetricsInterceptor(ActivityInboundInterceptor next) Register it on the Worker: -```dotnet +```csharp using var worker = new TemporalWorker( client, new TemporalWorkerOptions("my-task-queue") diff --git a/docs/encyclopedia/interceptors.mdx b/docs/encyclopedia/interceptors.mdx index c44def30c0..958c3f545f 100644 --- a/docs/encyclopedia/interceptors.mdx +++ b/docs/encyclopedia/interceptors.mdx @@ -28,3 +28,4 @@ Here are SDK-specific guides: - [Python](/develop/python/workers/interceptors) - [TypeScript](/develop/typescript/workers/interceptors) +- [.NET](/develop/dotnet/workers/interceptors)