1+ namespace TemporalioSamples . ContextPropagation ;
2+
3+ using System . Threading . Tasks ;
4+ using Temporalio . Api . Common . V1 ;
5+ using Temporalio . Client ;
6+ using Temporalio . Client . Interceptors ;
7+ using Temporalio . Converters ;
8+ using Temporalio . Worker . Interceptors ;
9+ using Temporalio . Workflows ;
10+
11+ /// <summary>
12+ /// General purpose interceptor that can be used to propagate async-local context through workflows
13+ /// and activities. This must be set on the client used for interacting with workflows and used for
14+ /// the worker.
15+ /// </summary>
16+ /// <typeparam name="T">Context data type.</typeparam>
17+ public class ContextPropagationInterceptor < T > : IClientInterceptor , IWorkerInterceptor
18+ {
19+ private readonly AsyncLocal < T > context ;
20+ private readonly IPayloadConverter payloadConverter ;
21+ private readonly string headerKey ;
22+
23+ public ContextPropagationInterceptor (
24+ AsyncLocal < T > context ,
25+ IPayloadConverter payloadConverter ,
26+ string headerKey = "__my_context_key" )
27+ {
28+ this . context = context ;
29+ this . payloadConverter = payloadConverter ;
30+ this . headerKey = headerKey ;
31+ }
32+
33+ public ClientOutboundInterceptor InterceptClient ( ClientOutboundInterceptor nextInterceptor ) =>
34+ new ContextPropagationClientOutboundInterceptor ( this , nextInterceptor ) ;
35+
36+ public WorkflowInboundInterceptor InterceptWorkflow ( WorkflowInboundInterceptor nextInterceptor ) =>
37+ new ContextPropagationWorkflowInboundInterceptor ( this , nextInterceptor ) ;
38+
39+ public ActivityInboundInterceptor InterceptActivity ( ActivityInboundInterceptor nextInterceptor ) =>
40+ new ContextPropagationActivityInboundInterceptor ( this , nextInterceptor ) ;
41+
42+ private Dictionary < string , Payload > HeaderFromContext ( IDictionary < string , Payload > ? existing )
43+ {
44+ var ret = existing != null ?
45+ new Dictionary < string , Payload > ( existing ) : new Dictionary < string , Payload > ( 1 ) ;
46+ ret [ headerKey ] = payloadConverter . ToPayload ( context . Value ) ;
47+ return ret ;
48+ }
49+
50+ private void WithHeadersApplied ( IReadOnlyDictionary < string , Payload > ? headers , Action func ) =>
51+ WithHeadersApplied (
52+ headers ,
53+ ( ) =>
54+ {
55+ func ( ) ;
56+ return ( object ? ) null ;
57+ } ) ;
58+
59+ private TResult WithHeadersApplied < TResult > (
60+ IReadOnlyDictionary < string , Payload > ? headers , Func < TResult > func )
61+ {
62+ if ( headers ? . TryGetValue ( headerKey , out var payload ) == true && payload != null )
63+ {
64+ context . Value = payloadConverter . ToValue < T > ( payload ) ;
65+ }
66+ // These are async local, no need to unapply afterwards
67+ return func ( ) ;
68+ }
69+
70+ private class ContextPropagationClientOutboundInterceptor : ClientOutboundInterceptor
71+ {
72+ private readonly ContextPropagationInterceptor < T > root ;
73+
74+ public ContextPropagationClientOutboundInterceptor (
75+ ContextPropagationInterceptor < T > root , ClientOutboundInterceptor next )
76+ : base ( next ) => this . root = root ;
77+
78+ public override Task < WorkflowHandle < TWorkflow , TResult > > StartWorkflowAsync < TWorkflow , TResult > (
79+ StartWorkflowInput input ) =>
80+ base . StartWorkflowAsync < TWorkflow , TResult > (
81+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
82+
83+ public override Task SignalWorkflowAsync ( SignalWorkflowInput input ) =>
84+ base . SignalWorkflowAsync (
85+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
86+
87+ public override Task < TResult > QueryWorkflowAsync < TResult > ( QueryWorkflowInput input ) =>
88+ base . QueryWorkflowAsync < TResult > (
89+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
90+
91+ public override Task < WorkflowUpdateHandle < TResult > > StartWorkflowUpdateAsync < TResult > (
92+ StartWorkflowUpdateInput input ) =>
93+ base . StartWorkflowUpdateAsync < TResult > (
94+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
95+ }
96+
97+ private class ContextPropagationWorkflowInboundInterceptor : WorkflowInboundInterceptor
98+ {
99+ private readonly ContextPropagationInterceptor < T > root ;
100+
101+ public ContextPropagationWorkflowInboundInterceptor (
102+ ContextPropagationInterceptor < T > root , WorkflowInboundInterceptor next )
103+ : base ( next ) => this . root = root ;
104+
105+ public override void Init ( WorkflowOutboundInterceptor outbound ) =>
106+ base . Init ( new ContextPropagationWorkflowOutboundInterceptor ( root , outbound ) ) ;
107+
108+ public override Task < object ? > ExecuteWorkflowAsync ( ExecuteWorkflowInput input ) =>
109+ root . WithHeadersApplied ( Workflow . Info . Headers , ( ) => Next . ExecuteWorkflowAsync ( input ) ) ;
110+
111+ public override Task HandleSignalAsync ( HandleSignalInput input ) =>
112+ root . WithHeadersApplied ( input . Headers , ( ) => Next . HandleSignalAsync ( input ) ) ;
113+
114+ public override object ? HandleQuery ( HandleQueryInput input ) =>
115+ root . WithHeadersApplied ( input . Headers , ( ) => Next . HandleQuery ( input ) ) ;
116+
117+ public override void ValidateUpdate ( HandleUpdateInput input ) =>
118+ root . WithHeadersApplied ( input . Headers , ( ) => Next . ValidateUpdate ( input ) ) ;
119+
120+ public override Task < object ? > HandleUpdateAsync ( HandleUpdateInput input ) =>
121+ root . WithHeadersApplied ( input . Headers , ( ) => Next . HandleUpdateAsync ( input ) ) ;
122+ }
123+
124+ private class ContextPropagationWorkflowOutboundInterceptor : WorkflowOutboundInterceptor
125+ {
126+ private readonly ContextPropagationInterceptor < T > root ;
127+
128+ public ContextPropagationWorkflowOutboundInterceptor (
129+ ContextPropagationInterceptor < T > root , WorkflowOutboundInterceptor next )
130+ : base ( next ) => this . root = root ;
131+
132+ public override Task < TResult > ScheduleActivityAsync < TResult > (
133+ ScheduleActivityInput input ) =>
134+ Next . ScheduleActivityAsync < TResult > (
135+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
136+
137+ public override Task < TResult > ScheduleLocalActivityAsync < TResult > (
138+ ScheduleLocalActivityInput input ) =>
139+ Next . ScheduleLocalActivityAsync < TResult > (
140+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
141+
142+ public override Task SignalChildWorkflowAsync (
143+ SignalChildWorkflowInput input ) =>
144+ Next . SignalChildWorkflowAsync (
145+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
146+
147+ public override Task SignalExternalWorkflowAsync (
148+ SignalExternalWorkflowInput input ) =>
149+ Next . SignalExternalWorkflowAsync (
150+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
151+
152+ public override Task < ChildWorkflowHandle < TWorkflow , TResult > > StartChildWorkflowAsync < TWorkflow , TResult > (
153+ StartChildWorkflowInput input ) =>
154+ Next . StartChildWorkflowAsync < TWorkflow , TResult > (
155+ input with { Headers = root . HeaderFromContext ( input . Headers ) } ) ;
156+ }
157+
158+ private class ContextPropagationActivityInboundInterceptor : ActivityInboundInterceptor
159+ {
160+ private readonly ContextPropagationInterceptor < T > root ;
161+
162+ public ContextPropagationActivityInboundInterceptor (
163+ ContextPropagationInterceptor < T > root , ActivityInboundInterceptor next )
164+ : base ( next ) => this . root = root ;
165+
166+ public override Task < object ? > ExecuteActivityAsync ( ExecuteActivityInput input ) =>
167+ root . WithHeadersApplied ( input . Headers , ( ) => Next . ExecuteActivityAsync ( input ) ) ;
168+ }
169+ }
0 commit comments