Skip to content

Commit 44eba4e

Browse files
donald-pinckneyclaudejmaeagle99
authored
Add .NET SDK support to temporal-developer skill (#39)
* Add .NET reference files for temporal-developer skill Created 11 .NET reference files covering: dotnet.md (overview/quick start), patterns.md, determinism.md, determinism-protection.md, error-handling.md, testing.md, versioning.md, observability.md, data-handling.md, gotchas.md, and advanced-features.md. Follows Python/TypeScript patterns with .NET-specific content for Task determinism, CancellationToken, dependency injection, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix .NET alignment issues from self-review - dotnet.md: Reduce Determinism Rules section to brief cross-reference (was duplicating determinism.md content) - patterns.md: Add ParentClosePolicy to Child Workflows example - gotchas.md: Add missing "Heartbeat Timeout Too Short" subsection - versioning.md: Add missing Key Concepts, Deployment Strategies, Query Filters, PINNED/AUTO_UPGRADE guidance, CLI examples - advanced-features.md: Add worker-level heading for exception types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix .NET correctness issues from verification pass - patterns.md: Fix cancellation pattern to use official TemporalException.IsCanceledException(e) with detached CancellationTokenSource - advanced-features.md: Fix DI hosting example to use official AddHostedTemporalWorker(clientTargetHost:, clientNamespace:, taskQueue:) pattern Verified against official SDK README, API docs, and temporal-docs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update supported language references to include .NET - SKILL.md: Add "Temporal .NET" and "Temporal C#" trigger phrases, update overview to mention .NET, add .NET entry in getting started - core/determinism.md: Add .NET entry in SDK Protection Mechanisms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Edits to advanced features * edits to determinism protection, and move the .editorconfig section * missed one * edit determinism.md * edit error-handling.md * edit gotchas.md * edit patterns.md * edit versioning.md * edit observability.md * fix metrics * self-review round 1 * minor correctness fixed * Update references/dotnet/patterns.md Co-authored-by: Justin Anderson <44687433+jmaeagle99@users.noreply.github.com> * address comments, clarify reference to earlier code snippet * clarify that operations are forbidden IN WORKFLOWS * cleanup workflow cancellation handling example * add task token retrieval comment * update .net requirements * Fix propagation of workflow cancellation --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Justin Anderson <44687433+jmaeagle99@users.noreply.github.com>
1 parent 418cbd7 commit 44eba4e

23 files changed

Lines changed: 2244 additions & 12 deletions

SKILL.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
---
22
name: temporal-developer
3-
description: Develop, debug, and manage Temporal applications across Python, TypeScript, Go, and Java. Use when the user is building workflows, activities, or workers with a Temporal SDK, debugging issues like non-determinism errors, stuck workflows, or activity retries, using Temporal CLI, Temporal Server, or Temporal Cloud, or working with durable execution concepts like signals, queries, heartbeats, versioning, continue-as-new, child workflows, or saga patterns.
3+
description: Develop, debug, and manage Temporal applications across Python, TypeScript, Go, Java and .NET. Use when the user is building workflows, activities, or workers with a Temporal SDK, debugging issues like non-determinism errors, stuck workflows, or activity retries, using Temporal CLI, Temporal Server, or Temporal Cloud, or working with durable execution concepts like signals, queries, heartbeats, versioning, continue-as-new, child workflows, or saga patterns.
44
version: 0.2.0
55
---
66

77
# Skill: temporal-developer
88

99
## Overview
1010

11-
Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python, TypeScript, Go, and Java.
11+
Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications in Python, TypeScript, Go, Java and .NET.
1212

1313
## Core Architecture
1414

@@ -79,8 +79,9 @@ Once you've downloaded the file, extract the downloaded archive and add the temp
7979
1. First, read the getting started guide for the language you are working in:
8080
- Python -> read `references/python/python.md`
8181
- TypeScript -> read `references/typescript/typescript.md`
82-
- Java -> read `references/java/java.md`
8382
- Go -> read `references/go/go.md`
83+
- Java -> read `references/java/java.md`
84+
- .NET (C#) -> read `references/dotnet/dotnet.md`
8485
2. Second, read appropriate `core` and language-specific references for the task at hand.
8586

8687
## Primary References

references/core/determinism.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Each Temporal SDK language provides a different level of protection against non-
8888
- TypeScript: The TypeScript SDK runs workflows in an isolated V8 sandbox, intercepting many common sources of non-determinism and replacing them automatically with deterministic variants.
8989
- Java: The Java SDK has no sandbox. Determinism is enforced by developer conventions — the SDK provides `Workflow.*` APIs as safe alternatives (e.g., `Workflow.sleep()` instead of `Thread.sleep()`), and non-determinism is only detected at replay time via `NonDeterministicException`. A static analysis tool (`temporal-workflowcheck`, beta) can catch violations at build time. Cooperative threading under a global lock eliminates the need for synchronization.
9090
- Go: The Go SDK has no runtime sandbox. Therefore, non-determinism bugs will never be immediately appararent, and are usually only observable during replay. The optional `workflowcheck` static analysis tool can be used to check for many sources of non-determinism at compile time.
91+
- .NET: The .NET SDK has no sandbox. It uses a custom TaskScheduler and a runtime EventListener to detect invalid task scheduling. Developers must use Workflow.* safe alternatives (e.g., Workflow.DelayAsync instead of Task.Delay) and avoid non-deterministic .NET Task APIs.
9192

9293
Regardless of which SDK you are using, it is your responsibility to ensure that workflow code does not contain sources of non-determinism. Use SDK-specific tools as well as replay tests for doing so.
9394

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# .NET SDK Advanced Features
2+
3+
## Schedules
4+
5+
Create recurring workflow executions.
6+
7+
```csharp
8+
using Temporalio.Client.Schedules;
9+
10+
var scheduleId = "daily-report";
11+
await client.CreateScheduleAsync(
12+
scheduleId,
13+
new Schedule(
14+
Action: ScheduleActionStartWorkflow.Create(
15+
(DailyReportWorkflow wf) => wf.RunAsync(),
16+
new(id: "daily-report", taskQueue: "reports")),
17+
Spec: new ScheduleSpec
18+
{
19+
Intervals = new List<ScheduleIntervalSpec>
20+
{
21+
new(Every: TimeSpan.FromDays(1)),
22+
},
23+
}));
24+
25+
// Manage schedules
26+
var handle = client.GetScheduleHandle(scheduleId);
27+
await handle.PauseAsync("Maintenance window");
28+
await handle.UnpauseAsync();
29+
await handle.TriggerAsync(); // Run immediately
30+
await handle.DeleteAsync();
31+
```
32+
33+
## Async Activity Completion
34+
35+
For activities that complete asynchronously (e.g., human tasks, external callbacks).
36+
If you configure a `HeartbeatTimeout` on this activity, the external completer is responsible for sending heartbeats via the async handle.
37+
If you do NOT set a `HeartbeatTimeout`, no heartbeats are required.
38+
39+
**Note:** If the external system that completes the asynchronous action can reliably be trusted to do the task and Signal back with the result, and it doesn't need to Heartbeat or receive Cancellation, then consider using **signals** instead.
40+
41+
```csharp
42+
using Temporalio.Activities;
43+
using Temporalio.Client;
44+
45+
[Activity]
46+
public async Task RequestApprovalAsync(string requestId)
47+
{
48+
var taskToken = ActivityExecutionContext.Current.Info.TaskToken;
49+
50+
// Store task token for later completion (e.g., in database)
51+
await StoreTaskTokenAsync(requestId, taskToken);
52+
53+
// Mark this activity as waiting for external completion
54+
throw new CompleteAsyncException();
55+
}
56+
57+
// Later, complete the activity from another process
58+
public async Task CompleteApprovalAsync(string requestId, bool approved)
59+
{
60+
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
61+
// Retrieve the task token from external storage (e.g., database)
62+
var taskToken = await GetTaskTokenAsync(requestId);
63+
64+
var handle = client.GetAsyncActivityHandle(taskToken);
65+
66+
// Optional: if a HeartbeatTimeout was set, you can periodically:
67+
// await handle.HeartbeatAsync(progressDetails);
68+
69+
if (approved)
70+
await handle.CompleteAsync("approved");
71+
else
72+
// You can also fail or report cancellation via the handle
73+
await handle.FailAsync(new ApplicationFailureException("Rejected"));
74+
}
75+
```
76+
77+
## Worker Tuning
78+
79+
Configure worker performance settings.
80+
81+
```csharp
82+
var worker = new TemporalWorker(
83+
client,
84+
new TemporalWorkerOptions("my-task-queue")
85+
{
86+
// Workflow task concurrency
87+
MaxConcurrentWorkflowTasks = 100,
88+
// Activity task concurrency
89+
MaxConcurrentActivities = 100,
90+
// Graceful shutdown timeout
91+
GracefulShutdownTimeout = TimeSpan.FromSeconds(30),
92+
}
93+
.AddWorkflow<MyWorkflow>()
94+
.AddAllActivities(new MyActivities()));
95+
```
96+
97+
## Workflow Init Attribute
98+
99+
Use `[WorkflowInit]` on a constructor to run initialization code when a workflow is first created.
100+
101+
**Purpose:** Execute some setup code before signal/update happens or run is invoked.
102+
103+
```csharp
104+
[Workflow]
105+
public class MyWorkflow
106+
{
107+
private readonly string _initialValue;
108+
private readonly List<string> _items = new();
109+
110+
[WorkflowInit]
111+
public MyWorkflow(string initialValue)
112+
{
113+
_initialValue = initialValue;
114+
}
115+
116+
[WorkflowRun]
117+
public async Task<string> RunAsync(string initialValue)
118+
{
119+
// _initialValue and _items are already initialized
120+
return _initialValue;
121+
}
122+
}
123+
```
124+
125+
Constructor and `[WorkflowRun]` method must have the same parameters with the same types. You cannot make blocking calls (activities, sleeps, etc.) from the constructor.
126+
127+
## Workflow Failure Exception Types
128+
129+
Control which exceptions cause workflow failures vs workflow task retries.
130+
131+
**Default behavior:** Only `ApplicationFailureException` fails a workflow. All other exceptions retry the workflow task forever (treated as bugs to fix with a code deployment).
132+
133+
**Tip for testing:** Set `WorkflowFailureExceptionTypes` to include `Exception` so any unhandled exception fails the workflow immediately rather than retrying the workflow task forever. This surfaces bugs faster.
134+
135+
### Worker-Level Configuration
136+
137+
```csharp
138+
var worker = new TemporalWorker(
139+
client,
140+
new TemporalWorkerOptions("my-task-queue")
141+
{
142+
// These exception types will fail the workflow execution (not just the task)
143+
WorkflowFailureExceptionTypes = new[] { typeof(ArgumentException), typeof(InvalidOperationException) },
144+
}
145+
.AddWorkflow<MyWorkflow>()
146+
.AddAllActivities(new MyActivities()));
147+
```
148+
149+
## Dependency Injection
150+
151+
The .NET SDK supports dependency injection via the `Temporalio.Extensions.Hosting` package, which integrates with .NET's generic host.
152+
153+
### Worker as Generic Host
154+
155+
```csharp
156+
using Temporalio.Extensions.Hosting;
157+
158+
public class Program
159+
{
160+
public static async Task Main(string[] args)
161+
{
162+
var host = Host.CreateDefaultBuilder(args)
163+
.ConfigureServices(ctx =>
164+
ctx.
165+
AddScoped<IOrderRepository, OrderRepository>().
166+
AddHostedTemporalWorker(
167+
clientTargetHost: "localhost:7233",
168+
clientNamespace: "default",
169+
taskQueue: "my-task-queue").
170+
AddScopedActivities<MyActivities>().
171+
AddWorkflow<MyWorkflow>())
172+
.Build();
173+
await host.RunAsync();
174+
}
175+
}
176+
```
177+
178+
### Activity Dependency Injection
179+
180+
As shown in the host setup above, activities can be registered with `AddScopedActivities<T>()`, `AddSingletonActivities<T>()`, or `AddTransientActivities<T>()`. Activities registered this way are created via DI, allowing constructor injection:
181+
182+
```csharp
183+
public class MyActivities
184+
{
185+
private readonly ILogger<MyActivities> _logger;
186+
private readonly IOrderRepository _repository;
187+
188+
public MyActivities(ILogger<MyActivities> logger, IOrderRepository repository)
189+
{
190+
_logger = logger;
191+
_repository = repository;
192+
}
193+
194+
[Activity]
195+
public async Task<Order> GetOrderAsync(string orderId)
196+
{
197+
_logger.LogInformation("Fetching order {OrderId}", orderId);
198+
return await _repository.GetAsync(orderId);
199+
}
200+
}
201+
```
202+
203+
**Note:** Dependency injection is NOT available in workflows — workflows must be self-contained for determinism.

0 commit comments

Comments
 (0)