diff --git a/.vscode/settings.json b/.vscode/settings.json index ad92582bd0..e69de29bb2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +0,0 @@ -{ - "editor.formatOnSave": true -} diff --git a/docs/cloud/get-started/api-keys.mdx b/docs/cloud/get-started/api-keys.mdx index a86b25ff99..cc718cc49c 100644 --- a/docs/cloud/get-started/api-keys.mdx +++ b/docs/cloud/get-started/api-keys.mdx @@ -415,7 +415,7 @@ To use your API key with a Temporal SDK, see the instructions in each SDK sectio [How to connect to Temporal Cloud using an API Key with the TypeScript SDK](/develop/typescript/temporal-client#connect-to-temporal-cloud) -[How to connect to Temporal Cloud using an API Key with the .NET SDK](/develop/dotnet/temporal-client#connect-to-temporal-cloud) +[How to connect to Temporal Cloud using an API Key with the .NET SDK](/develop/dotnet/client/temporal-client#connect-to-temporal-cloud) ### tcld diff --git a/docs/cloud/get-started/certificates.mdx b/docs/cloud/get-started/certificates.mdx index dce0cb246a..7decce83ca 100644 --- a/docs/cloud/get-started/certificates.mdx +++ b/docs/cloud/get-started/certificates.mdx @@ -486,7 +486,7 @@ To view the current certificate filters, use the - [PHP SDK](/develop/php/temporal-client#connect-to-a-dev-cluster) - [Python SDK](/develop/python/temporal-client#connect-to-temporal-cloud) - [TypeScript SDK](/develop/typescript/temporal-client#connect-to-temporal-cloud) -- [.NET SDK](/develop/dotnet/temporal-client#connect-to-temporal-cloud) +- [.NET SDK](/develop/dotnet/client/temporal-client#connect-to-temporal-cloud) ### Configure Temporal CLI {#configure-temporal-cli} diff --git a/docs/cloud/get-started/index.mdx b/docs/cloud/get-started/index.mdx index 382983e2c3..532b635e77 100644 --- a/docs/cloud/get-started/index.mdx +++ b/docs/cloud/get-started/index.mdx @@ -56,7 +56,7 @@ See our guides for connecting each SDK to your Temporal Cloud Namespace: - [Connect to Temporal Cloud in Java](/develop/java/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in Python](/develop/python/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in TypeScript](/develop/typescript/core-application#connect-to-temporal-cloud) -- [Connect to Temporal Cloud in .NET](/develop/dotnet/temporal-client#connect-to-temporal-cloud) +- [Connect to Temporal Cloud in .NET](/develop/dotnet/client/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in PHP](/develop/php/temporal-client#connect-to-temporal-cloud) - [Connect to Temporal Cloud in Ruby](/develop/ruby/temporal-client#connect-to-temporal-cloud) @@ -68,7 +68,7 @@ See our guides for starting a workflow using each SDK: - [Start a workflow in Java](/develop/java/temporal-client#start-workflow-execution) - [Start a workflow in Python](/develop/python/temporal-client#start-workflow-execution) - [Start a workflow in TypeScript](/develop/typescript/core-application#start-workflow-execution) -- [Start a workflow in .NET](/develop/dotnet/temporal-client#start-workflow) +- [Start a workflow in .NET](/develop/dotnet/client/temporal-client#start-workflow) - [Start a workflow in PHP](/develop/php/temporal-client#start-workflow-execution) - [Start a workflow in Ruby](/develop/ruby/temporal-client#start-workflow) diff --git a/docs/cloud/metrics/prometheus-grafana.mdx b/docs/cloud/metrics/prometheus-grafana.mdx index 71ffda59da..83dda37298 100644 --- a/docs/cloud/metrics/prometheus-grafana.mdx +++ b/docs/cloud/metrics/prometheus-grafana.mdx @@ -57,7 +57,7 @@ If you're following through with the examples provided here, ensure that you hav - [PHP](/develop/php/temporal-client#connect-to-a-dev-cluster) - [Python](/develop/python/temporal-client#connect-to-temporal-cloud) - [TypeScript](/develop/typescript/core-application#connect-to-temporal-cloud) - - [.NET](/develop/dotnet/temporal-client#connect-to-temporal-cloud) + - [.NET](/develop/dotnet/client/temporal-client#connect-to-temporal-cloud) - Prometheus and Grafana installed. @@ -96,7 +96,7 @@ Each language development guide has details on how to set this up. - [Java SDK](/develop/java/observability#metrics) - [TypeScript SDK](/develop/typescript/observability#metrics) - [Python](/develop/python/observability#metrics) -- [.NET](/develop/dotnet/observability#metrics) +- [.NET](/develop/dotnet/workers/observability#metrics) The following example uses the Java SDK to set the Prometheus registry and Micrometer stats reporter, set the scope, and expose an endpoint from which Prometheus can scrape the SDK metrics. diff --git a/docs/develop/dotnet/asynchronous-activity.mdx b/docs/develop/dotnet/activities/asynchronous-activity.mdx similarity index 100% rename from docs/develop/dotnet/asynchronous-activity.mdx rename to docs/develop/dotnet/activities/asynchronous-activity.mdx diff --git a/docs/develop/dotnet/activities/basics.mdx b/docs/develop/dotnet/activities/basics.mdx new file mode 100644 index 0000000000..cfc8a4bad4 --- /dev/null +++ b/docs/develop/dotnet/activities/basics.mdx @@ -0,0 +1,55 @@ +--- +id: basics +title: Activity Basics - .NET SDK +sidebar_label: Activity Basics +description: This section explains Activity Basics with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +## Develop an Activity {#develop-activity} + +One of the primary things that Workflows do is orchestrate the execution of Activities. +An Activity is a normal method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file. +An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Temporal Service. +For the Workflow to be able to execute the Activity, we must define the [Activity Definition](/activity-definition). + +You can develop an Activity Definition by using the `[Activity]` attribute from the `Temporalio.Activities` namespace on the method. +To register a method as an Activity with a custom name, use an attribute parameter, for example `[Activity("your-activity")]`. +Otherwise, the activity name is the unqualified method name (sans an "Async" suffix if the method is async). + +Activities can be asynchronous or synchronous. + +```csharp +using Temporalio.Activities; + +public class MyActivities +{ + // Activities can be async and/or static too. We just demonstrate instance methods since many + // use them that way. + [Activity] + public string MyActivity(MyActivityParams input) => + $"{input.Greeting}, {input.Name}!"; +} +``` + +There is no explicit limit to the total number of parameters that an [Activity Definition](/activity-definition) may support. +However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload. + +A single argument is limited to a maximum size of 2 MB. +And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB. + +Also, keep in mind that all Payload data is recorded in the [Workflow Execution Event History](/workflow-execution/event#event-history) and large Event Histories can affect Worker performance. +This is because the entire Event History could be transferred to a Worker Process with a [Workflow Task](/tasks#workflow-task). + +Some SDKs require that you pass context objects, others do not. +When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. +This is so that you can change what data is passed to the Activity without breaking a method signature. + +Activity parameters are the method parameters of the method with the `[Activity]` attribute. +These can be any data type Temporal can convert, including records. +Technically this can be multiple parameters, but Temporal strongly encourages a single parameter containing all input fields. diff --git a/docs/develop/dotnet/benign-exceptions.mdx b/docs/develop/dotnet/activities/benign-exceptions.mdx similarity index 100% rename from docs/develop/dotnet/benign-exceptions.mdx rename to docs/develop/dotnet/activities/benign-exceptions.mdx diff --git a/docs/develop/dotnet/activities/dynamic-activity.mdx b/docs/develop/dotnet/activities/dynamic-activity.mdx new file mode 100644 index 0000000000..4efb9850f1 --- /dev/null +++ b/docs/develop/dotnet/activities/dynamic-activity.mdx @@ -0,0 +1,36 @@ +--- +id: dynamic-activity +title: Dynamic Activity - .NET SDK +sidebar_label: Dynamic Activity +description: This section explains Dynamic Activities with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +## Set a Dynamic Activity {#set-a-dynamic-activity} + +**How to set a Dynamic Activity using the Temporal .NET SDK** + +A Dynamic Activity in Temporal is an Activity that is invoked dynamically at runtime if no other Activity with the same name is registered. +An Activity can be made dynamic by setting `Dynamic` as `true` on the `[Activity]` attribute. +You must register the Activity with the Worker before it can be invoked. +Only one Dynamic Activity can be present on a Worker. + +The Activity Definition must then accept a single argument of type `Temporalio.Converters.IRawValue[]`. +The [PayloadConverter](https://dotnet.temporal.io/api/Temporalio.Activities.ActivityExecutionContext.html#Temporalio_Activities_ActivityExecutionContext_PayloadConverter) property on the `ActivityExecutionContext` is used to convert an `IRawValue` object to the desired type using extension methods in the `Temporalio.Converters` namespace. + +```csharp +public class MyActivities +{ + [Activity(Dynamic = true)] + public string DynamicActivity(IRawValue[] args) + { + var input = ActivityExecutionContext.Current.PayloadConverter.ToValue(args.Single()); + return $"{input.Greeting}, {input.Name}!"; + } +} +``` diff --git a/docs/develop/dotnet/activities/execution.mdx b/docs/develop/dotnet/activities/execution.mdx new file mode 100644 index 0000000000..fdf23e93b0 --- /dev/null +++ b/docs/develop/dotnet/activities/execution.mdx @@ -0,0 +1,54 @@ +--- +id: execution +title: Activity execution - .NET SDK +description: Shows how to perform Activity execution with the .NET SDK +sidebar_label: Activity Execution +slug: /develop/dotnet/activities/execution +toc_max_heading_level: 3 +tags: + - .NET SDK + - Temporal SDKs + - Activity +--- + +## Start Activity Execution {#activity-execution} + +Calls to spawn [Activity Executions](/activity-execution) are written within a [Workflow Definition](/workflow-definition). +The call to spawn an Activity Execution generates the [ScheduleActivityTask](/references/commands#scheduleactivitytask) Command. +This results in the set of three [Activity Task](/tasks#activity-task) related Events ([ActivityTaskScheduled](/references/events#activitytaskscheduled), [ActivityTaskStarted](/references/events#activitytaskstarted), and ActivityTask[Closed])in your Workflow Execution Event History. + +A single instance of the Activities implementation is shared across multiple simultaneous Activity invocations. +Activity implementation code should be _idempotent_. + +The values passed to Activities through invocation parameters or returned through a result value are recorded in the Execution history. +The entire Execution history is transferred from the Temporal service to Workflow Workers when a Workflow state needs to recover. +A large Execution history can thus adversely impact the performance of your Workflow. + +Therefore, be mindful of the amount of data you transfer through Activity invocation parameters or Return Values. +Otherwise, no additional limitations exist on Activity implementations. + +To spawn an Activity Execution, use the `ExecuteActivityAsync` operation from within your Workflow Definition. + +```csharp +using Temporalio.Workflows; + +[Workflow] +public class MyWorkflow +{ + public async Task RunAsync(string name) + { + var param = MyActivityParams("Hello", name); + return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } +} +``` + +Activity Execution semantics rely on several parameters. +The only required value that needs to be set is either a [Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout) or a [Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout). +These values are set in the Activity Options. + +### Get Activity Execution results {#get-activity-results} + +The Activity result is the returned in the task from the `ExecuteActivityAsync` call. \ No newline at end of file diff --git a/docs/develop/dotnet/activities/index.mdx b/docs/develop/dotnet/activities/index.mdx new file mode 100644 index 0000000000..9127acc1c0 --- /dev/null +++ b/docs/develop/dotnet/activities/index.mdx @@ -0,0 +1,25 @@ +--- +id: index +title: Activities - .NET SDK +sidebar_label: Activities +description: This section explains how to implement Activities with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Activities + +- [Activity Basics](/develop/dotnet/activities/basics) +- [Activity Execution](/develop/dotnet/activities/execution) +- [Timeouts](/develop/dotnet/activities/timeouts) +- [Asynchronous Activity Completion](/develop/dotnet/activities/asynchronous-activity) +- [Dynamic Activity](/develop/dotnet/activities/dynamic-activity) +- [Benign exceptions](/develop/dotnet/activities/benign-exceptions) \ No newline at end of file diff --git a/docs/develop/dotnet/activities/timeouts.mdx b/docs/develop/dotnet/activities/timeouts.mdx new file mode 100644 index 0000000000..5b4f9b552b --- /dev/null +++ b/docs/develop/dotnet/activities/timeouts.mdx @@ -0,0 +1,129 @@ +--- +id: timeouts +title: Activity Timeouts - .NET SDK +sidebar_label: Timeouts +description: Optimize Workflow Execution with Temporal .NET SDK - Set Activity Timeouts and Retry Policies efficiently. +toc_max_heading_level: 4 +keywords: + - .NET + - failure detection + - timeouts +tags: + - Activities + - Workflows + - Errors + - Failures + - .NET SDK + - Temporal SDKs +--- + +## Activity Timeouts {#activity-timeouts} + +Each Activity Timeout controls the maximum duration of a different aspect of an Activity Execution. + +The following Timeouts are available in the Activity Options. + +- **[Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout):** is the maximum amount of time allowed for the overall [Activity Execution](/activity-execution). +- **[Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout):** is the maximum time allowed for a single [Activity Task Execution](/tasks#activity-task-execution). +- **[Schedule-To-Start Timeout](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout):** is the maximum amount of time that is allowed from when an [Activity Task](/tasks#activity-task) is scheduled to when a [Worker](/workers#worker) starts that Activity Task. + +An Activity Execution must have either the Start-To-Close or the Schedule-To-Close Timeout set. + +These values can be set in the `ActivityOptions` when calling `ExecuteActivityAsync`. + +Available timeouts are: + +- ScheduleToCloseTimeout +- ScheduleToStartTimeout +- StartToCloseTimeout + +```csharp +return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); +``` + +### Set an Activity Retry Policy {#activity-retries} + +A Retry Policy works in cooperation with the timeouts to provide fine controls to optimize the execution experience. + +Activity Executions are automatically associated with a default [Retry Policy](/encyclopedia/retry-policies) if a custom one is not provided. + +To create an Activity Retry Policy in .NET, set the `RetryPolicy` on the `ActivityOptions` when calling `ExecuteActivityAsync`. + +```csharp +return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() + { + StartToCloseTimeout = TimeSpan.FromMinutes(5), + RetryPolicy = new() { MaximumInterval = TimeSpan.FromSeconds(10) }, + }); +``` + +### Override the Retry interval with `nextRetryDelay` {#next-retry-delay} + +When you throw an [Application Failure](/references/failures#application-failure) and assign the `nextRetryDelay` field, its value replaces and overrides the Retry interval defined in the active Retry Policy. + +For example, you might scale the next Retry delay interval based on the current number of attempts. +Here's how you'd do that in an Activity. +In the following sample, the `attempt` count is retrieved from the Activity Execution context and used to set the number of seconds for the next Retry delay: + +```csharp +var attempt = ActivityExecutionContext.Current.Info.Attempt; + +throw new ApplicationFailureException( + $"Something bad happened on attempt {attempt}", + errorType: "my_failure_type", + nextRetryDelay: TimeSpan.FromSeconds(3 * attempt)); +``` + +## Heartbeat an Activity {#activity-heartbeats} + +An [Activity Heartbeat](/encyclopedia/detecting-activity-failures#activity-heartbeat) is a ping from the [Worker Process](/workers#worker-process) that is executing the Activity to the [Temporal Service](/temporal-service). +Each Heartbeat informs the Temporal Service that the [Activity Execution](/activity-execution) is making progress and the Worker has not crashed. +If the Temporal Service does not receive a Heartbeat within a [Heartbeat Timeout](/encyclopedia/detecting-activity-failures#heartbeat-timeout) time period, the Activity will be considered failed and another [Activity Task Execution](/tasks#activity-task-execution) may be scheduled according to the Retry Policy. + +Heartbeats may not always be sent to the Temporal Service—they may be [throttled](/encyclopedia/detecting-activity-failures#throttling) by the Worker. + +Activity Cancellations are delivered to Activities from the Temporal Service when they Heartbeat. Activities that don't Heartbeat can't receive a Cancellation. +Heartbeat throttling may lead to Cancellation getting delivered later than expected. + +Heartbeats can contain a `Details` field describing the Activity's current progress. +If an Activity gets retried, the Activity can access the `Details` from the last Heartbeat that was sent to the Temporal Service. + +To Heartbeat an Activity Execution in .NET, use the [`Heartbeat()`](https://dotnet.temporal.io/api/Temporalio.Activities.ActivityExecutionContext.html#Temporalio_Activities_ActivityExecutionContext_Heartbeat_System_Object___) method on the `ActivityExecutionContext`. + +```csharp +[Activity] +public async Task MyActivityAsync() +{ + while (true) + { + // Send heartbeat + ActivityExecutionContext.Current.Heartbeat(); + + // Do some work, passing the cancellation token + await Task.Delay(1000, ActivityExecutionContext.Current.CancellationToken); + } +} +``` + +In addition to obtaining cancellation information, Heartbeats also support detail data that persists on the server for retrieval during Activity retry. +If an Activity calls `Heartbeat(123, 456)` and then fails and is retried, `HeartbeatDetails` on the `ActivityInfo` returns an collection containing `123` and `456` on the next Run. + +### Set a Heartbeat Timeout {#heartbeat-timeout} + +A [Heartbeat Timeout](/encyclopedia/detecting-activity-failures#heartbeat-timeout) works in conjunction with [Activity Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat). + +`HeartbeatTimeout` is a property on `ActivityOptions` for `ExecuteActivityAsync` used to set the maximum time between Activity Heartbeats. + +```csharp +await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() + { + StartToCloseTimeout = TimeSpan.FromMinutes(5), + HeartbeatTimeout = TimeSpan.FromSeconds(30), + }); +``` diff --git a/docs/develop/dotnet/converters-and-encryption.mdx b/docs/develop/dotnet/best-practices/converters-and-encryption.mdx similarity index 98% rename from docs/develop/dotnet/converters-and-encryption.mdx rename to docs/develop/dotnet/best-practices/converters-and-encryption.mdx index 7bcb2df620..8f362cc1d8 100644 --- a/docs/develop/dotnet/converters-and-encryption.mdx +++ b/docs/develop/dotnet/best-practices/converters-and-encryption.mdx @@ -34,8 +34,6 @@ When working with sensitive data, you should always implement Payload encryption ## Custom Payload Codec {#custom-payload-codec} -**How to use a custom Payload Codec using the .NET SDK** - Custom Data Converters can change the default Temporal Data Conversion behavior by adding hooks, sending payloads to external storage, or performing different encoding steps. If you only need to change the encoding performed on your payloads -- by adding compression or encryption -- you can override the default Data Converter to use a new `PayloadCodec`. @@ -121,8 +119,6 @@ Temporal's Converter architecture looks like this: ### Custom Payload Converter {#custom-payload-converter} -**How to use a custom Payload Converter with the .NET SDK.** - Data converters are used to convert raw Temporal payloads to/from actual .NET types. A custom data converter can be set via the `DataConverter` option when creating a client. Data converters are a combination of payload converters, payload codecs, and failure converters. Payload converters convert .NET values to/from serialized bytes. Payload codecs convert bytes to bytes (e.g. for compression or encryption). Failure converters convert exceptions to/from serialized failures. diff --git a/docs/develop/dotnet/debugging.mdx b/docs/develop/dotnet/best-practices/debugging.mdx similarity index 82% rename from docs/develop/dotnet/debugging.mdx rename to docs/develop/dotnet/best-practices/debugging.mdx index 8ad7bf8198..94cb7ce853 100644 --- a/docs/develop/dotnet/debugging.mdx +++ b/docs/develop/dotnet/best-practices/debugging.mdx @@ -24,25 +24,21 @@ This page shows how to do the following: ### Debug in a development environment {#debug-in-a-development-environment} -**How to debug in a development environment using the Temporal .NET SDK** - In developing Workflows, you can use the normal development tools of logging and a debugger to see what’s happening in your Workflow. In addition to the normal development tools of logging and a debugger, you can also see what’s happening in your Workflow by using the [Web UI](/web-ui) or [Temporal CLI](/cli). The Web UI provides insight into your Workflows, making it easier to identify issues and monitor the state of your Workflows in real time. ### Debug in a development production {#debug-in-a-development-production} -**How to debug in a development production using the Temporal .NET SDK** - You can debug production Workflows using: - [Web UI](/web-ui) - [Temporal CLI](/cli) -- [Replay](/develop/dotnet/testing-suite#replay) -- [Tracing](/develop/dotnet/observability#tracing) -- [Logging](/develop/dotnet/observability#logging) +- [Replay](/develop/dotnet/best-practices/testing-suite#replay) +- [Tracing](/develop/dotnet/workers/observability#tracing) +- [Logging](/develop/dotnet/workers/observability#logging) You can debug and tune Worker performance with metrics and the [Worker performance guide](/develop/worker-performance). -For more information, see [Observability ▶️ Metrics](/develop/dotnet/observability#metrics) for setting up SDK metrics. +For more information, see [Observability ▶️ Metrics](/develop/dotnet/workers/observability#metrics) for setting up SDK metrics. Debug Server performance with [Cloud metrics](/cloud/metrics/) or [self-hosted Server metrics](/self-hosted-guide/production-checklist#scaling-and-metrics). diff --git a/docs/develop/dotnet/best-practices/error-handling.mdx b/docs/develop/dotnet/best-practices/error-handling.mdx new file mode 100644 index 0000000000..a069a3ae01 --- /dev/null +++ b/docs/develop/dotnet/best-practices/error-handling.mdx @@ -0,0 +1,102 @@ +--- +id: error-handling +title: Error handling - .NET SDK +sidebar_label: Error handling +description: Handle errors with Temporal .Net SDK +toc_max_heading_level: 4 +keywords: + - guide context + - how to + - dotnet + - sdk + - failure detection +tags: + - Activities + - Workflows + - .Net SDK + - Temporal SDKs + - Failures + - Errors +--- + +## Raise and Handle Exceptions {#exception-handling} + +In each Temporal SDK, error handling is implemented idiomatically, following the conventions of the language. +Temporal uses several different error classes internally — for example, [`CancelledFailureException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.CanceledFailureException.html) in the .NET SDK, to handle a Workflow cancellation. +You should not raise or otherwise implement these manually, as they are tied to Temporal platform logic. + +The one Temporal error class that you will typically raise deliberately is [`ApplicationFailureException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.ApplicationFailureException.html). +In fact, *any* other exceptions that are raised from your C# code in a Temporal Activity will be converted to an `ApplicationFailureException` internally. +This way, an error's type, severity, and any additional details can be sent to the Temporal Service, indexed by the Web UI, and even serialized across language boundaries. + +In other words, these two code samples do the same thing: + +```csharp +[Serializable] +public class InvalidDepartmentException : Exception +{ + public InvalidDepartmentException() : base() { } + public InvalidDepartmentException(string message) : base(message) { } + public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { } +} + +[Activity] +public Task SendBillAsync(Bill bill) +{ + throw new InvalidDepartmentException("Invalid department"); +} +``` + +```csharp +[Activity] +public Task SendBillAsync(Bill bill) +{ + throw new ApplicationFailureException("Invalid department", errorType: "InvalidDepartmentException"); +} +``` + +Depending on your implementation, you may decide to use either method. +One reason to use the Temporal `ApplicationFailureException` class is because it allows you to set an additional `non_retryable` parameter. +This way, you can decide whether an error should not be retried automatically by Temporal. +This can be useful for deliberately failing a Workflow due to bad input data, rather than waiting for a timeout to elapse: + +```csharp +[Activity] +public Task SendBillAsync(Bill bill) +{ + throw new ApplicationFailureException("Invalid department", nonRetryable: true); +} +``` + +You can alternately specify a list of errors that are non-retryable in your Activity [Retry Policy](#activity-retries). + +## Failing Workflows {#workflow-failure} + +One of the core design principles of Temporal is that an Activity Failure will never directly cause a Workflow Failure — a Workflow should never return as Failed unless deliberately. +The default retry policy associated with Temporal Activities is to retry them until reaching a certain timeout threshold. +Activities will not actually *return* a failure to your Workflow until this condition or another non-retryable condition is met. +At this point, you can decide how to handle an error returned by your Activity the way you would in any other program. +For example, you could implement a [Saga Pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/Saga) that uses `try`/`catch` blocks to "unwind" some of the steps your Workflow has performed up to the point of Activity Failure. + +**You will only fail a Workflow by manually raising an `ApplicationFailureException` from the Workflow code.** +You could do this in response to an Activity Failure, if the failure of that Activity means that your Workflow should not continue: + +```csharp +try +{ + await Workflow.ExecuteActivityAsync( + (Activities act) => act.ValidateCreditCardAsync(order.Customer.CreditCardNumber), + options); +} +catch (ActivityFailureException err) +{ + logger.LogError("Unable to process credit card: {Message}", err.Message); + throw new ApplicationFailureException(message: "Invalid credit card number error"); +} +``` + +This works differently in a Workflow than raising exceptions from Activities. +In an Activity, any C# exceptions or custom exceptions are converted to a Temporal `ApplicationError`. +In a Workflow, any exceptions that are raised other than an explicit Temporal `ApplicationError` will only fail that particular [Workflow Task](https://docs.temporal.io/tasks#workflow-task-execution) and be retried. +This includes any typical C# `RuntimeError`s that are raised automatically. +These errors are treated as bugs that can be corrected with a fixed deployment, rather than a reason for a Temporal Workflow Execution to return unexpectedly. diff --git a/docs/develop/dotnet/best-practices/index.mdx b/docs/develop/dotnet/best-practices/index.mdx new file mode 100644 index 0000000000..db9678a391 --- /dev/null +++ b/docs/develop/dotnet/best-practices/index.mdx @@ -0,0 +1,23 @@ +--- +id: index +title: Best Practices - .NET SDK +sidebar_label: Best Practices +description: This section explains how to implement Best Practices with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Best practices + +- [Error handling](/develop/dotnet/best-practices/error-handling) +- [Testing](/develop/dotnet/best-practices/testing-suite) +- [Debugging](/develop/dotnet/best-practices/debugging) +- [Converters and encryption](/develop/dotnet/best-practices/converters-and-encryption) \ No newline at end of file diff --git a/docs/develop/dotnet/testing-suite.mdx b/docs/develop/dotnet/best-practices/testing-suite.mdx similarity index 100% rename from docs/develop/dotnet/testing-suite.mdx rename to docs/develop/dotnet/best-practices/testing-suite.mdx diff --git a/docs/develop/dotnet/client/index.mdx b/docs/develop/dotnet/client/index.mdx new file mode 100644 index 0000000000..ba1b545fa7 --- /dev/null +++ b/docs/develop/dotnet/client/index.mdx @@ -0,0 +1,21 @@ +--- +id: index +title: Client - .NET SDK +sidebar_label: Client +description: This section explains how to implement the Temporal Client with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Temporal Client + +- [Temporal Client](/develop/dotnet/client/temporal-client) +- [Namespaces](/develop/dotnet/client/namespaces) \ No newline at end of file diff --git a/docs/develop/dotnet/client/namespaces.mdx b/docs/develop/dotnet/client/namespaces.mdx new file mode 100644 index 0000000000..906bc63729 --- /dev/null +++ b/docs/develop/dotnet/client/namespaces.mdx @@ -0,0 +1,13 @@ +--- +id: namespaces +title: Namespaces - .NET SDK +sidebar_label: Namespaces +toc_max_heading_level: 4 +keywords: + - namespaces +tags: + - Namespaces + - .NET SDK + - Temporal SDKs +description: Register, update, deprecate, and delete Namespaces using Temporal CLI or SDK APIs. Manage Workflow Executions with isolated Namespaces to match your needs. +--- \ No newline at end of file diff --git a/docs/develop/dotnet/temporal-client.mdx b/docs/develop/dotnet/client/temporal-client.mdx similarity index 99% rename from docs/develop/dotnet/temporal-client.mdx rename to docs/develop/dotnet/client/temporal-client.mdx index 3d36a510cf..a66735bcc4 100644 --- a/docs/develop/dotnet/temporal-client.mdx +++ b/docs/develop/dotnet/client/temporal-client.mdx @@ -53,7 +53,7 @@ configure multiple profiles, each with its own set of connection options. You ca creating the Temporal Client. You can use the environment variable `TEMPORAL_CONFIG_FILE` to specify the location of the TOML file or provide the path to the file directly in code. If you don't provide the configuration file path, the SDK looks for it at the path `~/.config/temporalio/temporal.toml` or the equivalent on your OS. Refer to -[Environment Configuration](../environment-configuration.mdx#configuration-methods) for more details about configuration +[Environment Configuration](/references/client-environment-configuration) for more details about configuration files and profiles. :::info diff --git a/docs/develop/dotnet/core-application.mdx b/docs/develop/dotnet/core-application.mdx deleted file mode 100644 index 9643bddfc1..0000000000 --- a/docs/develop/dotnet/core-application.mdx +++ /dev/null @@ -1,429 +0,0 @@ ---- -id: core-application -title: Core application - .NET SDK -sidebar_label: Core application -description: Develop a basic Workflow and Activity Definition using the Temporal .NET SDK, run a Worker Process, and set up Dynamic Workflows and Activities. -toc_max_heading_level: 4 -keywords: - - activity - - activity definition - - activity execution - - dotnet - - dotnet sdk - - worker - - workflow - - workflow execution - - workflow parameters - - workflow return values -tags: - - Activities - - .Net SDK - - Temporal SDKs ---- - -This page shows how to do the following: - -- [Develop a basic Workflow Definition](#develop-workflow) -- [Develop a basic Activity Definition](#develop-activity) -- [Start an Activity from a Workflow](#activity-execution) -- [Run a Worker Process](#run-worker-process) -- [Set a Dynamic Workflow](#set-a-dynamic-workflow) -- [Set a Dynamic Activity](#set-a-dynamic-activity) - -## Develop a Workflow {#develop-workflow} - -**How to develop a basic Workflow using the Temporal .NET SDK** - -Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a [Workflow Definition](/workflow-definition). - -In the Temporal .NET SDK programming model, Workflows are defined as classes. - -Specify the `[Workflow]` attribute from the `Temporalio.Workflows` namespace on the Workflow class to identify a Workflow. - -Use the `[WorkflowRun]` attribute to mark the entry point method to be invoked. This must be set on one asynchronous method defined on the same class as `[Workflow]`. - -```csharp -using Temporalio.Workflows; - -[Workflow] -public class MyWorkflow -{ - [WorkflowRun] - public async Task RunAsync(string name) - { - var param = MyActivityParams("Hello", name); - return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } -} -``` - -Temporal Workflows may have any number of custom parameters. -However, we strongly recommend that objects are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. -All Workflow Definition parameters must be serializable. - -### Workflow logic requirements {#workflow-logic-requirements} - -Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints). -Therefore, each language is limited to the use of certain idiomatic techniques. -However, each Temporal SDK provides a set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code. - -This means there are several things Workflows cannot do such as: - -- Perform IO (network, disk, stdio, etc) -- Access/alter external mutable state -- Do any threading -- Do anything using the system clock (e.g. `DateTime.Now`) - - This includes .NET timers (e.g. `Task.Delay` or `Thread.Sleep`) -- Make any random calls -- Make any not-guaranteed-deterministic calls (e.g. iterating over a dictionary) - -#### .NET Task Determinism - -Some calls in .NET do unsuspecting non-deterministic things and are easy to accidentally use. -This is especially true with `Task`s. -Temporal requires that the deterministic `TaskScheduler.Current` is used, but many .NET async calls will use `TaskScheduler.Default` implicitly (and some analyzers even encourage this). -Here are some known gotchas to avoid with .NET tasks inside of Workflows: - -- Do not use `Task.Run` - this uses the default scheduler and puts work on the thread pool. - - Use `Workflow.RunTaskAsync` instead. - - Can also use `Task.Factory.StartNew` with current scheduler or instantiate the `Task` and run `Task.Start` on it. -- Do not use `Task.ConfigureAwait(false)` - this will not use the current context. - - If you must use `Task.ConfigureAwait`, use `Task.ConfigureAwait(true)`. - - There is no significant performance benefit to `Task.ConfigureAwait` in workflows anyways due to how the scheduler works. -- Do not use anything that defaults to the default task scheduler. -- Do not use `Task.Delay`, `Task.Wait`, timeout-based `CancellationTokenSource`, or anything that uses .NET built-in timers. - - `Workflow.DelayAsync`, `Workflow.WaitConditionAsync`, or non-timeout-based cancellation token source is suggested. -- Do not use `Task.WhenAny`. - - Use `Workflow.WhenAnyAsync` instead. - - Technically this only applies to an enumerable set of tasks with results or more than 2 tasks with results. Other - uses are safe. See [this issue](https://github.com/dotnet/runtime/issues/87481). -- Do not use `Task.WhenAll` - - Use `Workflow.WhenAllAsync` instead. - - Technically `Task.WhenAll` is currently deterministic in .NET and safe, but it is better to use the wrapper to be - sure. -- Do not use `CancellationTokenSource.CancelAsync`. - - Use `CancellationTokenSource.Cancel` instead. -- Do not use `System.Threading.Semaphore` or `System.Threading.SemaphoreSlim` or `System.Threading.Mutex`. - - Use `Temporalio.Workflows.Semaphore` or `Temporalio.Workflows.Mutex` instead. - - _Technically_ `SemaphoreSlim` does work if only the async form of `WaitAsync` is used without no timeouts and - `Release` is used. But anything else can deadlock the workflow and its use is cumbersome since it must be disposed. -- Be wary of additional libraries' implicit use of the default scheduler. - - For example, while there are articles for `Dataflow` about [using a specific scheduler](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block), there are hidden implicit uses of `TaskScheduler.Default`. For example, see [this bug](https://github.com/dotnet/runtime/issues/83159). - -In order to help catch wrong scheduler use, by default the Temporal .NET SDK adds an event source listener for info-level task events. -While this technically receives events from all uses of tasks in the process, we make sure to ignore anything that is not running in a Workflow in a high performant way (basically one thread local check). -For code that does run in a Workflow and accidentally starts a task in another scheduler, an `InvalidWorkflowOperationException` will be thrown which "pauses" the Workflow (fails the Workflow Rask which continually retries until the code is fixed). -This is unfortunately a runtime-only check, but can help catch mistakes early. If this needs to be turned off for any reason, set `DisableWorkflowTracingEventListener` to `true` in Worker options. - -In the near future for modern .NET versions we hope to use the -[new `TimeProvider` API](https://github.com/dotnet/runtime/issues/36617) which will allow us to control current time and -timers. - -#### Workflow .editorconfig - -Since Workflow code follows some different logic rules than regular C# code, there are some common analyzer rules that developers may want to disable. -To ensure these are only disabled for Workflows, current recommendation is to use the `.workflow.cs` extension for files containing Workflows. - -Here are the rules to disable: - -- [CA1024](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1024) - This encourages properties instead of methods that look like getters. However for reflection reasons we cannot use property getters for queries, so it is very normal to have - - ```csharp - [WorkflowQuery] - public string GetSomeThing() => someThing; - ``` - -- [CA1822](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822) - This encourages static methods when methods don't access instance state. Workflows however use instance methods for run, Signals, Queries, or Updates even if they could be static. -- [CA2007](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2007) - This encourages users to use `ConfigureAwait` instead of directly waiting on a task. But in Workflows, there is no benefit to this and it just adds noise (and if used, needs to be `ConfigureAwait(true)` not `ConfigureAwait(false)`). -- [CA2008](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008) - This encourages users to always apply an explicit task scheduler because the default of `TaskScheduler.Current` is bad. But for Workflows, the default of `TaskScheduler.Current` is good/required. -- [CA5394](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5394) - This discourages use of non-crypto random. But deterministic Workflows, via `Workflow.Random` intentionally provide a deterministic non-crypto random instance. -- `CS1998` - This discourages use of `async` on async methods that don't `await`. But Workflows handlers like Signals are often easier to write in one-line form this way, e.g. `public async Task SignalSomethingAsync(string value) => this.value = value;`. -- [VSTHRD105](https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD105.md) - This is similar to `CA2008` above in that use of implicit current scheduler is discouraged. That does not apply to Workflows where it is encouraged/required. - -Here is the `.editorconfig` snippet for the above which may frequently change as more analyzers need to be adjusted: - -```ini -##### Configuration specific for Temporal workflows ##### -[*.workflow.cs] - -# We use getters for queries, they cannot be properties -dotnet_diagnostic.CA1024.severity = none - -# Don't force workflows to have static methods -dotnet_diagnostic.CA1822.severity = none - -# Do not need ConfigureAwait for workflows -dotnet_diagnostic.CA2007.severity = none - -# Do not need task scheduler for workflows -dotnet_diagnostic.CA2008.severity = none - -# Workflow randomness is intentionally deterministic -dotnet_diagnostic.CA5394.severity = none - -# Allow async methods to not have await in them -dotnet_diagnostic.CS1998.severity = none - -# Don't avoid, but rather encourage things using TaskScheduler.Current in workflows -dotnet_diagnostic.VSTHRD105.severity = none -``` - -### Customize Workflow Type {#workflow-type} - -**How to customize your Workflow Type name using the Temporal .NET SDK** - -Workflows have a Type that are referred to as the Workflow name. - -The following examples demonstrate how to set a custom name for your Workflow Type. - -You can customize the Workflow name with a custom name in the attribute. For example, `[Workflow("my-workflow-name")]`. If the name parameter is not specified, the Workflow name defaults to the unqualified class name. - -```csharp -using Temporalio.Workflows; - -[Workflow("MyDifferentWorkflowName")] -public class MyWorkflow -{ - public async Task RunAsync(string name) - { - var param = MyActivityParams("Hello", name); - return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } -} -``` - -## Develop an Activity {#develop-activity} - -**How to develop a basic Activity using the Temporal .NET SDK** - -One of the primary things that Workflows do is orchestrate the execution of Activities. -An Activity is a normal method execution that's intended to execute a single, well-defined action (either short or long-running), such as querying a database, calling a third-party API, or transcoding a media file. -An Activity can interact with world outside the Temporal Platform or use a Temporal Client to interact with a Temporal Service. -For the Workflow to be able to execute the Activity, we must define the [Activity Definition](/activity-definition). - -You can develop an Activity Definition by using the `[Activity]` attribute from the `Temporalio.Activities` namespace on the method. -To register a method as an Activity with a custom name, use an attribute parameter, for example `[Activity("your-activity")]`. -Otherwise, the activity name is the unqualified method name (sans an "Async" suffix if the method is async). - -Activities can be asynchronous or synchronous. - -```csharp -using Temporalio.Activities; - -public class MyActivities -{ - // Activities can be async and/or static too. We just demonstrate instance methods since many - // use them that way. - [Activity] - public string MyActivity(MyActivityParams input) => - $"{input.Greeting}, {input.Name}!"; -} -``` - -There is no explicit limit to the total number of parameters that an [Activity Definition](/activity-definition) may support. -However, there is a limit to the total size of the data that ends up encoded into a gRPC message Payload. - -A single argument is limited to a maximum size of 2 MB. -And the total size of a gRPC message, which includes all the arguments, is limited to a maximum of 4 MB. - -Also, keep in mind that all Payload data is recorded in the [Workflow Execution Event History](/workflow-execution/event#event-history) and large Event Histories can affect Worker performance. -This is because the entire Event History could be transferred to a Worker Process with a [Workflow Task](/tasks#workflow-task). - -Some SDKs require that you pass context objects, others do not. -When it comes to your application data—that is, data that is serialized and encoded into a Payload—we recommend that you use a single object as an argument that wraps the application data passed to Activities. -This is so that you can change what data is passed to the Activity without breaking a method signature. - -Activity parameters are the method parameters of the method with the `[Activity]` attribute. -These can be any data type Temporal can convert, including records. -Technically this can be multiple parameters, but Temporal strongly encourages a single parameter containing all input fields. - -## Start Activity Execution {#activity-execution} - -**How to start an Activity Execution using the Temporal .NET SDK** - -Calls to spawn [Activity Executions](/activity-execution) are written within a [Workflow Definition](/workflow-definition). -The call to spawn an Activity Execution generates the [ScheduleActivityTask](/references/commands#scheduleactivitytask) Command. -This results in the set of three [Activity Task](/tasks#activity-task) related Events ([ActivityTaskScheduled](/references/events#activitytaskscheduled), [ActivityTaskStarted](/references/events#activitytaskstarted), and ActivityTask[Closed])in your Workflow Execution Event History. - -A single instance of the Activities implementation is shared across multiple simultaneous Activity invocations. -Activity implementation code should be _idempotent_. - -The values passed to Activities through invocation parameters or returned through a result value are recorded in the Execution history. -The entire Execution history is transferred from the Temporal service to Workflow Workers when a Workflow state needs to recover. -A large Execution history can thus adversely impact the performance of your Workflow. - -Therefore, be mindful of the amount of data you transfer through Activity invocation parameters or Return Values. -Otherwise, no additional limitations exist on Activity implementations. - -To spawn an Activity Execution, use the `ExecuteActivityAsync` operation from within your Workflow Definition. - -```csharp -using Temporalio.Workflows; - -[Workflow] -public class MyWorkflow -{ - public async Task RunAsync(string name) - { - var param = MyActivityParams("Hello", name); - return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } -} -``` - -Activity Execution semantics rely on several parameters. -The only required value that needs to be set is either a [Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout) or a [Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout). -These values are set in the Activity Options. - -### Get Activity Execution results {#get-activity-results} - -**How to get the results of an Activity Execution using the Temporal .NET SDK** - -The Activity result is the returned in the task from the `ExecuteActivityAsync` call. - -## Run Worker Process - -**How to create and run a Worker Process using the Temporal .NET SDK** - -The [Worker Process](/workers#worker-process) is where Workflow Functions and Activity Functions are executed. - -- Each [Worker Entity](/workers#worker-entity) in the Worker Process must register the exact Workflow Types and Activity Types it may execute. -- Each Worker Entity must also associate itself with exactly one [Task Queue](/task-queue). -- Each Worker Entity polling the same Task Queue must be registered with the same Workflow Types and Activity Types. - -A [Worker Entity](/workers#worker-entity) is the component within a Worker Process that listens to a specific Task Queue. - -Although multiple Worker Entities can be in a single Worker Process, a single Worker Entity Worker Process may be perfectly sufficient. -For more information, see the [Worker tuning guide](/develop/worker-performance). - -A Worker Entity contains a Workflow Worker and/or an Activity Worker, which makes progress on Workflow Executions and Activity Executions, respectively. - -To develop a Worker, create a new `Temporalio.Worker.TemporalWorker` providing the Client and worker options which include Task Queue, Workflows, and Activities and more. -The following code example creates a Worker that polls for tasks from the Task Queue and executes the Workflow. -When a Worker is created, it accepts a list of Workflows, a list of Activities, or both. - -```csharp -// Create a client to localhost on default namespace -var client = await TemporalClient.ConnectAsync(new("localhost:7233") -{ - LoggerFactory = LoggerFactory.Create(builder => - builder. - AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). - SetMinimumLevel(LogLevel.Information)), -}); - -// Cancellation token cancelled on ctrl+c -using var tokenSource = new CancellationTokenSource(); -Console.CancelKeyPress += (_, eventArgs) => -{ - tokenSource.Cancel(); - eventArgs.Cancel = true; -}; - -// Create an activity instance with some state -var activities = new MyActivities(); - -// Run worker until cancelled -Console.WriteLine("Running worker"); -using var worker = new TemporalWorker( - client, - new TemporalWorkerOptions(taskQueue: "my-task-queue"). - AddAllActivities(activities). - AddWorkflow()); -try -{ - await worker.ExecuteAsync(tokenSource.Token); -} -catch (OperationCanceledException) -{ - Console.WriteLine("Worker cancelled"); -} -``` - -All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types. - -If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. -However, the failure of the Task does not cause the associated Workflow Execution to fail. - -### Worker Processes with host builder and dependency injection - -The [Temporalio.Extensions.Hosting](https://github.com/temporalio/sdk-dotnet/tree/main/src/Temporalio.Extensions.Hosting) extension exists for .NET developers to support HostBuilder and Dependency Injection approaches. - -To create the same worker as before using this approach: - -```csharp -var host = Host.CreateDefaultBuilder(args) - .ConfigureLogging(ctx => ctx.AddSimpleConsole().SetMinimumLevel(LogLevel.Information)) - .ConfigureServices(ctx => - ctx. - // Add the database client at the scoped level - AddScoped(). - // Add the worker - AddHostedTemporalWorker( - clientTargetHost: "localhost:7233", - clientNamespace: "default", - taskQueue: "my-task-queue"). - // Add the activities class at the scoped level - AddScopedActivities(). - AddWorkflow()) - .Build(); -await host.RunAsync(); -``` - -## Set a Dynamic Workflow {#set-a-dynamic-workflow} - -**How to set a Dynamic Workflow using the Temporal .NET SDK** - -A Dynamic Workflow in Temporal is a Workflow that is invoked dynamically at runtime if no other Workflow with the same name is registered. -A Workflow can be made dynamic by setting `Dynamic` as `true` on the `[Workflow]` attribute. -You must register the Workflow with the Worker before it can be invoked. -Only one Dynamic Workflow can be present on a Worker. - -The Workflow Definition must then accept a single argument of type `Temporalio.Converters.IRawValue[]`. -The [Workflow.PayloadConverter](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_PayloadConverter) property is used to convert an `IRawValue` object to the desired type using extension methods in the `Temporalio.Converters` namespace. - -```csharp -[Workflow(Dynamic = true)] -public class DynamicWorkflow -{ - [WorkflowRun] - public async Task RunAsync(IRawValue[] args) - { - var name = Workflow.PayloadConverter.ToValue(args.Single()); - var param = MyActivityParams("Hello", name); - return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); - } -} -``` - -## Set a Dynamic Activity {#set-a-dynamic-activity} - -**How to set a Dynamic Activity using the Temporal .NET SDK** - -A Dynamic Activity in Temporal is an Activity that is invoked dynamically at runtime if no other Activity with the same name is registered. -An Activity can be made dynamic by setting `Dynamic` as `true` on the `[Activity]` attribute. -You must register the Activity with the Worker before it can be invoked. -Only one Dynamic Activity can be present on a Worker. - -The Activity Definition must then accept a single argument of type `Temporalio.Converters.IRawValue[]`. -The [PayloadConverter](https://dotnet.temporal.io/api/Temporalio.Activities.ActivityExecutionContext.html#Temporalio_Activities_ActivityExecutionContext_PayloadConverter) property on the `ActivityExecutionContext` is used to convert an `IRawValue` object to the desired type using extension methods in the `Temporalio.Converters` namespace. - -```csharp -public class MyActivities -{ - [Activity(Dynamic = true)] - public string DynamicActivity(IRawValue[] args) - { - var input = ActivityExecutionContext.Current.PayloadConverter.ToValue(args.Single()); - return $"{input.Greeting}, {input.Name}!"; - } -} -``` diff --git a/docs/develop/dotnet/failure-detection.mdx b/docs/develop/dotnet/failure-detection.mdx deleted file mode 100644 index fc0a12f491..0000000000 --- a/docs/develop/dotnet/failure-detection.mdx +++ /dev/null @@ -1,282 +0,0 @@ ---- -id: failure-detection -title: Failure detection - .NET SDK -sidebar_label: Failure detection -description: Optimize Workflow Execution with Temporal .Net SDK - Set Timeouts, Retry Policies, and manage Activity Heartbeats efficiently. -toc_max_heading_level: 4 -keywords: - - guide context - - how to - - dotnet - - sdk - - failure detection -tags: - - Activities - - Workflows - - .Net SDK - - Temporal SDKs - - Failures - - Errors ---- - -This page shows how to do the following: - -- [Raise and Handle Exceptions](#exception-handling) -- [Deliberately Fail Workflows](#workflow-failure) -- [Set Workflow timeouts](#workflow-timeouts) -- [Set Workflow retries](#workflow-retries) -- [Set Activity timeouts](#activity-timeouts) -- [Set Activity Retry Policy](#activity-retries) -- [Heartbeat an Activity](#activity-heartbeats) -- [Set Heartbeat timeouts](#heartbeat-timeout) - -## Raise and Handle Exceptions {#exception-handling} - -In each Temporal SDK, error handling is implemented idiomatically, following the conventions of the language. -Temporal uses several different error classes internally — for example, [`CancelledFailureException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.CanceledFailureException.html) in the .NET SDK, to handle a Workflow cancellation. -You should not raise or otherwise implement these manually, as they are tied to Temporal platform logic. - -The one Temporal error class that you will typically raise deliberately is [`ApplicationFailureException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.ApplicationFailureException.html). -In fact, *any* other exceptions that are raised from your C# code in a Temporal Activity will be converted to an `ApplicationFailureException` internally. -This way, an error's type, severity, and any additional details can be sent to the Temporal Service, indexed by the Web UI, and even serialized across language boundaries. - -In other words, these two code samples do the same thing: - -```csharp -[Serializable] -public class InvalidDepartmentException : Exception -{ - public InvalidDepartmentException() : base() { } - public InvalidDepartmentException(string message) : base(message) { } - public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { } -} - -[Activity] -public Task SendBillAsync(Bill bill) -{ - throw new InvalidDepartmentException("Invalid department"); -} -``` - -```csharp -[Activity] -public Task SendBillAsync(Bill bill) -{ - throw new ApplicationFailureException("Invalid department", errorType: "InvalidDepartmentException"); -} -``` - -Depending on your implementation, you may decide to use either method. -One reason to use the Temporal `ApplicationFailureException` class is because it allows you to set an additional `non_retryable` parameter. -This way, you can decide whether an error should not be retried automatically by Temporal. -This can be useful for deliberately failing a Workflow due to bad input data, rather than waiting for a timeout to elapse: - -```csharp -[Activity] -public Task SendBillAsync(Bill bill) -{ - throw new ApplicationFailureException("Invalid department", nonRetryable: true); -} -``` - -You can alternately specify a list of errors that are non-retryable in your Activity [Retry Policy](#activity-retries). - -## Failing Workflows {#workflow-failure} - -One of the core design principles of Temporal is that an Activity Failure will never directly cause a Workflow Failure — a Workflow should never return as Failed unless deliberately. -The default retry policy associated with Temporal Activities is to retry them until reaching a certain timeout threshold. -Activities will not actually *return* a failure to your Workflow until this condition or another non-retryable condition is met. -At this point, you can decide how to handle an error returned by your Activity the way you would in any other program. -For example, you could implement a [Saga Pattern](https://github.com/temporalio/samples-dotnet/tree/main/src/Saga) that uses `try`/`catch` blocks to "unwind" some of the steps your Workflow has performed up to the point of Activity Failure. - -**You will only fail a Workflow by manually raising an `ApplicationFailureException` from the Workflow code.** -You could do this in response to an Activity Failure, if the failure of that Activity means that your Workflow should not continue: - -```csharp -try -{ - await Workflow.ExecuteActivityAsync( - (Activities act) => act.ValidateCreditCardAsync(order.Customer.CreditCardNumber), - options); -} -catch (ActivityFailureException err) -{ - logger.LogError("Unable to process credit card: {Message}", err.Message); - throw new ApplicationFailureException(message: "Invalid credit card number error"); -} -``` - -This works differently in a Workflow than raising exceptions from Activities. -In an Activity, any C# exceptions or custom exceptions are converted to a Temporal `ApplicationError`. -In a Workflow, any exceptions that are raised other than an explicit Temporal `ApplicationError` will only fail that particular [Workflow Task](https://docs.temporal.io/tasks#workflow-task-execution) and be retried. -This includes any typical C# `RuntimeError`s that are raised automatically. -These errors are treated as bugs that can be corrected with a fixed deployment, rather than a reason for a Temporal Workflow Execution to return unexpectedly. - -## Workflow timeouts {#workflow-timeouts} - -**How to set Workflow timeouts using the Temporal .NET SDK** - -Each Workflow timeout controls the maximum duration of a different aspect of a Workflow Execution. - -Workflow timeouts are set when [starting the Workflow Execution](#workflow-timeouts). - -- **[Workflow Execution Timeout](/encyclopedia/detecting-workflow-failures#workflow-execution-timeout)** - restricts the maximum amount of time that a single Workflow Execution can be executed. -- **[Workflow Run Timeout](/encyclopedia/detecting-workflow-failures#workflow-run-timeout):** restricts the maximum amount of time that a single Workflow Run can last. -- **[Workflow Task Timeout](/encyclopedia/detecting-workflow-failures#workflow-task-timeout):** restricts the maximum amount of time that a Worker can execute a Workflow Task. - -These values can be set in the `WorkflowOptions` when calling `StartWorkflowAsync` or `ExecuteWorkflowAsync`. - -Available timeouts are: - -- ExecutionTimeout -- RunTimeout -- TaskTimeout - -```csharp -var result = await client.ExecuteWorkflowAsync( - (MyWorkflow wf) => wf.RunAsync(), - new(id: "my-workflow-id", taskQueue: "my-task-queue") - { - WorkflowExecutionTimeout = TimeSpan.FromMinutes(5), - }); -``` - -### Set Workflow retries {#workflow-retries} - -**How to set Workflow retries using the Temporal .NET SDK** - -A Retry Policy can work in cooperation with the timeouts to provide fine controls to optimize the execution experience. - -Use a [Retry Policy](/encyclopedia/retry-policies) to retry a Workflow Execution in the event of a failure. - -Workflow Executions do not retry by default, and Retry Policies should be used with Workflow Executions only in certain situations. - -The `RetryPolicy` can be set in the `WorkflowOptions` when calling `StartWorkflowAsync` or `ExecuteWorkflowAsync`. - -```csharp -var result = await client.ExecuteWorkflowAsync( - (MyWorkflow wf) => wf.RunAsync(), - new(id: "my-workflow-id", taskQueue: "my-task-queue") - { - RetryPolicy = new() { MaximumInterval = TimeSpan.FromSeconds(10) }, - }); -``` - -## Activity Timeouts {#activity-timeouts} - -**How to set Activity Timeouts using the Temporal .NET SDK** - -Each Activity Timeout controls the maximum duration of a different aspect of an Activity Execution. - -The following Timeouts are available in the Activity Options. - -- **[Schedule-To-Close Timeout](/encyclopedia/detecting-activity-failures#schedule-to-close-timeout):** is the maximum amount of time allowed for the overall [Activity Execution](/activity-execution). -- **[Start-To-Close Timeout](/encyclopedia/detecting-activity-failures#start-to-close-timeout):** is the maximum time allowed for a single [Activity Task Execution](/tasks#activity-task-execution). -- **[Schedule-To-Start Timeout](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout):** is the maximum amount of time that is allowed from when an [Activity Task](/tasks#activity-task) is scheduled to when a [Worker](/workers#worker) starts that Activity Task. - -An Activity Execution must have either the Start-To-Close or the Schedule-To-Close Timeout set. - -These values can be set in the `ActivityOptions` when calling `ExecuteActivityAsync`. - -Available timeouts are: - -- ScheduleToCloseTimeout -- ScheduleToStartTimeout -- StartToCloseTimeout - -```csharp -return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); -``` - -### Set an Activity Retry Policy {#activity-retries} - -**How to Set an Activity Retry Policy using the Temporal .NET SDK** - -A Retry Policy works in cooperation with the timeouts to provide fine controls to optimize the execution experience. - -Activity Executions are automatically associated with a default [Retry Policy](/encyclopedia/retry-policies) if a custom one is not provided. - -To create an Activity Retry Policy in .NET, set the `RetryPolicy` on the `ActivityOptions` when calling `ExecuteActivityAsync`. - -```csharp -return await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() - { - StartToCloseTimeout = TimeSpan.FromMinutes(5), - RetryPolicy = new() { MaximumInterval = TimeSpan.FromSeconds(10) }, - }); -``` - -### Override the Retry interval with `nextRetryDelay` {#next-retry-delay} - -When you throw an [Application Failure](/references/failures#application-failure) and assign the `nextRetryDelay` field, its value replaces and overrides the Retry interval defined in the active Retry Policy. - -For example, you might scale the next Retry delay interval based on the current number of attempts. -Here's how you'd do that in an Activity. -In the following sample, the `attempt` count is retrieved from the Activity Execution context and used to set the number of seconds for the next Retry delay: - -```csharp -var attempt = ActivityExecutionContext.Current.Info.Attempt; - -throw new ApplicationFailureException( - $"Something bad happened on attempt {attempt}", - errorType: "my_failure_type", - nextRetryDelay: TimeSpan.FromSeconds(3 * attempt)); -``` - -## Heartbeat an Activity {#activity-heartbeats} - -**How to Heartbeat an Activity using the Temporal .NET SDK** - -An [Activity Heartbeat](/encyclopedia/detecting-activity-failures#activity-heartbeat) is a ping from the [Worker Process](/workers#worker-process) that is executing the Activity to the [Temporal Service](/temporal-service). -Each Heartbeat informs the Temporal Service that the [Activity Execution](/activity-execution) is making progress and the Worker has not crashed. -If the Temporal Service does not receive a Heartbeat within a [Heartbeat Timeout](/encyclopedia/detecting-activity-failures#heartbeat-timeout) time period, the Activity will be considered failed and another [Activity Task Execution](/tasks#activity-task-execution) may be scheduled according to the Retry Policy. - -Heartbeats may not always be sent to the Temporal Service—they may be [throttled](/encyclopedia/detecting-activity-failures#throttling) by the Worker. - -Activity Cancellations are delivered to Activities from the Temporal Service when they Heartbeat. Activities that don't Heartbeat can't receive a Cancellation. -Heartbeat throttling may lead to Cancellation getting delivered later than expected. - -Heartbeats can contain a `Details` field describing the Activity's current progress. -If an Activity gets retried, the Activity can access the `Details` from the last Heartbeat that was sent to the Temporal Service. - -To Heartbeat an Activity Execution in .NET, use the [`Heartbeat()`](https://dotnet.temporal.io/api/Temporalio.Activities.ActivityExecutionContext.html#Temporalio_Activities_ActivityExecutionContext_Heartbeat_System_Object___) method on the `ActivityExecutionContext`. - -```csharp -[Activity] -public async Task MyActivityAsync() -{ - while (true) - { - // Send heartbeat - ActivityExecutionContext.Current.Heartbeat(); - - // Do some work, passing the cancellation token - await Task.Delay(1000, ActivityExecutionContext.Current.CancellationToken); - } -} -``` - -In addition to obtaining cancellation information, Heartbeats also support detail data that persists on the server for retrieval during Activity retry. -If an Activity calls `Heartbeat(123, 456)` and then fails and is retried, `HeartbeatDetails` on the `ActivityInfo` returns an collection containing `123` and `456` on the next Run. - -### Set a Heartbeat Timeout {#heartbeat-timeout} - -**How to set a Heartbeat Timeout using the Temporal .NET SDK** - -A [Heartbeat Timeout](/encyclopedia/detecting-activity-failures#heartbeat-timeout) works in conjunction with [Activity Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat). - -`HeartbeatTimeout` is a property on `ActivityOptions` for `ExecuteActivityAsync` used to set the maximum time between Activity Heartbeats. - -```csharp -await Workflow.ExecuteActivityAsync( - (MyActivities a) => a.MyActivity(param), - new() - { - StartToCloseTimeout = TimeSpan.FromMinutes(5), - HeartbeatTimeout = TimeSpan.FromSeconds(30), - }); -``` diff --git a/docs/develop/dotnet/index.mdx b/docs/develop/dotnet/index.mdx index c208c4e306..b0ec80d172 100644 --- a/docs/develop/dotnet/index.mdx +++ b/docs/develop/dotnet/index.mdx @@ -16,237 +16,76 @@ tags: import * as Components from '@site/src/components'; - +![.NET](/img/assets/banner-dotnet-temporal.png) -![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) +## Install and get started -:::info .NET SPECIFIC RESOURCES +You can find detailed installation instructions for the .NET SDK in the [Quickstart](/develop/dotnet/set-up-your-local-dotnet). -Build Temporal Applications with the .NET SDK. +There's also a short walkthrough of how to use the Temporal primitives (Activities, Workflows, and Workers) to build and run a Temporal application to get you up and running. -**Temporal .NET Technical Resources:** +Once your local Temporal Service is set up, continue building with the following resources: -- [.NET Quickstart](https://docs.temporal.io/develop/dotnet/set-up-your-local-dotnet) -- [.NET API Documentation](https://dotnet.temporal.io/api/) -- [.NET SDK Code Samples](https://github.com/temporalio/samples-dotnet) -- [.NET SDK GitHub](https://github.com/temporalio/sdk-dotnet) -- [Temporal 101 in .NET Free Course](https://learn.temporal.io/courses/temporal_101/dotnet/) - -**Get Connected with the Temporal .NET Community:** - -- [Temporal .NET Community Slack](https://temporalio.slack.com/archives/C012SHMPDDZ) -- [.NET SDK Forum](https://community.temporal.io/tag/dotnet-sdk) - -::: - -## [Core Application](/develop/dotnet/core-application) - -Use the essential components of a Temporal Application (Workflows, Activities, and Workers) to build and run a Temporal -application. - -- [Develop a basic Workflow Definition](/develop/dotnet/core-application#develop-workflow): Workflows are the - fundamental unit of a Temporal Application, and it all starts with the development of a Workflow Definition. -- [Develop a basic Activity Definition](/develop/dotnet/core-application#develop-activity): One of the primary things - that Workflows do is orchestrate the execution of Activities. -- [Start an Activity from a Workflow](/develop/dotnet/core-application#activity-execution): Calls to spawn Activity - Executions are written within a Workflow Definition. -- [Run a Worker Process](/develop/dotnet/core-application#run-worker-process): The Worker Process is where Workflow - Functions and Activity Functions are executed. -- [Set a Dynamic Workflow](/develop/dotnet/core-application#set-a-dynamic-workflow): Set a Workflow that can be invoked - dynamically at runtime. -- [Set a Dynamic Activity](/develop/dotnet/core-application#set-a-dynamic-activity): Set an Activity that can be invoked - dynamically at runtime. - -## [Temporal Client](/develop/dotnet/temporal-client) - -Connect to a Temporal Service and start a Workflow Execution. - -- [Create a Temporal Client](/develop/dotnet/temporal-client#connect-to-development-service): Instantiate and configure - a client to interact with the Temporal Service. -- [Connect to Temporal Cloud](/develop/dotnet/temporal-client#connect-to-temporal-cloud): Securely connect to the - Temporal Cloud for a fully managed service. -- [Start a Workflow](/develop/dotnet/temporal-client#start-workflow): Initiate Workflows seamlessly via the .NET SDK. -- [Get Workflow results](/develop/dotnet/temporal-client#get-workflow-results): Retrieve and process the results of your - Workflows efficiently. - -## [Testing](/develop/dotnet/testing-suite) - -Set up the testing suite and test Workflows and Activities. - -- [Test frameworks](/develop/dotnet/testing-suite#test-frameworks): Testing provides a framework to facilitate Workflow - and integration testing. -- [Testing Workflows](/develop/dotnet/testing-suite#testing-workflows): Ensure the functionality and reliability of your - Workflows. -- [Testing Activities](/develop/dotnet/testing-suite#test-activities): Validate the execution and outcomes of your - Activities. -- [Replay test](/develop/dotnet/testing-suite#replay): Replay recreates the exact state of a Workflow Execution. - -## [Failure detection](/develop/dotnet/failure-detection) - -Explore how your application can detect failures using timeouts and automatically attempt to mitigate them with retries. - -- [Workflow timeouts](/develop/dotnet/failure-detection#workflow-timeouts): Each Workflow timeout controls the maximum - duration of a different aspect of a Workflow Execution. -- [Workflow retries](/develop/dotnet/failure-detection#workflow-retries): A Workflow Retry Policy can be used to retry a - Workflow Execution in the event of a failure. -- [Activity timeouts](/develop/dotnet/failure-detection#activity-timeouts): Each Activity timeout controls the maximum - duration of a different aspect of an Activity Execution. -- [Set an Activity Retry Policy](/develop/dotnet/failure-detection#activity-retries): Define retry logic for Activities - to handle failures. -- [Heartbeat an Activity](/develop/dotnet/failure-detection#activity-heartbeats): An Activity Heartbeat is a ping from - the Worker that is executing the Activity to the Temporal Service. -- [Heartbeat Timeout](/develop/dotnet/failure-detection#heartbeat-timeout): A Heartbeat Timeout works in conjunction - with Activity Heartbeats. - -## [Workflow message passing](/develop/dotnet/message-passing) - -Send messages to and read the state of Workflow Executions. - -### Signals - -- [Define Signal](/develop/dotnet/message-passing#signals): A Signal is a message sent to a running Workflow Execution. -- [Send a Signal from a Temporal Client](/develop/dotnet/message-passing#send-signal-from-client): Send a Signal to a - Workflow from a Temporal Client. -- [Send a Signal from a Workflow](/develop/dotnet/message-passing#send-signal-from-workflow): Send a Signal to another - Workflow from within a Workflow, this would also be called an External Signal. -- [Signal-With-Start](/develop/dotnet/message-passing#signal-with-start): Start a Workflow and send it a Signal in a - single operation used from the Client. -- [Dynamic Handler](/develop/dotnet/message-passing#dynamic-handler): Dynamic Handlers provide flexibility to handle - cases where the names of Workflows, Activities, Signals, or Queries aren't known at run time. -- [Set a Dynamic Signal](/develop/dotnet/message-passing#set-a-dynamic-signal): A Dynamic Signal in Temporal is a Signal - that is invoked dynamically at runtime if no other Signal with the same input is registered. - -### Queries - -- [Define a Query](/develop/dotnet/message-passing#queries): A Query is a synchronous operation that is used to get the - state of a Workflow Execution. -- [Send Queries](/develop/dotnet/message-passing#send-query): Queries are sent from the Temporal Client. -- [Set a Dynamic Query](/develop/dotnet/message-passing#set-a-dynamic-signal): A Dynamic Query in Temporal is a Query - that is invoked dynamically at runtime if no other Query with the same name is registered. - -### Updates - -- [Define an Update](/develop/dotnet/message-passing#updates): An Update is an operation that can mutate the state of a - Workflow Execution and return a response. -- [Send an Update](/develop/dotnet/message-passing#send-update-from-client): An Update is sent from the Temporal Client. - -## [Interrupt a Workflow](/develop/dotnet/cancellation) - -Interrupt a Workflow Execution with a Cancel or Terminate action. +- [Workflow Basics](/develop/dotnet/workflows/basics) +- [Activity Basics](/develop/dotnet/activities/basics) +- [Start an Activity Execution](/develop/dotnet/activities/execution) +- [Run Worker Processes](/develop/dotnet/workers/run-worker-process) -- [Cancel a Workflow](/develop/dotnet/cancellation#cancellation): Interrupt a Workflow Execution and its Activities - through Workflow cancellation. -- [Terminate a Workflow](/develop/dotnet/cancellation#termination): Interrupt a Workflow Execution and its Activities - through Workflow termination. -- [Reset a Workflow](/develop/dotnet/cancellation#reset): Resume a Workflow Execution from an earlier point in its Event History. +From there, you can dive deeper into any of the Temporal primitives to start building Workflows that fit your use cases. -## [Asynchronous Activity completion](/develop/dotnet/asynchronous-activity) +## [Workflows](/develop/dotnet/workflows) -Complete Activities asynchronously. +- [Workflow Basics](/develop/dotnet/workflows/basics) +- [Child Workflows](/develop/dotnet/workflows/child-workflows) +- [Continue-As-New](/develop/dotnet/workflows/continue-as-new) +- [Cancellation](/develop/dotnet/workflows/cancellation) +- [Timeouts](/develop/dotnet/workflows/timeouts) +- [Message Passing](/develop/dotnet/workflows/message-passing) +- [Enriching the UI](/develop/dotnet/workflows/enriching-ui) +- [Schedules](/develop/dotnet/workflows/schedules) +- [Timers](/develop/dotnet/workflows/timers) +- [Dynamic Workflow](/develop/dotnet/workflows/dynamic-workflow) +- [Versioning](/develop/dotnet/workflows/versioning) -- [Asynchronous Activity](/develop/dotnet/asynchronous-activity): Asynchronous Activity completion enables the Activity - Function to return without the Activity Execution completing. +## [Activities](/develop/dotnet/activities) -## [Versioning](/develop/dotnet/versioning) +- [Activity Basics](/develop/dotnet/activities/basics) +- [Activity Execution](/develop/dotnet/activities/execution) +- [Timeouts](/develop/dotnet/activities/timeouts) +- [Asynchronous Activity Completion](/develop/dotnet/activities/asynchronous-activity) +- [Dynamic Activity](/develop/dotnet/activities/dynamic-activity) +- [Benign exceptions](/develop/dotnet/activities/benign-exceptions) -Change Workflow Definitions without causing non-deterministic behavior in running Workflows. +## [Workers](/develop/dotnet/workers) -- [Use the .NET SDK Patching API](/develop/dotnet/versioning#patching): Patching Workflows using the .NET SDK. +- [Run Worker processes](/develop/dotnet/workers/run-worker-process) +- [Observability](/develop/dotnet/workers/observability) -## [Observability](/develop/dotnet/observability) +## [Temporal Client](/develop/dotnet/client) -Configure and use the Temporal Observability APIs. +- [Temporal Client](/develop/dotnet/client/temporal-client) +- [Namespaces](/develop/dotnet/client/namespaces) -- [Emit Metrics](/develop/dotnet/observability#metrics): Each Temporal SDK is capable of emitting an optional set of - metrics from either the Client or the Worker process. -- [Set up Tracing](/develop/dotnet/observability#tracing): Explains how the Go SDK supports tracing and custom context - propagation. -- [Log from a Workflow](/develop/dotnet/observability#logging): Send logs and errors to a logging service, so that when - things go wrong, you can see what happened. -- [Use Visibility APIs](/develop/dotnet/observability#visibility): The term Visibility, within the Temporal Platform, - refers to the subsystems and APIs that enable an operator to view Workflow Executions that currently exist within a - Terminal Service. +## [Temporal Nexus](/develop/dotnet/nexus) -## [Debugging](/develop/dotnet/debugging) +- [Service Handlers](/develop/dotnet/nexus/service-handler) -Explore various ways to debug your application. +## [Best practices](/develop/dotnet/best-practices) -- [Debug in a development environment](/develop/dotnet/debugging#debug-in-a-development-environment): In addition to the - normal development tools of logging and a debugger, you can also see what’s happening in your Workflow by using the - Web UI and the Temporal CLI. -- [Debug in a development production](/develop/dotnet/debugging#debug-in-a-development-production): Debug production - Workflows using the Web UI, the Temporal CLI, Replays, Tracing, or Logging. +- [Error handling](/develop/dotnet/best-practices/error-handling) +- [Testing](/develop/dotnet/best-practices/testing-suite) +- [Debugging](/develop/dotnet/best-practices/debugging) +- [Converters and encryption](/develop/dotnet/best-practices/converters-and-encryption) -## [Schedules](/develop/dotnet/schedules) +## Temporal .NET Technical Resources -Run Workflows on a schedule and delay the start of a Workflow. - -- [Schedule a Workflow](/develop/dotnet/schedules#schedule-a-workflow) - - [Create a Scheduled Workflow](/develop/dotnet/schedules#create-a-workflow): Create a new schedule for a scheduled - Workflow. - - [Backfill a Scheduled Workflow](/develop/dotnet/schedules#backfill-a-scheduled-workflow): Backfills a past time - range of actions for a scheduled Workflow. - - [Delete a Scheduled Workflow](/develop/dotnet/schedules#delete-a-scheduled-workflow): Deletes a schedule for a - scheduled Workflow. - - [Describe a Scheduled Workflow](/develop/dotnet/schedules#describe-a-scheduled-workflow): Get schedule configuration - and current state for a scheduled Workflow. - - [List a Scheduled Workflow](/develop/dotnet/schedules#list-a-scheduled-workflow): List a schedule for a scheduled - Workflow. - - [Pause a Scheduled Workflow](/develop/dotnet/schedules#pause-a-scheduled-workflow): Pause a schedule for a scheduled - Workflow. - - [Trigger a Scheduled Workflow](/develop/dotnet/schedules#trigger-a-scheduled-workflow): Triggers an immediate action - for a scheduled Workflow. - - [Update a Scheduled Workflow](/develop/dotnet/schedules#update-a-scheduled-workflow): Updates a schedule with a new - definition for a scheduled Workflow. -- [Use Start Delay](/develop/dotnet/schedules#start-delay): Start delay functionality if you need to delay the execution - of the Workflow without the need for regular launches. - -## [Data encryption](/develop/dotnet/converters-and-encryption) - -Use compression, encryption, and other data handling by implementing custom converters and codecs. - -- [Use a custom Payload Codec](/develop/dotnet/converters-and-encryption#custom-payload-codec): Create a custom - PayloadCodec implementation and define your encryption/compression and decryption/decompression logic. -- [Use a custom Payload Converter](/develop/dotnet/converters-and-encryption#custom-payload-converter): A custom data - converter can be set via the `DataConverter` option when creating a client. - -## [Durable Timers](/develop/dotnet/durable-timers) - -Use Timers to make a Workflow Execution pause or "sleep" for seconds, minutes, days, months, or years. - -- [Sleep](/develop/dotnet/durable-timers): A Timer lets a Workflow sleep for a fixed time period. - -## Temporal Nexus - -The [Temporal Nexus](/develop/dotnet/nexus) feature guide shows how to use Temporal Nexus to connect Durable Executions -within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. - -- [Create a Nexus Endpoint to route requests from caller to handler](/develop/dotnet/nexus#create-nexus-endpoint) -- [Define the Nexus Service contract](/develop/dotnet/nexus#define-nexus-service-contract) -- [Develop a Nexus Service and Operation handlers](/develop/dotnet/nexus#develop-nexus-service-operation-handlers) -- [Develop a caller Workflow that uses a Nexus Service](/develop/dotnet/nexus#develop-caller-workflow-nexus-service) -- [Make Nexus calls across Namespaces with a development Server](/develop/dotnet/nexus#nexus-calls-across-namespaces-dev-server) -- [Make Nexus calls across Namespaces in Temporal Cloud](/develop/dotnet/nexus#nexus-calls-across-namespaces-temporal-cloud) - -## [Child Workflows](/develop/dotnet/child-workflows) - -Explore how to spawn a Child Workflow Execution and handle Child Workflow Events. - -- [Start a Child Workflow Execution](/develop/dotnet/child-workflows): A Child Workflow Execution is a Workflow - Execution that is scheduled from within another Workflow using a Child Workflow API. -- [Set a Parent Close Policy](/develop/dotnet/child-workflows#parent-close-policy): A Parent Close Policy determines - what happens to a Child Workflow Execution if its Parent changes to a Closed status. - -## [Continue-As-New](/develop/dotnet/continue-as-new) - -Continue the Workflow Execution with a new Workflow Execution using the same Workflow ID. - -- [Continue-As-New](/develop/dotnet/continue-as-new): Continue-As-New enables a Workflow Execution to close successfully - and create a new Workflow Execution in a single atomic operation if the number of Events in the Event History is - becoming too large. - -## [Enriching the User Interface](/develop/dotnet/enriching-ui) +- [.NET Quickstart](/develop/dotnet/set-up-your-local-dotnet) +- [.NET API Documentation](https://dotnet.temporal.io/api/) +- [.NET SDK Code Samples](https://github.com/temporalio/samples-dotnet) +- [.NET SDK GitHub](https://github.com/temporalio/sdk-dotnet) +- [Temporal 101 in .NET Free Course](https://learn.temporal.io/courses/temporal_101/dotnet/) -Add descriptive information to workflows and events for better visibility and context in the UI. +Get Connected with the Temporal .NET Community -- [Adding Summary and Details to Workflows](/develop/dotnet/enriching-ui#adding-summary-and-details-to-workflows) +- [Temporal .NET Community Slack](https://temporalio.slack.com/archives/C012SHMPDDZ) +- [.NET SDK Forum](https://community.temporal.io/tag/dotnet-sdk) \ No newline at end of file diff --git a/docs/develop/dotnet/nexus/index.mdx b/docs/develop/dotnet/nexus/index.mdx new file mode 100644 index 0000000000..4df6a23d31 --- /dev/null +++ b/docs/develop/dotnet/nexus/index.mdx @@ -0,0 +1,20 @@ +--- +id: index +title: Nexus - .NET SDK +sidebar_label: Nexus +description: This section explains how to use Temporal Nexus with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Temporal Nexus + +- [Service Handlers](/develop/dotnet/nexus/service-handler) \ No newline at end of file diff --git a/docs/develop/dotnet/temporal-nexus.mdx b/docs/develop/dotnet/nexus/service-handler.mdx similarity index 99% rename from docs/develop/dotnet/temporal-nexus.mdx rename to docs/develop/dotnet/nexus/service-handler.mdx index f0ea419372..b7947a03e5 100644 --- a/docs/develop/dotnet/temporal-nexus.mdx +++ b/docs/develop/dotnet/nexus/service-handler.mdx @@ -1,7 +1,7 @@ --- -id: nexus -title: Temporal Nexus - .NET SDK feature guide -sidebar_label: Temporal Nexus +id: service-handler +title: Service handler - .NET SDK feature guide +sidebar_label: Service handler description: Use Temporal Nexus within the .NET SDK to connect Durable Executions within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations. toc_max_heading_level: 4 keywords: diff --git a/docs/develop/dotnet/workers/basics.mdx b/docs/develop/dotnet/workers/basics.mdx new file mode 100644 index 0000000000..e09c751027 --- /dev/null +++ b/docs/develop/dotnet/workers/basics.mdx @@ -0,0 +1,12 @@ +--- +id: basics +title: Worker Basics - .NET SDK +sidebar_label: Worker basics +description: This section explains Worker Basics with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- \ No newline at end of file diff --git a/docs/develop/dotnet/workers/index.mdx b/docs/develop/dotnet/workers/index.mdx new file mode 100644 index 0000000000..bb9d047003 --- /dev/null +++ b/docs/develop/dotnet/workers/index.mdx @@ -0,0 +1,21 @@ +--- +id: index +title: Workers - .NET SDK +sidebar_label: Workers +description: This section explains how to implement Workers with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Workers + +- [Run Worker processes](/develop/dotnet/workers/run-worker-process) +- [Observability](/develop/dotnet/workers/observability) \ No newline at end of file diff --git a/docs/develop/dotnet/observability.mdx b/docs/develop/dotnet/workers/observability.mdx similarity index 94% rename from docs/develop/dotnet/observability.mdx rename to docs/develop/dotnet/workers/observability.mdx index 042bb2961c..43b7519a16 100644 --- a/docs/develop/dotnet/observability.mdx +++ b/docs/develop/dotnet/workers/observability.mdx @@ -37,8 +37,6 @@ This includes the ways to view which [Workflow Executions](/workflow-execution) ## Emit metrics {#metrics} -**How to emit metrics using the Temporal .NET SDK** - Each Temporal SDK is capable of emitting an optional set of metrics from either the Client or the Worker process. For a complete list of metrics capable of being emitted, see the [SDK metrics reference](/references/sdk-metrics). @@ -50,8 +48,6 @@ Metrics in .NET are configured on the `Metrics` property of the `Telemetry` prop ### Set a Prometheus endpoint -**How to set a Prometheus endpoint using the .NET SDK** - The following example exposes a Prometheus endpoint on port `9000`. ```csharp @@ -67,8 +63,6 @@ var client = await Temporalio.ConnectAsync(new("localhost:7233") { Runtime = run ### Set a custom metric meter -**How to reuse the .NET metric meter using the Temporal .NET SDK** - A custom metric meter can be set on the telemetry options to handle metrics programmatically. The [Temporalio.Extensions.DiagnosticSource](https://github.com/temporalio/sdk-dotnet/tree/main/src/Temporalio.Extensions.DiagnosticSource) extension provides a custom metric meter implementation that sends all metrics to a [System.Diagnostics.Metrics.Meter](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics.meter) instance. @@ -95,8 +89,6 @@ var client = await Temporalio.ConnectAsync(new("localhost:7233") { Runtime = run ## Setup Tracing {#tracing} -**How to configure tracing using the Temporal .NET SDK** - Tracing allows you to view the call graph of a Workflow along with its Activities, Nexus Operations, and any Child Workflows. To configure OpenTelemetry tracing in .NET, use the [Temporalio.Extensions.OpenTelemetry](https://github.com/temporalio/sdk-dotnet/tree/main/src/Temporalio.Extensions.OpenTelemetry) extension. @@ -108,8 +100,6 @@ Spans are created and serialized through the server to give one trace for a Work ## Log from a Workflow {#logging} -**How to log from a Workflow to Temporal .NET SDK** - Logging enables you to record critical information during code execution. Loggers create an audit trail and capture information about your Workflow's operation. An appropriate logging level depends on your specific needs. @@ -150,14 +140,10 @@ Workflow.Logger.LogInformation("Given name: {Name}", name); ## Use Visibility APIs {#visibility} -**How to use Visibility APIs using the Temporal .NET SDK** - The term Visibility, within the Temporal Platform, refers to the subsystems and APIs that enable an operator to view Workflow Executions that currently exist within a Temporal Service. ### Use Search Attributes {#search-attributes} -**How to use Search Attributes using the Temporal .NET SDK** - The typical method of retrieving a Workflow Execution is by its Workflow Id. However, sometimes you'll want to retrieve one or more Workflow Executions based on another property. For example, imagine you want to get all Workflow Executions of a certain type that have failed within a time range, so that you can start new ones with the same arguments. @@ -186,8 +172,6 @@ The steps to using custom Search Attributes are: ### List Workflow Executions {#list-workflow-executions} -**How to list Workflow Executions using the .NET SDK** - Use the [ListWorkflowsAsync()](https://dotnet.temporal.io/api/Temporalio.Client.ITemporalClient.html#Temporalio_Client_ITemporalClient_ListWorkflowsAsync_System_String_Temporalio_Client_WorkflowListOptions_) method on the Client and pass a [List Filter](/list-filter) as an argument to filter the listed Workflows. The result is an async enumerable. @@ -200,8 +184,6 @@ await foreach (var wf in client.ListWorkflowsAsync("WorkflowType='GreetingWorkfl ### Set Custom Search Attributes {#custom-search-attributes} -**How to use custom Search Attributes using the Temporal .NET SDK** - After you've created custom Search Attributes in your Temporal Service (using `temporal operator search-attribute create`or the Cloud UI), you can set the values of the custom Search Attributes when starting a Workflow. To set custom Search Attributes, use the `TypedSearchAttributes` property on `WorkflowOptions` for `StartWorkflowAsync` or `ExecuteWorkflowAsync`. @@ -225,8 +207,6 @@ var handle = await client.StartWorkflowAsync( ### Upsert Search Attributes {#upsert-search-attributes} -**How to upsert custom Search Attributes using the Temporal .NET SDK** - You can upsert Search Attributes to add, update, or remove Search Attributes from within Workflow code. To upsert custom Search Attributes, use the [`UpsertTypedSearchAttributes()`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_UpsertTypedSearchAttributes_Temporalio_Workflows_SearchAttributeUpdate___) method with a set of updates. diff --git a/docs/develop/dotnet/workers/run-worker-process.mdx b/docs/develop/dotnet/workers/run-worker-process.mdx new file mode 100644 index 0000000000..acfef0a0af --- /dev/null +++ b/docs/develop/dotnet/workers/run-worker-process.mdx @@ -0,0 +1,101 @@ +--- +id: run-worker-process +title: Run Worker processes - .NET SDK +description: Shows how to run Worker processes with the .NET SDK +sidebar_label: Worker processes +slug: /develop/dotnet/workers/run-worker-process +toc_max_heading_level: 3 +tags: + - .NET SDK + - Temporal SDKs + - Worker +--- + +## Run Worker Process + +**How to create and run a Worker Process using the Temporal .NET SDK** + +The [Worker Process](/workers#worker-process) is where Workflow Functions and Activity Functions are executed. + +- Each [Worker Entity](/workers#worker-entity) in the Worker Process must register the exact Workflow Types and Activity Types it may execute. +- Each Worker Entity must also associate itself with exactly one [Task Queue](/task-queue). +- Each Worker Entity polling the same Task Queue must be registered with the same Workflow Types and Activity Types. + +A [Worker Entity](/workers#worker-entity) is the component within a Worker Process that listens to a specific Task Queue. + +Although multiple Worker Entities can be in a single Worker Process, a single Worker Entity Worker Process may be perfectly sufficient. +For more information, see the [Worker tuning guide](/develop/worker-performance). + +A Worker Entity contains a Workflow Worker and/or an Activity Worker, which makes progress on Workflow Executions and Activity Executions, respectively. + +To develop a Worker, create a new `Temporalio.Worker.TemporalWorker` providing the Client and worker options which include Task Queue, Workflows, and Activities and more. +The following code example creates a Worker that polls for tasks from the Task Queue and executes the Workflow. +When a Worker is created, it accepts a list of Workflows, a list of Activities, or both. + +```csharp +// Create a client to localhost on default namespace +var client = await TemporalClient.ConnectAsync(new("localhost:7233") +{ + LoggerFactory = LoggerFactory.Create(builder => + builder. + AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). + SetMinimumLevel(LogLevel.Information)), +}); + +// Cancellation token cancelled on ctrl+c +using var tokenSource = new CancellationTokenSource(); +Console.CancelKeyPress += (_, eventArgs) => +{ + tokenSource.Cancel(); + eventArgs.Cancel = true; +}; + +// Create an activity instance with some state +var activities = new MyActivities(); + +// Run worker until cancelled +Console.WriteLine("Running worker"); +using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions(taskQueue: "my-task-queue"). + AddAllActivities(activities). + AddWorkflow()); +try +{ + await worker.ExecuteAsync(tokenSource.Token); +} +catch (OperationCanceledException) +{ + Console.WriteLine("Worker cancelled"); +} +``` + +All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types. + +If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. +However, the failure of the Task does not cause the associated Workflow Execution to fail. + +### Worker Processes with host builder and dependency injection + +The [Temporalio.Extensions.Hosting](https://github.com/temporalio/sdk-dotnet/tree/main/src/Temporalio.Extensions.Hosting) extension exists for .NET developers to support HostBuilder and Dependency Injection approaches. + +To create the same worker as before using this approach: + +```csharp +var host = Host.CreateDefaultBuilder(args) + .ConfigureLogging(ctx => ctx.AddSimpleConsole().SetMinimumLevel(LogLevel.Information)) + .ConfigureServices(ctx => + ctx. + // Add the database client at the scoped level + AddScoped(). + // Add the worker + AddHostedTemporalWorker( + clientTargetHost: "localhost:7233", + clientNamespace: "default", + taskQueue: "my-task-queue"). + // Add the activities class at the scoped level + AddScopedActivities(). + AddWorkflow()) + .Build(); +await host.RunAsync(); +``` diff --git a/docs/develop/dotnet/workflows/basics.mdx b/docs/develop/dotnet/workflows/basics.mdx new file mode 100644 index 0000000000..f1996b8448 --- /dev/null +++ b/docs/develop/dotnet/workflows/basics.mdx @@ -0,0 +1,174 @@ +--- +id: basics +title: Workflow Basics - .NET SDK +sidebar_label: Workflow basics +description: This section explains Workflow Basics with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +## Develop a Workflow {#develop-workflow} + +Workflows are the fundamental unit of a Temporal Application, and it all starts with the development of a [Workflow Definition](/workflow-definition). + +In the Temporal .NET SDK programming model, Workflows are defined as classes. + +Specify the `[Workflow]` attribute from the `Temporalio.Workflows` namespace on the Workflow class to identify a Workflow. + +Use the `[WorkflowRun]` attribute to mark the entry point method to be invoked. This must be set on one asynchronous method defined on the same class as `[Workflow]`. + +```csharp +using Temporalio.Workflows; + +[Workflow] +public class MyWorkflow +{ + [WorkflowRun] + public async Task RunAsync(string name) + { + var param = MyActivityParams("Hello", name); + return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } +} +``` + +Temporal Workflows may have any number of custom parameters. +However, we strongly recommend that objects are used as parameters, so that the object's individual fields may be altered without breaking the signature of the Workflow. +All Workflow Definition parameters must be serializable. + +### Workflow logic requirements {#workflow-logic-requirements} + +Workflow logic is constrained by [deterministic execution requirements](/workflow-definition#deterministic-constraints). +Therefore, each language is limited to the use of certain idiomatic techniques. +However, each Temporal SDK provides a set of APIs that can be used inside your Workflow to interact with external (to the Workflow) application code. + +This means there are several things Workflows cannot do such as: + +- Perform IO (network, disk, stdio, etc) +- Access/alter external mutable state +- Do any threading +- Do anything using the system clock (e.g. `DateTime.Now`) + - This includes .NET timers (e.g. `Task.Delay` or `Thread.Sleep`) +- Make any random calls +- Make any not-guaranteed-deterministic calls (e.g. iterating over a dictionary) + +#### .NET Task Determinism + +Some calls in .NET do unsuspecting non-deterministic things and are easy to accidentally use. +This is especially true with `Task`s. +Temporal requires that the deterministic `TaskScheduler.Current` is used, but many .NET async calls will use `TaskScheduler.Default` implicitly (and some analyzers even encourage this). +Here are some known gotchas to avoid with .NET tasks inside of Workflows: + +- Do not use `Task.Run` - this uses the default scheduler and puts work on the thread pool. + - Use `Workflow.RunTaskAsync` instead. + - Can also use `Task.Factory.StartNew` with current scheduler or instantiate the `Task` and run `Task.Start` on it. +- Do not use `Task.ConfigureAwait(false)` - this will not use the current context. + - If you must use `Task.ConfigureAwait`, use `Task.ConfigureAwait(true)`. + - There is no significant performance benefit to `Task.ConfigureAwait` in workflows anyways due to how the scheduler works. +- Do not use anything that defaults to the default task scheduler. +- Do not use `Task.Delay`, `Task.Wait`, timeout-based `CancellationTokenSource`, or anything that uses .NET built-in timers. + - `Workflow.DelayAsync`, `Workflow.WaitConditionAsync`, or non-timeout-based cancellation token source is suggested. +- Do not use `Task.WhenAny`. + - Use `Workflow.WhenAnyAsync` instead. + - Technically this only applies to an enumerable set of tasks with results or more than 2 tasks with results. Other + uses are safe. See [this issue](https://github.com/dotnet/runtime/issues/87481). +- Do not use `Task.WhenAll` + - Use `Workflow.WhenAllAsync` instead. + - Technically `Task.WhenAll` is currently deterministic in .NET and safe, but it is better to use the wrapper to be + sure. +- Do not use `CancellationTokenSource.CancelAsync`. + - Use `CancellationTokenSource.Cancel` instead. +- Do not use `System.Threading.Semaphore` or `System.Threading.SemaphoreSlim` or `System.Threading.Mutex`. + - Use `Temporalio.Workflows.Semaphore` or `Temporalio.Workflows.Mutex` instead. + - _Technically_ `SemaphoreSlim` does work if only the async form of `WaitAsync` is used without no timeouts and + `Release` is used. But anything else can deadlock the workflow and its use is cumbersome since it must be disposed. +- Be wary of additional libraries' implicit use of the default scheduler. + - For example, while there are articles for `Dataflow` about [using a specific scheduler](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block), there are hidden implicit uses of `TaskScheduler.Default`. For example, see [this bug](https://github.com/dotnet/runtime/issues/83159). + +In order to help catch wrong scheduler use, by default the Temporal .NET SDK adds an event source listener for info-level task events. +While this technically receives events from all uses of tasks in the process, we make sure to ignore anything that is not running in a Workflow in a high performant way (basically one thread local check). +For code that does run in a Workflow and accidentally starts a task in another scheduler, an `InvalidWorkflowOperationException` will be thrown which "pauses" the Workflow (fails the Workflow Rask which continually retries until the code is fixed). +This is unfortunately a runtime-only check, but can help catch mistakes early. If this needs to be turned off for any reason, set `DisableWorkflowTracingEventListener` to `true` in Worker options. + +In the near future for modern .NET versions we hope to use the +[new `TimeProvider` API](https://github.com/dotnet/runtime/issues/36617) which will allow us to control current time and +timers. + +#### Workflow .editorconfig + +Since Workflow code follows some different logic rules than regular C# code, there are some common analyzer rules that developers may want to disable. +To ensure these are only disabled for Workflows, current recommendation is to use the `.workflow.cs` extension for files containing Workflows. + +Here are the rules to disable: + +- [CA1024](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1024) - This encourages properties instead of methods that look like getters. However for reflection reasons we cannot use property getters for queries, so it is very normal to have + + ```csharp + [WorkflowQuery] + public string GetSomeThing() => someThing; + ``` + +- [CA1822](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822) - This encourages static methods when methods don't access instance state. Workflows however use instance methods for run, Signals, Queries, or Updates even if they could be static. +- [CA2007](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2007) - This encourages users to use `ConfigureAwait` instead of directly waiting on a task. But in Workflows, there is no benefit to this and it just adds noise (and if used, needs to be `ConfigureAwait(true)` not `ConfigureAwait(false)`). +- [CA2008](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008) - This encourages users to always apply an explicit task scheduler because the default of `TaskScheduler.Current` is bad. But for Workflows, the default of `TaskScheduler.Current` is good/required. +- [CA5394](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca5394) - This discourages use of non-crypto random. But deterministic Workflows, via `Workflow.Random` intentionally provide a deterministic non-crypto random instance. +- `CS1998` - This discourages use of `async` on async methods that don't `await`. But Workflows handlers like Signals are often easier to write in one-line form this way, e.g. `public async Task SignalSomethingAsync(string value) => this.value = value;`. +- [VSTHRD105](https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD105.md) - This is similar to `CA2008` above in that use of implicit current scheduler is discouraged. That does not apply to Workflows where it is encouraged/required. + +Here is the `.editorconfig` snippet for the above which may frequently change as more analyzers need to be adjusted: + +```ini +##### Configuration specific for Temporal workflows ##### +[*.workflow.cs] + +# We use getters for queries, they cannot be properties +dotnet_diagnostic.CA1024.severity = none + +# Don't force workflows to have static methods +dotnet_diagnostic.CA1822.severity = none + +# Do not need ConfigureAwait for workflows +dotnet_diagnostic.CA2007.severity = none + +# Do not need task scheduler for workflows +dotnet_diagnostic.CA2008.severity = none + +# Workflow randomness is intentionally deterministic +dotnet_diagnostic.CA5394.severity = none + +# Allow async methods to not have await in them +dotnet_diagnostic.CS1998.severity = none + +# Don't avoid, but rather encourage things using TaskScheduler.Current in workflows +dotnet_diagnostic.VSTHRD105.severity = none +``` + +### Customize Workflow Type {#workflow-type} + +Workflows have a Type that are referred to as the Workflow name. + +The following examples demonstrate how to set a custom name for your Workflow Type. + +You can customize the Workflow name with a custom name in the attribute. For example, `[Workflow("my-workflow-name")]`. If the name parameter is not specified, the Workflow name defaults to the unqualified class name. + +```csharp +using Temporalio.Workflows; + +[Workflow("MyDifferentWorkflowName")] +public class MyWorkflow +{ + public async Task RunAsync(string name) + { + var param = MyActivityParams("Hello", name); + return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } +} +``` diff --git a/docs/develop/dotnet/cancellation.mdx b/docs/develop/dotnet/workflows/cancellation.mdx similarity index 93% rename from docs/develop/dotnet/cancellation.mdx rename to docs/develop/dotnet/workflows/cancellation.mdx index 512002e59f..9ca83fffa5 100644 --- a/docs/develop/dotnet/cancellation.mdx +++ b/docs/develop/dotnet/workflows/cancellation.mdx @@ -1,7 +1,7 @@ --- id: cancellation -title: Interrupt a Workflow - .NET SDK -sidebar_label: Interrupt a Workflow +title: Cancellation - .NET SDK +sidebar_label: Cancellation description: Interrupt Workflow Execution in .NET using the Temporal SDK. Cancel for graceful stops; terminate for forceful stops. Handle Cancellation in Workflow and Activities efficiently. @@ -62,8 +62,6 @@ To give a Workflow and its Activities the ability to be cancelled, do the follow ### Handle Cancellation in Workflow {#handle-cancellation-in-workflow} -**How to handle a Cancellation in a Workflow in .NET.** - Workflow Definitions can be written to respond to cancellation requests. It is common for an Activity to be run on Cancellation to perform cleanup. @@ -111,11 +109,9 @@ public async Task RunAsync() ### Handle Cancellation in an Activity {#handle-cancellation-in-an-activity} -**How to handle a Cancellation in an Activity using the Temporal .NET SDK** - -Ensure that the Activity is [Heartbeating](/develop/dotnet/failure-detection#activity-heartbeats) to receive the +Ensure that the Activity is [Heartbeating](/develop/dotnet/activities/timeouts#activity-heartbeats) to receive the Cancellation request and stop execution. Also make sure that the -[Heartbeat Timeout](/develop/dotnet/failure-detection#heartbeat-timeout) is set on the Activity Options when calling +[Heartbeat Timeout](/develop/dotnet/activities/timeouts#heartbeat-timeout) is set on the Activity Options when calling from the Workflow. An Activity Cancellation Request cancels the `CancellationToken` on the `ActivityExecutionContext`. ```csharp @@ -137,8 +133,6 @@ public async Task MyActivityAsync() ### Request Cancellation {#request-cancellation} -**How to request Cancellation of a Workflow using the Temporal .NET SDK** - Use `CancelAsync` on the `WorkflowHandle` to cancel a Workflow Execution. ```csharp @@ -151,7 +145,7 @@ var handle = myClient.GetWorkflowHandle("my-workflow-id"); await handle.CancelAsync(); ``` -**How to request Cancellation of an Activity in .NET using the Temporal .NET SDK** +#### How to request Cancellation of an Activity By default, Activities are automatically cancelled when the Workflow is cancelled since the workflow cancellation token is used by activities by default. To issue a cancellation explicitly, a new cancellation token can be created. @@ -188,8 +182,6 @@ public async Task RunAsync() ## Termination {#termination} -**How to Terminate a Workflow Execution in .NET using the Temporal .NET SDK** - To Terminate a Workflow Execution in .NET, use the [TerminateAsync()](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_TerminateAsync_System_String_Temporalio_Client_WorkflowTerminateOptions_) method on the Workflow handle. diff --git a/docs/develop/dotnet/child-workflows.mdx b/docs/develop/dotnet/workflows/child-workflows.mdx similarity index 96% rename from docs/develop/dotnet/child-workflows.mdx rename to docs/develop/dotnet/workflows/child-workflows.mdx index 13688598f7..4e49349f65 100644 --- a/docs/develop/dotnet/child-workflows.mdx +++ b/docs/develop/dotnet/workflows/child-workflows.mdx @@ -36,8 +36,6 @@ This page shows how to do the following: ## Start a Child Workflow Execution {#child-workflows} -**How to start a Child Workflow Execution using the Temporal .NET SDK** - A [Child Workflow Execution](/child-workflows) is a Workflow Execution that is scheduled from within another Workflow using a Child Workflow API. When using a Child Workflow API, Child Workflow related Events ([StartChildWorkflowExecutionInitiated](/references/events#startchildworkflowexecutioninitiated), [ChildWorkflowExecutionStarted](/references/events#childworkflowexecutionstarted), [ChildWorkflowExecutionCompleted](/references/events#childworkflowexecutioncompleted), etc...) are logged in the Workflow Execution Event History. @@ -61,8 +59,6 @@ await Workflow.ExecuteChildWorkflowAsync((MyChildWorkflow wf) => wf.RunAsync()); ## Set a Parent Close Policy {#parent-close-policy} -**How to set a Parent Close Policy using the Temporal .NET SDK** - A [Parent Close Policy](/parent-close-policy) determines what happens to a Child Workflow Execution if its Parent changes to a Closed status (Completed, Failed, or Timed Out). The default Parent Close Policy option is set to terminate the Child Workflow Execution. diff --git a/docs/develop/dotnet/continue-as-new.mdx b/docs/develop/dotnet/workflows/continue-as-new.mdx similarity index 100% rename from docs/develop/dotnet/continue-as-new.mdx rename to docs/develop/dotnet/workflows/continue-as-new.mdx diff --git a/docs/develop/dotnet/workflows/dynamic-workflow.mdx b/docs/develop/dotnet/workflows/dynamic-workflow.mdx new file mode 100644 index 0000000000..3d07a4828b --- /dev/null +++ b/docs/develop/dotnet/workflows/dynamic-workflow.mdx @@ -0,0 +1,40 @@ +--- +id: dynamic-workflow +title: Dynamic Workflow - .NET SDK +sidebar_label: Dynamic Workflow +description: This section explains Dynamic Workflows with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +## Set a Dynamic Workflow {#set-a-dynamic-workflow} + +**How to set a Dynamic Workflow using the Temporal .NET SDK** + +A Dynamic Workflow in Temporal is a Workflow that is invoked dynamically at runtime if no other Workflow with the same name is registered. +A Workflow can be made dynamic by setting `Dynamic` as `true` on the `[Workflow]` attribute. +You must register the Workflow with the Worker before it can be invoked. +Only one Dynamic Workflow can be present on a Worker. + +The Workflow Definition must then accept a single argument of type `Temporalio.Converters.IRawValue[]`. +The [Workflow.PayloadConverter](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_PayloadConverter) property is used to convert an `IRawValue` object to the desired type using extension methods in the `Temporalio.Converters` namespace. + +```csharp +[Workflow(Dynamic = true)] +public class DynamicWorkflow +{ + [WorkflowRun] + public async Task RunAsync(IRawValue[] args) + { + var name = Workflow.PayloadConverter.ToValue(args.Single()); + var param = MyActivityParams("Hello", name); + return await Workflow.ExecuteActivityAsync( + (MyActivities a) => a.MyActivity(param), + new() { StartToCloseTimeout = TimeSpan.FromMinutes(5) }); + } +} +``` diff --git a/docs/develop/dotnet/enriching-ui.mdx b/docs/develop/dotnet/workflows/enriching-ui.mdx similarity index 100% rename from docs/develop/dotnet/enriching-ui.mdx rename to docs/develop/dotnet/workflows/enriching-ui.mdx diff --git a/docs/develop/dotnet/workflows/index.mdx b/docs/develop/dotnet/workflows/index.mdx new file mode 100644 index 0000000000..049259c886 --- /dev/null +++ b/docs/develop/dotnet/workflows/index.mdx @@ -0,0 +1,30 @@ +--- +id: index +title: Workflows - .NET SDK +sidebar_label: Workflows +description: This section explains how to implement Workflows with the .NET SDK +toc_max_heading_level: 4 +keywords: + - .NET SDK +tags: + - .NET SDK + - Temporal SDKs +--- + +import * as Components from '@site/src/components'; + +![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png) + +## Workflows + +- [Workflow Basics](/develop/dotnet/workflows/basics) +- [Child Workflows](/develop/dotnet/workflows/child-workflows) +- [Continue-As-New](/develop/dotnet/workflows/continue-as-new) +- [Cancellation](/develop/dotnet/workflows/cancellation) +- [Timeouts](/develop/dotnet/workflows/timeouts) +- [Message Passing](/develop/dotnet/workflows/message-passing) +- [Enriching the UI](/develop/dotnet/workflows/enriching-ui) +- [Schedules](/develop/dotnet/workflows/schedules) +- [Timers](/develop/dotnet/workflows/timers) +- [Dynamic Workflow](/develop/dotnet/workflows/dynamic-workflow) +- [Versioning](/develop/dotnet/workflows/versioning) \ No newline at end of file diff --git a/docs/develop/dotnet/message-passing.mdx b/docs/develop/dotnet/workflows/message-passing.mdx similarity index 99% rename from docs/develop/dotnet/message-passing.mdx rename to docs/develop/dotnet/workflows/message-passing.mdx index d95a6756c8..6d0f79b044 100644 --- a/docs/develop/dotnet/message-passing.mdx +++ b/docs/develop/dotnet/workflows/message-passing.mdx @@ -348,7 +348,7 @@ For open source server users, Temporal Server version [Temporal Server version 1 ::: -[Update-with-Start](/sending-messages#update-with-start) lets you [send an Update](/develop/dotnet/message-passing#send-update-from-client) that checks whether an already-running Workflow with that ID exists: +[Update-with-Start](/sending-messages#update-with-start) lets you [send an Update](/develop/dotnet/workflows/message-passing#send-update-from-client) that checks whether an already-running Workflow with that ID exists: - If the Workflow exists, the Update is processed. - If the Workflow does not exist, a new Workflow Execution is started with the given ID, and the Update is processed before the main Workflow method starts to execute. @@ -583,7 +583,7 @@ See [Finishing handlers before the Workflow completes](/handling-messages#finish The `[WorkflowInit]` attribute gives message handlers access to [Workflow input](/handling-messages#workflow-initializers). When you use the `[WorkflowInit]` attribute on your constructor, you give the constructor the same Workflow parameters as your `[WorkflowRun]` method. -The SDK will then ensure that your constructor receives the Workflow input arguments that the [Client sent](/develop/dotnet/temporal-client#start-workflow). +The SDK will then ensure that your constructor receives the Workflow input arguments that the [Client sent](/develop/dotnet/client/temporal-client#start-workflow). The Workflow input arguments are also passed to your `[WorkflowRun]` method -- that always happens, whether or not you use the `[WorkflowInit]` attribute. Here's an example. diff --git a/docs/develop/dotnet/schedules.mdx b/docs/develop/dotnet/workflows/schedules.mdx similarity index 93% rename from docs/develop/dotnet/schedules.mdx rename to docs/develop/dotnet/workflows/schedules.mdx index ce206e1122..23f34b76e7 100644 --- a/docs/develop/dotnet/schedules.mdx +++ b/docs/develop/dotnet/workflows/schedules.mdx @@ -29,16 +29,12 @@ This page shows how to do the following: ## Schedule a Workflow {#schedule-a-workflow} -**How to Schedule a Workflow using the Temporal .NET SDK** - Scheduling Workflows is a crucial aspect of any automation process, especially when dealing with time-sensitive tasks. By scheduling a Workflow, you can automate repetitive tasks, reduce the need for manual intervention, and ensure timely execution of your business processes Use any of the following action to help Schedule a Workflow Execution and take control over your automation process. ### Create a Scheduled Workflow {#create-a-workflow} -**How to create a Scheduled Workflow using the Temporal .NET SDK** - The create action enables you to create a new Schedule. When you create a new Schedule, a unique Schedule ID is generated, which you can use to reference the Schedule in other Schedule commands. To create a Scheduled Workflow Execution in .NET, use the [CreateScheduleAsync](https://dotnet.temporal.io/api/Temporalio.Client.ITemporalClient.html#Temporalio_Client_ITemporalClient_CreateScheduleAsync_System_String_Temporalio_Client_Schedules_Schedule_Temporalio_Client_Schedules_ScheduleOptions_) @@ -73,8 +69,6 @@ The Temporal Service doesn't guarantee when this removal will happen. ### Backfill a Scheduled Workflow {#backfill-a-scheduled-workflow} -**How to backfill a Scheduled Workflow using the Temporal .NET SDK** - The backfill action executes Actions ahead of their specified time range. This command is useful when you need to execute a missed or delayed Action, or when you want to test the Workflow before its scheduled time. To backfill a Scheduled Workflow Execution in .NET, use the [BackfillAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_BackfillAsync_System_Collections_Generic_IReadOnlyCollection_Temporalio_Client_Schedules_ScheduleBackfill__Temporalio_Client_RpcOptions_) @@ -99,8 +93,6 @@ await handle.BackfillAsync(new List ### Delete a Scheduled Workflow {#delete-a-scheduled-workflow} -**How to delete a Scheduled Workflow using the Temporal .NET SDK** - The delete action enables you to delete a Schedule. When you delete a Schedule, it does not affect any Workflows that were started by the Schedule. To delete a Scheduled Workflow Execution in .NET, use the [DeleteAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_DeleteAsync_Temporalio_Client_RpcOptions_) method on the Schedule Handle. @@ -117,8 +109,6 @@ await handle.DeleteAsync(); ### Describe a Scheduled Workflow {#describe-a-scheduled-workflow} -**How to describe a Scheduled Workflow using the Temporal .NET SDK** - The describe action shows the current Schedule configuration, including information about past, current, and future Workflow Runs. This command is helpful when you want to get a detailed view of the Schedule and its associated Workflow Runs. To describe a Scheduled Workflow Execution in .NET, use the [DescribeAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_DescribeAsync_Temporalio_Client_RpcOptions_) method on the Schedule Handle. @@ -136,8 +126,6 @@ Console.WriteLine("Schedule info: {0}", desc.Info); ### List a Scheduled Workflow {#list-a-scheduled-workflow} -**How to list a Scheduled Workflow using the Temporal .NET SDK** - The list action lists all the available Schedules. This command is useful when you want to view a list of all the Schedules and their respective Schedule IDs. To list all schedules, use the [ListSchedulesAsync()](https://dotnet.temporal.io/api/Temporalio.Client.ITemporalClient.html#Temporalio_Client_ITemporalClient_ListSchedulesAsync_Temporalio_Client_Schedules_ScheduleListOptions_) asynchronous method on the Client. @@ -157,8 +145,6 @@ await foreach (var desc in client.ListSchedulesAsync()) ### Pause a Scheduled Workflow {#pause-a-scheduled-workflow} -**How to pause a Scheduled Workflow using the Temporal .NET SDK** - The pause action enables you to pause and unpause a Schedule. When you pause a Schedule, all the future Workflow Runs associated with the Schedule are temporarily stopped. This command is useful when you want to temporarily halt a Workflow due to maintenance or any other reason. To pause a Scheduled Workflow Execution in .NET, use the [PauseAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_PauseAsync_System_String_Temporalio_Client_RpcOptions_) method on the Schedule Handle. @@ -176,8 +162,6 @@ await handle.PauseAsync("Pausing the schedule for now"); ### Trigger a Scheduled Workflow {#trigger-a-scheduled-workflow} -**How to trigger a Scheduled Workflow using the Temporal .NET SDK** - The trigger action triggers an immediate action with a given Schedule. By default, this action is subject to the Overlap Policy of the Schedule. This command is helpful when you want to execute a Workflow outside of its scheduled time. To trigger a Scheduled Workflow Execution in .NET, use the [TriggerAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_TriggerAsync_Temporalio_Client_Schedules_ScheduleTriggerOptions_) method on the Schedule Handle. @@ -194,8 +178,6 @@ await handle.TriggerAsync(); ### Update a Scheduled Workflow {#update-a-scheduled-workflow} -**How to update a Scheduled Workflow using the Temporal .NET SDK** - The update action enables you to update an existing Schedule. This command is useful when you need to modify the Schedule's configuration, such as changing the start time, end time, or interval. To update a Scheduled Workflow Execution in .NET, use the [UpdateAsync()](https://dotnet.temporal.io/api/Temporalio.Client.Schedules.ScheduleHandle.html#Temporalio_Client_Schedules_ScheduleHandle_UpdateAsync_System_Func_Temporalio_Client_Schedules_ScheduleUpdateInput_Temporalio_Client_Schedules_ScheduleUpdate__Temporalio_Client_RpcOptions_) method on the Schedule Handle. @@ -220,8 +202,6 @@ await handle.UpdateAsync(input => ## Use Start Delay {#start-delay} -**How to use Start Delay using the Temporal .NET SDK** - Use the `StartDelay` to schedule a Workflow Execution at a specific one-time future point rather than on a recurring schedule. Use the `StartDelay` option on `WorkflowOptions` in either the `StartWorkflowAsync()` or `ExecuteWorkflowAsync()` methods in the Client. diff --git a/docs/develop/dotnet/workflows/timeouts.mdx b/docs/develop/dotnet/workflows/timeouts.mdx new file mode 100644 index 0000000000..64cad029a2 --- /dev/null +++ b/docs/develop/dotnet/workflows/timeouts.mdx @@ -0,0 +1,64 @@ +--- +id: timeouts +title: Workflow Timeouts - .NET SDK +sidebar_label: Timeouts +description: Optimize Workflow Execution with Temporal .NET SDK - Set Workflow Timeouts and Retry Policies efficiently. +toc_max_heading_level: 4 +keywords: + - .NET + - failure detection + - timeouts +tags: + - Activities + - Workflows + - Errors + - Failures + - .NET SDK + - Temporal SDKs +--- + +## Workflow timeouts {#workflow-timeouts} + +Each Workflow timeout controls the maximum duration of a different aspect of a Workflow Execution. + +Workflow timeouts are set when [starting the Workflow Execution](#workflow-timeouts). + +- **[Workflow Execution Timeout](/encyclopedia/detecting-workflow-failures#workflow-execution-timeout)** - restricts the maximum amount of time that a single Workflow Execution can be executed. +- **[Workflow Run Timeout](/encyclopedia/detecting-workflow-failures#workflow-run-timeout):** restricts the maximum amount of time that a single Workflow Run can last. +- **[Workflow Task Timeout](/encyclopedia/detecting-workflow-failures#workflow-task-timeout):** restricts the maximum amount of time that a Worker can execute a Workflow Task. + +These values can be set in the `WorkflowOptions` when calling `StartWorkflowAsync` or `ExecuteWorkflowAsync`. + +Available timeouts are: + +- ExecutionTimeout +- RunTimeout +- TaskTimeout + +```csharp +var result = await client.ExecuteWorkflowAsync( + (MyWorkflow wf) => wf.RunAsync(), + new(id: "my-workflow-id", taskQueue: "my-task-queue") + { + WorkflowExecutionTimeout = TimeSpan.FromMinutes(5), + }); +``` + +### Set Workflow retries {#workflow-retries} + +A Retry Policy can work in cooperation with the timeouts to provide fine controls to optimize the execution experience. + +Use a [Retry Policy](/encyclopedia/retry-policies) to retry a Workflow Execution in the event of a failure. + +Workflow Executions do not retry by default, and Retry Policies should be used with Workflow Executions only in certain situations. + +The `RetryPolicy` can be set in the `WorkflowOptions` when calling `StartWorkflowAsync` or `ExecuteWorkflowAsync`. + +```csharp +var result = await client.ExecuteWorkflowAsync( + (MyWorkflow wf) => wf.RunAsync(), + new(id: "my-workflow-id", taskQueue: "my-task-queue") + { + RetryPolicy = new() { MaximumInterval = TimeSpan.FromSeconds(10) }, + }); +``` diff --git a/docs/develop/dotnet/durable-timers.mdx b/docs/develop/dotnet/workflows/timers.mdx similarity index 94% rename from docs/develop/dotnet/durable-timers.mdx rename to docs/develop/dotnet/workflows/timers.mdx index 4d17c992ba..81116fe435 100644 --- a/docs/develop/dotnet/durable-timers.mdx +++ b/docs/develop/dotnet/workflows/timers.mdx @@ -1,8 +1,8 @@ --- -id: durable-timers -title: Durable Timers - .NET SDK +id: timers +title: Timers - .NET SDK description: Set a Durable Timer using the Temporal .NET SDK. Pause Workflow execution for days or months. Timers are persisted and highly resource-efficient using Workflow.DelayAsync. -sidebar_label: Durable Timers +sidebar_label: Timers keywords: - sdk - dotnet diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/workflows/versioning.mdx similarity index 99% rename from docs/develop/dotnet/versioning.mdx rename to docs/develop/dotnet/workflows/versioning.mdx index 9ccbeb5d27..b154c7ab23 100644 --- a/docs/develop/dotnet/versioning.mdx +++ b/docs/develop/dotnet/workflows/versioning.mdx @@ -229,4 +229,4 @@ The downside of this method is that it requires you to duplicate code and to upd ### Testing a Workflow for replay safety -To determine whether your Workflow your needs a patch, or that you've patched it successfully, you should incorporate [Replay Testing](/develop/dotnet/testing-suite#replay). +To determine whether your Workflow your needs a patch, or that you've patched it successfully, you should incorporate [Replay Testing](/develop/dotnet/best-practices/testing-suite#replay). diff --git a/docs/encyclopedia/activities/activity-definition.mdx b/docs/encyclopedia/activities/activity-definition.mdx index 5acbfba998..692842249e 100644 --- a/docs/encyclopedia/activities/activity-definition.mdx +++ b/docs/encyclopedia/activities/activity-definition.mdx @@ -38,7 +38,7 @@ Activities encapsulate business logic that is prone to failure, allowing for aut - [How to develop an Activity Definition using the PHP SDK](/develop/php/core-application#develop-activities) - [How to develop an Activity Definition using the Python SDK](/develop/python/core-application#develop-activities) - [How to develop an Activity Definition using the TypeScript SDK](/develop/typescript/core-application#develop-activities) -- [How to develop an Activity Definition using the .NET SDK](/develop/dotnet/core-application#develop-activity) +- [How to develop an Activity Definition using the .NET SDK](/develop/dotnet/activities/basics) The term 'Activity Definition' is used to refer to the full set of primitives in any given language SDK that provides an access point to an Activity Function Definition——the method or function that is invoked for an [Activity Task Execution](/tasks#activity-task-execution). Therefore, the terms Activity Function and Activity Method refer to the source of an instance of an execution. diff --git a/docs/encyclopedia/activities/activity-execution.mdx b/docs/encyclopedia/activities/activity-execution.mdx index 5696d66a19..860c16fb72 100644 --- a/docs/encyclopedia/activities/activity-execution.mdx +++ b/docs/encyclopedia/activities/activity-execution.mdx @@ -36,7 +36,7 @@ An Activity Execution is the full chain of [Activity Task Executions](/tasks#act - [How to start an Activity Execution using the PHP SDK](/develop/php/core-application#activity-execution) - [How to start an Activity Execution using the Python SDK](/develop/python/core-application#activity-execution) - [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/core-application#activity-execution) -- [How to start an Activity Execution using the .NET SDK](/develop/dotnet/core-application#activity-execution) +- [How to start an Activity Execution using the .NET SDK](/develop/dotnet/activities/execution) ::: @@ -126,7 +126,7 @@ How to complete an Activity Asynchronously in: - [PHP](/develop/php/asynchronous-activity-completion) - [Python](/develop/python/asynchronous-activity-completion) - [TypeScript](/develop/typescript/asynchronous-activity-completion) -- [.NET](/develop/dotnet/asynchronous-activity) +- [.NET](/develop/dotnet/activities/asynchronous-activity) ### When to use Async Completion diff --git a/docs/encyclopedia/child-workflows/child-workflows.mdx b/docs/encyclopedia/child-workflows/child-workflows.mdx index c6881a77ee..90a926db72 100644 --- a/docs/encyclopedia/child-workflows/child-workflows.mdx +++ b/docs/encyclopedia/child-workflows/child-workflows.mdx @@ -35,7 +35,7 @@ A Child Workflow Execution is a [Workflow Execution](/workflow-execution) that i - [PHP SDK Child Workflow feature guide](/develop/php/child-workflows) - [Python SDK Child Workflow feature guide](/develop/python/child-workflows) - [TypeScript SDK Child Workflow feature guide](/develop/typescript/child-workflows) -- [.NET SDK Child Workflow feature guide](/develop/dotnet/child-workflows) +- [.NET SDK Child Workflow feature guide](/develop/dotnet/workflows/child-workflows) A Workflow Execution can be both a Parent and a Child Workflow Execution because any Workflow can spawn another Workflow. diff --git a/docs/encyclopedia/child-workflows/parent-close-policy.mdx b/docs/encyclopedia/child-workflows/parent-close-policy.mdx index 8f75186071..c439d28b38 100644 --- a/docs/encyclopedia/child-workflows/parent-close-policy.mdx +++ b/docs/encyclopedia/child-workflows/parent-close-policy.mdx @@ -40,7 +40,7 @@ A Parent Close Policy determines what happens to a Child Workflow Execution if i - [How to set a Parent Close Policy using the PHP SDK](/develop/php/child-workflows#parent-close-policy) - [How to set a Parent Close Policy using the Python SDK](/develop/python/child-workflows#parent-close-policy) - [How to set a Parent Close Policy using the TypeScript SDK](/develop/typescript/child-workflows#parent-close-policy) -- [How to set a Parent Close Policy using the .NET SDK](/develop/dotnet/child-workflows#parent-close-policy) +- [How to set a Parent Close Policy using the .NET SDK](/develop/dotnet/workflows/child-workflows#parent-close-policy) There are three possible values: diff --git a/docs/encyclopedia/detecting-activity-failures.mdx b/docs/encyclopedia/detecting-activity-failures.mdx index 9a2355d719..ca6ec6c6ff 100644 --- a/docs/encyclopedia/detecting-activity-failures.mdx +++ b/docs/encyclopedia/detecting-activity-failures.mdx @@ -37,7 +37,7 @@ In other words, it's a limit for how long an Activity Task can be enqueued. - + The moment that the Task is picked by the Worker, from the Task Queue, is considered to be the start of the Activity Task Execution for the purposes of the Schedule-To-Start Timeout and associated metrics. @@ -84,7 +84,7 @@ A Start-To-Close Timeout is the maximum time allowed for a single [Activity Task - + **The default Start-To-Close Timeout is the same as the default [Schedule-To-Close Timeout](#schedule-to-close-timeout).** @@ -136,7 +136,7 @@ A Schedule-To-Close Timeout is the maximum amount of time allowed for the overal - + - + Activity Heartbeats work in conjunction with a [Heartbeat Timeout](#heartbeat-timeout). @@ -258,7 +258,7 @@ A Heartbeat Timeout is the maximum time between [Activity Heartbeats](#activity- - + - + - + - +
+ - [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process)

- [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/core-application#run-a-temporal-cloud-worker) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-temporal-cloud-worker) @@ -112,7 +112,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to start an Activity Execution using the PHP SDK](/develop/php/core-application#activity-execution) - [How to start an Activity Execution using the Python SDK](/develop/python/core-application#activity-execution) - [How to start an Activity Execution using the TypeScript SDK](/develop/typescript/core-application#activity-execution) - - [How to start an Activity Execution using the .NET SDK](/develop/dotnet/core-application#activity-execution) + - [How to start an Activity Execution using the .NET SDK](/develop/dotnet/activities/execution) 4. A Task Queue name can be provided when spawning a Child Workflow Execution: @@ -124,7 +124,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to start a Child Workflow Execution using the PHP SDK](/develop/php/continue-as-new) - [How to start a Child Workflow Execution using the Python SDK](/develop/python/child-workflows) - [How to start a Child Workflow Execution using the TypeScript SDK](/develop/typescript/child-workflows) - - [How to start a Child Workflow Execution using the .NET SDK](/develop/dotnet/child-workflows) + - [How to start a Child Workflow Execution using the .NET SDK](/develop/dotnet/workflows/child-workflows) 5. A Task Queue name can be provided when creating a Nexus Endpoint. Nexus Endpoints route requests to the target Task Queue. diff --git a/docs/encyclopedia/workers/workers.mdx b/docs/encyclopedia/workers/workers.mdx index 72d4529bd8..d47f6f34b1 100644 --- a/docs/encyclopedia/workers/workers.mdx +++ b/docs/encyclopedia/workers/workers.mdx @@ -39,7 +39,7 @@ A Worker Program is the static code that defines the constraints of the Worker P - [How to run a development Worker using the PHP SDK](/develop/php/core-application#run-a-dev-worker) - [How to run a development Worker using the Python SDK](/develop/python/core-application#run-a-dev-worker) - [How to run a development Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-dev-worker) -- [How to run a development Worker using the .NET SDK](/develop/dotnet/core-application#run-worker-process) +- [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process) - [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/core-application#run-a-temporal-cloud-worker) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/core-application#run-a-temporal-cloud-worker) diff --git a/docs/encyclopedia/workflow-message-passing/handling-messages.mdx b/docs/encyclopedia/workflow-message-passing/handling-messages.mdx index 5987ce4675..728853a4f8 100644 --- a/docs/encyclopedia/workflow-message-passing/handling-messages.mdx +++ b/docs/encyclopedia/workflow-message-passing/handling-messages.mdx @@ -128,7 +128,7 @@ See the links below for examples of solving this in your SDK. See examples of the above patterns. - + @@ -154,7 +154,7 @@ Once the Update handler is finished and has returned a value, the operation is c - + @@ -200,7 +200,7 @@ Use these links to see a simple Signal handler. - + @@ -213,7 +213,7 @@ Use these links to see a simple update handler. - + @@ -226,6 +226,6 @@ Author queries using these per-language guides. - + diff --git a/docs/encyclopedia/workflow-message-passing/sending-messages.mdx b/docs/encyclopedia/workflow-message-passing/sending-messages.mdx index 70deb90c48..f782f9f13b 100644 --- a/docs/encyclopedia/workflow-message-passing/sending-messages.mdx +++ b/docs/encyclopedia/workflow-message-passing/sending-messages.mdx @@ -53,7 +53,7 @@ You can also Signal-With-Start to lazily initialize a Workflow while sending a S - + #### Send a Signal from one Workflow to another @@ -64,7 +64,7 @@ You can also Signal-With-Start to lazily initialize a Workflow while sending a S - + #### Signal-With-Start {#signal-with-start} @@ -77,7 +77,7 @@ Signal-With-Start is a great tool for lazily initializing Workflows. When you se - + ### Sending Updates {#sending-updates} @@ -119,7 +119,7 @@ Use [Update Validators](/handling-messages#update-validators) and [Update IDs](/ - + #### Update-With-Start {#update-with-start} @@ -167,7 +167,7 @@ The SDKs will retry the Update-With-Start request, but there is no guarantee tha - + ### Sending Queries {#sending-queries} @@ -182,7 +182,7 @@ You can also send a built-in "Stack Trace Query" for debugging. - + #### Stack Trace Query {#stack-trace-query} diff --git a/docs/encyclopedia/workflow/dynamic-handler.mdx b/docs/encyclopedia/workflow/dynamic-handler.mdx index f5bb536da1..172c25a9c5 100644 --- a/docs/encyclopedia/workflow/dynamic-handler.mdx +++ b/docs/encyclopedia/workflow/dynamic-handler.mdx @@ -25,7 +25,7 @@ Currently, the Temporal SDKs that support Dynamic Handlers are: - [Java](/develop/java/message-passing#dynamic-handler) - [Python](/develop/python/message-passing#dynamic-handler) -- [.NET](/develop/dotnet/message-passing#dynamic-handler) +- [.NET](/develop/dotnet/workflows/message-passing#dynamic-handler) - [Go](/develop/go/core-application#set-a-dynamic-workflow) - [Ruby](/develop/ruby/message-passing#dynamic-handler) diff --git a/docs/encyclopedia/workflow/workflow-definition.mdx b/docs/encyclopedia/workflow/workflow-definition.mdx index 51f31e89ad..82158a1426 100644 --- a/docs/encyclopedia/workflow/workflow-definition.mdx +++ b/docs/encyclopedia/workflow/workflow-definition.mdx @@ -130,7 +130,7 @@ export async function WorkflowExample( -**[Workflow Definition in C# and .NET](/develop/dotnet/core-application#develop-workflow)** +**[Workflow Definition in C# and .NET](/develop/dotnet/workflows/basics)** ```csharp [Workflow] @@ -292,7 +292,7 @@ To patch: - [How to patch Workflow code in Python](/develop/python/versioning#patching) - [How to patch Workflow code in PHP](/develop/php/versioning#php-sdk-patching-api) - [How to patch Workflow code in TypeScript](/develop/typescript/versioning#patching) -- [How to patch Workflow code in .NET](/develop/dotnet/versioning#patching) +- [How to patch Workflow code in .NET](/develop/dotnet/workflows/versioning#patching) To test, see [Safe Deployments](/develop/safe-deployments.mdx). diff --git a/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx b/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx index 9ef1ff61e4..851c15f689 100644 --- a/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/continue-as-new.mdx @@ -41,7 +41,7 @@ Workflows that do this are often called Entity Workflows because they represent - [How to Continue-As-New using the PHP SDK](/develop/php/continue-as-new) - [How to Continue-As-New using the Python SDK](/develop/python/continue-as-new#how) - [How to Continue-As-New using the TypeScript SDK](/develop/typescript/continue-as-new) -- [How to Continue-As-New using the .NET SDK](/develop/dotnet/continue-as-new) +- [How to Continue-As-New using the .NET SDK](/develop/dotnet/workflows/continue-as-new) ## When in your Workflow is it right to Continue-As-New? {#when} @@ -55,4 +55,4 @@ To prevent long-running Workflows from running on stale versions of code, you ma - [Determine when to Continue-As-New using the PHP SDK](/develop/php/continue-as-new) - [Determine when to Continue-As-New using the Python SDK](/develop/python/continue-as-new#when) - [Determine when to Continue-As-New using the TypeScript SDK](/develop/typescript/continue-as-new) -- [Determine when to Continue-As-New using the .NET SDK](/develop/dotnet/continue-as-new) +- [Determine when to Continue-As-New using the .NET SDK](/develop/dotnet/workflows/continue-as-new) diff --git a/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx b/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx index 2da07e0453..32ee56937e 100644 --- a/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/timers-delays.mdx @@ -28,7 +28,7 @@ Workers consume no additional resources while waiting for a Timer to fire, so a - [How to set Timers in PHP](/develop/php/timers) - [How to set Timers in Python](/develop/python/timers) - [How to set Timers in TypeScript](/develop/typescript/timers) -- [How to set Timers in .NET](/develop/dotnet/durable-timers) +- [How to set Timers in .NET](/develop/dotnet/workflows/timers) The duration of a Timer is fixed, and your Workflow might specify a value as short as one second or as long as several years. Although it's possible to specify an extremely precise duration, such as 36 milliseconds or 15.072 minutes, your Workflows should not rely on sub-second accuracy for Timers. diff --git a/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx b/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx index 6c334226b4..e788797d25 100644 --- a/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx +++ b/docs/encyclopedia/workflow/workflow-execution/workflow-execution.mdx @@ -38,7 +38,7 @@ It is the main unit of execution of a [Temporal Application](/temporal#temporal- - [How to start a Workflow Execution using the PHP SDK](/develop/php/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the Python SDK](/develop/python/temporal-client#start-workflow-execution) - [How to start a Workflow Execution using the TypeScript SDK](/develop/typescript/temporal-client#start-workflow-execution) -- [How to start a Workflow Execution using the .NET SDK](/develop/dotnet/temporal-client#start-workflow) +- [How to start a Workflow Execution using the .NET SDK](/develop/dotnet/client/temporal-client#start-workflow) Each Temporal Workflow Execution has exclusive access to its local state. It executes concurrently to all other Workflow Executions, and communicates with other Workflow Executions through [Signals](/sending-messages#sending-signals) and the environment through [Activities](/activities). @@ -76,7 +76,7 @@ If a failure occurs, the Workflow Execution picks up where the last recorded eve - [How to use Replay APIs using the Java SDK](/develop/java/testing-suite#replay) - [How to use Replay APIs using the Python SDK](/develop/python/testing-suite#replay) - [How to use Replay APIs using the TypeScript SDK](/develop/typescript/testing-suite#replay) -- [How to use Replay APIs using the .NET SDK](/develop/dotnet/testing-suite#replay) +- [How to use Replay APIs using the .NET SDK](/develop/dotnet/best-practices/testing-suite#replay) ### Commands and awaitables {#commands-awaitables} diff --git a/docs/evaluate/development-production-features/core-application.mdx b/docs/evaluate/development-production-features/core-application.mdx index 632c860040..ca0b4a44af 100644 --- a/docs/evaluate/development-production-features/core-application.mdx +++ b/docs/evaluate/development-production-features/core-application.mdx @@ -50,7 +50,7 @@ Or jump straight to a Temporal SDK feature guide: - + diff --git a/docs/evaluate/development-production-features/data-encryption.mdx b/docs/evaluate/development-production-features/data-encryption.mdx index 7047f311dd..35fc7c0318 100644 --- a/docs/evaluate/development-production-features/data-encryption.mdx +++ b/docs/evaluate/development-production-features/data-encryption.mdx @@ -33,6 +33,6 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/debugging.mdx b/docs/evaluate/development-production-features/debugging.mdx index ad6beaebd7..ebcf3bf08e 100644 --- a/docs/evaluate/development-production-features/debugging.mdx +++ b/docs/evaluate/development-production-features/debugging.mdx @@ -38,6 +38,6 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/failure-detection.mdx b/docs/evaluate/development-production-features/failure-detection.mdx index 9ed8340540..9704a6e245 100644 --- a/docs/evaluate/development-production-features/failure-detection.mdx +++ b/docs/evaluate/development-production-features/failure-detection.mdx @@ -46,7 +46,7 @@ Or jump straight to a Temporal SDK feature guide. - + @@ -56,7 +56,7 @@ Or jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/interrupt-a-workflow.mdx b/docs/evaluate/development-production-features/interrupt-a-workflow.mdx index 3732d4e353..00a111c16a 100644 --- a/docs/evaluate/development-production-features/interrupt-a-workflow.mdx +++ b/docs/evaluate/development-production-features/interrupt-a-workflow.mdx @@ -60,7 +60,7 @@ robust and responsive. archetype="feature-guide" /> diff --git a/docs/evaluate/development-production-features/observability.mdx b/docs/evaluate/development-production-features/observability.mdx index e781938868..80c0279a9c 100644 --- a/docs/evaluate/development-production-features/observability.mdx +++ b/docs/evaluate/development-production-features/observability.mdx @@ -52,7 +52,7 @@ Jump straight into the Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/schedules.mdx b/docs/evaluate/development-production-features/schedules.mdx index 5a0955cd33..7e1472bc48 100644 --- a/docs/evaluate/development-production-features/schedules.mdx +++ b/docs/evaluate/development-production-features/schedules.mdx @@ -41,6 +41,6 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/testing-suite.mdx b/docs/evaluate/development-production-features/testing-suite.mdx index f3f8b0cd2e..296bea1c85 100644 --- a/docs/evaluate/development-production-features/testing-suite.mdx +++ b/docs/evaluate/development-production-features/testing-suite.mdx @@ -42,6 +42,6 @@ Jump straight to a Temporal SDK feature guide. - + diff --git a/docs/evaluate/development-production-features/throughput-composability.mdx b/docs/evaluate/development-production-features/throughput-composability.mdx index 1c11f5d923..7fd01f6efb 100644 --- a/docs/evaluate/development-production-features/throughput-composability.mdx +++ b/docs/evaluate/development-production-features/throughput-composability.mdx @@ -37,7 +37,7 @@ See the SDK feature guides for implementation details: - + diff --git a/docs/evaluate/development-production-features/workflow-message-passing.mdx b/docs/evaluate/development-production-features/workflow-message-passing.mdx index 5220949f8d..685f752e2f 100644 --- a/docs/evaluate/development-production-features/workflow-message-passing.mdx +++ b/docs/evaluate/development-production-features/workflow-message-passing.mdx @@ -58,6 +58,6 @@ If you want to jump to straight to implementation details, see the SDK feature g - + diff --git a/docs/production-deployment/self-hosted-guide/monitoring.mdx b/docs/production-deployment/self-hosted-guide/monitoring.mdx index cb1e4b978f..578688fd0e 100644 --- a/docs/production-deployment/self-hosted-guide/monitoring.mdx +++ b/docs/production-deployment/self-hosted-guide/monitoring.mdx @@ -98,7 +98,7 @@ The Metrics section in the Observability guide details how to create hooks for a - [PHP](/develop/php/observability) - [Python](/develop/python/observability#metrics) - [TypeScript](/develop/typescript/observability#metrics) -- [.NET](/develop/dotnet/observability#metrics) +- [.NET](/develop/dotnet/workers/observability#metrics) - [Ruby](/develop/ruby/observability#metrics) For end-to-end examples of how to expose metrics from each SDK, see the metrics samples: diff --git a/docs/references/sdk-metrics.mdx b/docs/references/sdk-metrics.mdx index c87792e0fa..2ee717c800 100644 --- a/docs/references/sdk-metrics.mdx +++ b/docs/references/sdk-metrics.mdx @@ -31,7 +31,7 @@ The Temporal SDKs emit a set of metrics from Temporal Client usage and Worker Pr - [How to emit metrics using the Java SDK](/develop/java/observability#metrics) - [How to emit metrics using the Python SDK](/develop/python/observability#metrics) - [How to emit metrics using the TypeScript SDK](/develop/typescript/observability#metrics) -- [How to emit metrics using the .NET SDK](/develop/dotnet/observability#metrics) +- [How to emit metrics using the .NET SDK](/develop/dotnet/workers/observability#metrics) - [How to emit metrics using the Ruby SDK](/develop/ruby/observability#metrics) - [How to tune Worker performance based on metrics](/develop/worker-performance) diff --git a/docs/web-ui.mdx b/docs/web-ui.mdx index aee2192560..9e9a371b83 100644 --- a/docs/web-ui.mdx +++ b/docs/web-ui.mdx @@ -265,7 +265,7 @@ To read more about Schedules, explore these links: text="Schedules using the TypeScript SDK" archetype="feature-guide" /> - + ### Settings diff --git a/sidebars.js b/sidebars.js index 6fae131fb6..9d3fbc27c9 100644 --- a/sidebars.js +++ b/sidebars.js @@ -277,24 +277,99 @@ module.exports = { }, items: [ 'develop/dotnet/set-up-your-local-dotnet', - 'develop/dotnet/core-application', - 'develop/dotnet/temporal-client', - 'develop/dotnet/testing-suite', - 'develop/dotnet/failure-detection', - 'develop/dotnet/message-passing', - 'develop/dotnet/cancellation', - 'develop/dotnet/asynchronous-activity', - 'develop/dotnet/versioning', - 'develop/dotnet/observability', - 'develop/dotnet/benign-exceptions', - 'develop/dotnet/enriching-ui', - 'develop/dotnet/debugging', - 'develop/dotnet/schedules', - 'develop/dotnet/converters-and-encryption', - 'develop/dotnet/durable-timers', - 'develop/dotnet/nexus', - 'develop/dotnet/child-workflows', - 'develop/dotnet/continue-as-new', + { + type: 'category', + label: 'Workflows', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/workflows/index', + }, + items: [ + 'develop/dotnet/workflows/basics', + 'develop/dotnet/workflows/child-workflows', + 'develop/dotnet/workflows/continue-as-new', + 'develop/dotnet/workflows/cancellation', + 'develop/dotnet/workflows/timeouts', + 'develop/dotnet/workflows/message-passing', + 'develop/dotnet/workflows/enriching-ui', + 'develop/dotnet/workflows/schedules', + 'develop/dotnet/workflows/timers', + 'develop/dotnet/workflows/dynamic-workflow', + 'develop/dotnet/workflows/versioning', + ] + }, + { + type: 'category', + label: 'Activities', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/activities/index', + }, + items: [ + 'develop/dotnet/activities/basics', + 'develop/dotnet/activities/execution', + 'develop/dotnet/activities/timeouts', + 'develop/dotnet/activities/asynchronous-activity', + 'develop/dotnet/activities/dynamic-activity', + 'develop/dotnet/activities/benign-exceptions', + ] + }, + { + type: 'category', + label: 'Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/workers/index', + }, + items: [ + 'develop/dotnet/workers/basics', + 'develop/dotnet/workers/run-worker-process', + 'develop/dotnet/workers/observability', + ] + }, + { + type: 'category', + label: 'Client', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/client/index', + }, + items: [ + 'develop/dotnet/client/temporal-client', + 'develop/dotnet/client/namespaces', + ], + }, + { + type: 'category', + label: 'Nexus', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/nexus/index', + }, + items: [ + 'develop/dotnet/nexus/service-handler', + ], + }, + { + type: 'category', + label: 'Best practices', + collapsed: true, + link: { + type: 'doc', + id: 'develop/dotnet/best-practices/index', + }, + items: [ + 'develop/dotnet/best-practices/error-handling', + 'develop/dotnet/best-practices/testing-suite', + 'develop/dotnet/best-practices/debugging', + 'develop/dotnet/best-practices/converters-and-encryption', + ] + }, ], }, {