# 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<string> 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<string> 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<MyWorkflow>());
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<IMyDatabaseClient, MyDatabaseClient>().
            // Add the worker
            AddHostedTemporalWorker(
                clientTargetHost: "localhost:7233",
                clientNamespace: "default",
                taskQueue: "my-task-queue").
            // Add the activities class at the scoped level
            AddScopedActivities<MyActivities>().
            AddWorkflow<MyWorkflow>())
    .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<string> RunAsync(IRawValue[] args)
    {
        var name = Workflow.PayloadConverter.ToValue<string>(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<MyActivityParams>(args.Single());
        return $"{input.Greeting}, {input.Name}!";
    }
}
```

---

## Debugging - .NET SDK

## Debugging {#debug}

This page shows how to do the following:

- [Debug in a development environment](#debug-in-a-development-environment)
- [Debug in a development production](#debug-in-a-development-production)

### 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)

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.

Debug Server performance with [Cloud metrics](/cloud/metrics/) or [self-hosted Server metrics](/self-hosted-guide/production-checklist#scaling-and-metrics).

---

## Durable Timers - .NET SDK

This page describes how to set a Durable Timer using the Temporal .NET SDK.

A [Durable Timer](/workflow-execution/timers-delays) is used to pause the execution of a Workflow for a specified duration.
A Workflow can sleep for days or even months.
Timers are persisted, so even if your Worker or Temporal Service is down when the time period completes, as soon as your Worker and Temporal Service are back up, the Durable Timer call will resolve and your code will continue executing.

Sleeping is a resource-light operation: it does not tie up the process, and you can run millions of Timers off a single Worker.

To add a Timer in a Workflow, use `Workflow.DelayAsync`. This is like a deterministic form of `Task.Delay`.

```csharp
// Sleep for 3 days
await Workflow.DelayAsync(TimeSpan.FromDays(3));
```

<!--  ## Skip time in tests {#testing-skip-time}

**How to skip time while testing Workflows using the .NET SDK**

TODO(cretz): This should not be under a durable timers section -->

---

## Enriching the User Interface - .NET SDK

Temporal supports adding context to Workflows and events with metadata. 
This helps users identify and understand Workflows and their operations.

## Adding Summary and Details to Workflows

### Starting a Workflow

When starting a Workflow, you can provide a static summary and details to help identify the Workflow in the UI:

```csharp
using Temporalio.Client;

// Create client
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

// Start a Workflow with static summary and details
var handle = await client.StartWorkflowAsync(
    (YourWorkflow wf) => wf.RunAsync("Workflow input"),
    new WorkflowOptions
    {
        Id = "your-Workflow-id",
        TaskQueue = "your-task-queue",
        StaticSummary = "Order processing for customer #12345",
        StaticDetails = "Processing premium order with expedited shipping"
    });
```

`StaticSummary` is a single-line description that appears in the Workflow list view, limited to 200 bytes.
`StaticDetails` can be multi-line and provides more comprehensive information that appears in the Workflow details view, with a larger limit of 20K bytes.

The input format is standard Markdown excluding images, HTML, and scripts.

You can also use the `ExecuteWorkflowAsync` method with the same parameters:

```csharp
var result = await client.ExecuteWorkflowAsync(
    (YourWorkflow wf) => wf.RunAsync("Workflow input"),
    new WorkflowOptions
    {
        Id = "your-Workflow-id",
        TaskQueue = "your-task-queue",
        StaticSummary = "Order processing for customer #12345",
        StaticDetails = "Processing premium order with expedited shipping"
    });
```

### Inside the Workflow

Within a Workflow, you can get and set the _current Workflow details_. 
Unlike static summary/details set at Workflow start, this value can be updated throughout the life of the Workflow. 
Current Workflow details also takes Markdown format (excluding images, HTML, and scripts) and can span multiple lines.

```csharp
using Temporalio.Workflows;

[Workflow]
public class YourWorkflow
{
    [WorkflowRun]
    public async Task<string> RunAsync(string input)
    {
        // Get the current details
        var currentDetails = Workflow.CurrentDetails;
        Workflow.Logger.LogInformation($"Current details: {currentDetails}");
        
        // Set/update the current details
        Workflow.CurrentDetails = "Updated Workflow details with new status";
        
        return "Workflow completed";
    }
}
```

### Adding Summary to Activities and Timers

You can attach a metadata parameter `Summary` to Activities when starting them from within a Workflow:

```csharp
using Temporalio.Activities;
using Temporalio.Workflows;

[Workflow]
public class YourWorkflow
{
    [WorkflowRun]
    public async Task<string> RunAsync(string input)
    {
        // Execute an activity with a summary
        var result = await Workflow.ExecuteActivityAsync(
            (YourActivities act) => act.YourActivityAsync(input),
            new ActivityOptions
            {
                StartToCloseTimeout = TimeSpan.FromSeconds(10),
                Summary = "Processing user data"
            });
        
        return result;
    }
}
```

Similarly, you can attach a `Summary` to timers within a Workflow:

```csharp
using Temporalio.Workflows;

[Workflow]
public class YourWorkflow
{
    [WorkflowRun]
    public async Task<string> RunAsync(string input)
    {
        // Create a timer with a summary
        await Workflow.DelayWithOptionsAsync(new DelayOptions(TimeSpan.FromMinutes(5))
        {
            Summary = "Waiting for payment confirmation"
        });
        
        return "Timer completed";
    }
}
```

The input format for `Summary` is a string, and limited to 200 bytes.

## Viewing Summary and Details in the UI

Once you've added summaries and details to your Workflows, Activities, and timers, you can view this enriched information in the Temporal Web UI. 
Navigate to your Workflow's details page to see the metadata displayed in two key locations:

### Workflow Overview Section

At the top of the Workflow details page, you'll find the Workflow-level metadata:

- **Summary & Details** - Displays the static summary and static details set when starting the Workflow
- **Current Details** - Displays the dynamic details that can be updated during Workflow execution

All Workflow details support standard Markdown formatting (excluding images, HTML, and scripts), allowing you to create rich, structured information displays.

### Event History

Individual events in the Workflow's Event History display their associated summaries when available.

Workflow, Activity and Timer summaries appear in purple text next to their corresponding events, providing immediate context without requiring you to expand the Event details. 
When you do expand an Event, the summary is also prominently displayed in the detailed view.

---

## Failure detection - .NET SDK

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<OrderConfirmation> SendBillAsync(Bill bill)
{
    throw new InvalidDepartmentException("Invalid department");
}
```

```csharp
[Activity]
public Task<OrderConfirmation> 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<OrderConfirmation> 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 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),
    });
```

---

## .Net SDK developer guide

<!-- .NET SDK feature guidance landing page-->

![.NET SDK Banner](/img/assets/banner-dotnet-temporal.png)

:::info .NET SPECIFIC RESOURCES 

Build Temporal Applications with the .NET SDK.

**Temporal .NET Technical 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.

- [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.

## [Asynchronous Activity completion](/develop/dotnet/asynchronous-activity)

Complete Activities asynchronously.

- [Asynchronous Activity](/develop/dotnet/asynchronous-activity): Asynchronous Activity completion enables the Activity
  Function to return without the Activity Execution completing.

## [Versioning](/develop/dotnet/versioning)

Change Workflow Definitions without causing non-deterministic behavior in running Workflows.

- [Use the .NET SDK Patching API](/develop/dotnet/versioning#patching): Patching Workflows using the .NET SDK.

## [Observability](/develop/dotnet/observability)

Configure and use the Temporal Observability APIs.

- [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.

## [Debugging](/develop/dotnet/debugging)

Explore various ways to debug your application.

- [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.

## [Schedules](/develop/dotnet/schedules)

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)

Add descriptive information to workflows and events for better visibility and context in the UI.

- [Adding Summary and Details to Workflows](/develop/dotnet/enriching-ui#adding-summary-and-details-to-workflows)

---

## Workflow message passing - .NET SDK

A Workflow can act like a stateful web service that receives messages: Queries, Signals, and Updates.
The Workflow implementation defines these endpoints via handler methods that can react to incoming messages and return values.
Temporal Clients use messages to read Workflow state and control execution.
See [Workflow message passing](/encyclopedia/workflow-message-passing) for a general overview of this topic.
This page introduces these features for the Temporal .NET SDK.

## Write message handlers {#writing-message-handlers}

:::info
The code that follows is part of a [working solution](https://github.com/temporalio/samples-dotnet/tree/main/src/MessagePassing).
:::

Follow these guidelines when writing your message handlers:

- Message handlers are defined as methods on the Workflow class, using one of the three attributes: [`WorkflowQueryAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowQueryAttribute.html), [`WorkflowSignalAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowSignalAttribute.html), and [`WorkflowUpdateAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowUpdateAttribute.html).
- The parameters and return values of handlers and the main Workflow function must be [serializable](/dataconversion).
- Prefer data classes to multiple input parameters.
  Data class parameters allow you to add fields without changing the calling signature.

### Query handlers {#queries}

A [Query](/sending-messages#sending-queries) is a synchronous operation that retrieves state from a Workflow Execution.
Define as a method:

```csharp
[Workflow]
public class GreetingWorkflow
{
    public enum Language
    {
        Chinese,
        English,
        French,
        Spanish,
        Portuguese,
    }

    public record GetLanguagesInput(bool IncludeUnsupported);

    // ...

    [WorkflowQuery]
    public IList<Language> GetLanguages(GetLanguagesInput input) =>
        Enum.GetValues<Language>().
            Where(language => input.IncludeUnsupported || Greetings.ContainsKey(language)).
            ToList();

    // ...
```

Or as a property getter:

```csharp
[Workflow]
public class GreetingWorkflow
{
    public enum Language
    {
        Chinese,
        English,
        French,
        Spanish,
        Portuguese,
    }

    // ...

    [WorkflowQuery]
    public Language CurrentLanguage { get; private set; } = Language.English;

    // ...
```

- The Query attribute can accept arguments.
  See the API reference docs: [`WorkflowQueryAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowQueryAttribute.html).
- A Query handler must not modify Workflow state.
- You can't perform async blocking operations such as executing an Activity in a Query handler.

### Signal handlers {#signals}

A [Signal](/sending-messages#sending-signals) is an asynchronous message sent to a running Workflow Execution to change its state and control its flow:

```csharp
[Workflow]
public class GreetingWorkflow
{
    public record ApproveInput(string Name);

    // ...

    [WorkflowSignal]
    public async Task ApproveAsync(ApproveInput input)
    {
        approvedForRelease = true;
        approverName = input.Name;
    }

    // ...
```

- The Signal attribute can accept arguments.
  Refer to the API docs: [`WorkflowSignalAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowSignalAttribute.html).

- The handler should not return a value.
  The response is sent immediately from the server, without waiting for the Workflow to process the Signal.

- Signal (and Update) handlers can be asynchronous and blocking.
  This allows you to use Activities, Child Workflows, durable [`Workflow.DelayAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_DelayAsync_System_Int32_System_Nullable_System_Threading_CancellationToken__) Timers, [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) conditions, and more.
  See [Async handlers](#async-handlers) and [Workflow message passing](/encyclopedia/workflow-message-passing) for guidelines on safely using async Signal and Update handlers.

### Update handlers and validators {#updates}

An [Update](/sending-messages#sending-updates) is a trackable synchronous request sent to a running Workflow Execution.
It can change the Workflow state, control its flow, and return a result.
The sender must wait until the Worker accepts or rejects the Update.
The sender may wait further to receive a returned value or an exception if something goes wrong:

```csharp
[Workflow]
public class GreetingWorkflow
{
    public enum Language
    {
        Chinese,
        English,
        French,
        Spanish,
        Portuguese,
    }

    // ...

    [WorkflowUpdateValidator(nameof(SetCurrentLanguageAsync))]
    public void ValidateLanguage(Language language)
    {
        if (!Greetings.ContainsKey(language))
        {
            throw new ApplicationFailureException($"{language} is not supported");
        }
    }

    [WorkflowUpdate]
    public async Task<Language> SetCurrentLanguageAsync(Language language)
    {
        var previousLanguage = CurrentLanguage;
        CurrentLanguage = language;
        return previousLanguage;
    }

    // ...
```

- The Update attribute can take arguments (like, `Name`, `Dynamic` and `UnfinishedPolicy`) as described in the API reference docs for [`WorkflowUpdateAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowUpdateAttribute.html).

- About validators:
  - Use validators to reject an Update before it is written to History.
    Validators are always optional.
    If you don't need to reject Updates, you can skip them.
  - Define an Update validator with the [`WorkflowUpdateValidatorAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowUpdateValidatorAttribute.html) attribute.
    Use the Name argument when declaring the validator to connect it to its Update.
    The validator must be a `void` type and accept the same argument types as the handler.

- Accepting and rejecting Updates with validators:
  - To reject an Update, raise an exception of any type in the validator.
  - Without a validator, Updates are always accepted.
- Validators and Event History:
  - The `WorkflowExecutionUpdateAccepted` event is written into the History whether the acceptance was automatic or programmatic.
  - When a Validator raises an error, the Update is rejected, the Update is not run, and `WorkflowExecutionUpdateAccepted` _won't_ be added to the Event History.
    The caller receives an "Update failed" error.

- Use [`Workflow.CurrentUpdateInfo`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_CurrentUpdateInfo) to obtain information about the current Update.
  This includes the Update ID, which can be useful for deduplication when using Continue-As-New: see [Ensuring your messages are processed exactly once](/handling-messages#exactly-once-message-processing).
- Update (and Signal) handlers can be asynchronous and blocking.
  This allows you to use Activities, Child Workflows, durable [`Workflow.DelayAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_DelayAsync_System_Int32_System_Nullable_System_Threading_CancellationToken__) Timers, [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) conditions, and more.
  See [Async handlers](#async-handlers) and [Workflow message passing](/encyclopedia/workflow-message-passing) for guidelines on safely using async Update and Signal handlers.

## Send messages {#send-messages}

To send Queries, Signals, or Updates you call methods on a [`WorkflowHandle`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html) object.
To obtain the WorkflowStub, you can:

- Use [`TemporalClient.StartWorkflowAsync`](https://dotnet.temporal.io/api/Temporalio.Client.TemporalClient.html#Temporalio_Client_TemporalClient_StartWorkflowAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowOptions_) to start a Workflow and return its handle.
- Use the [`TemporalClient.GetWorkflowHandle`](https://dotnet.temporal.io/api/Temporalio.Client.TemporalClient.html#Temporalio_Client_TemporalClient_GetWorkflowHandle_System_String_System_String_System_String_) method to retrieve a Workflow handle by its Workflow Id.

For example:

```csharp
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var workflowHandle = await client.StartWorkflowAsync(
    (GreetingWorkflow wf) => wf.RunAsync(),
    new(id: "message-passing-workflow-id", taskQueue: "message-passing-sample"));
```

To check the argument types required when sending messages -- and the return type for Queries and Updates -- refer to the corresponding handler method in the Workflow Definition.

:::warning Using Continue-as-New and Updates

- Temporal _does not_ support Continue-as-New functionality within Update handlers.
- Complete all handlers _before_ using Continue-as-New.
- Use Continue-as-New from your main Workflow Definition method, just as you would complete or fail a Workflow Execution.

:::

### Send a Query {#send-query}

Call a Query method with [`WorkflowHandle.QueryAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_QueryAsync__1_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowQueryOptions_):

```csharp
var supportedLanguages = await workflowHandle.QueryAsync(wf => wf.GetLanguages(new(false)));
```

- Sending a Query doesn’t add events to a Workflow's Event History.

- You can send Queries to closed Workflow Executions within a Namespace's Workflow retention period.
  This includes Workflows that have completed, failed, or timed out.
  Querying terminated Workflows is not safe and, therefore, not supported.

- A Worker must be online and polling the Task Queue to process a Query.

### Send a Signal {#send-signal}

You can send a Signal to a Workflow Execution from a Temporal Client or from another Workflow Execution.
However, you can only send Signals to Workflow Executions that haven’t closed.

#### Send a Signal from a Client {#send-signal-from-client}

Use [`WorkflowHandle.SignalAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_SignalAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowSignalOptions_) from Client code to send a Signal:

```csharp
await workflowHandle.SignalAsync(wf => wf.ApproveAsync(new("MyUser")));
```

- The call returns when the server accepts the Signal; it does _not_ wait for the Signal to be delivered to the Workflow Execution.

- The [WorkflowExecutionSignaled](/references/events#workflowexecutionsignaled) Event appears in the Workflow's Event History.

#### Send a Signal from a Workflow {#send-signal-from-workflow}

A Workflow can send a Signal to another Workflow, known as an _External Signal_.
In this case you need to obtain a Workflow handle for the external Workflow.
Use `Workflow.GetExternalWorkflowHandle`, passing a running Workflow Id, to retrieve a typed Workflow handle:

```csharp
// ...

[Workflow]
public class WorkflowB
{
    [WorkflowRun]
    public async Task RunAsync()
    {
        var handle = Workflow.GetExternalWorkflowHandle<WorkflowA>("workflow-a");
        await handle.SignalAsync(wf => wf.YourSignalAsync("signal argument"));
    }

    // ...
```

When an External Signal is sent:

- A [SignalExternalWorkflowExecutionInitiated](/references/events#signalexternalworkflowexecutioninitiated) Event appears in the sender's Event History.
- A [WorkflowExecutionSignaled](/references/events#workflowexecutionsignaled) Event appears in the recipient's Event History.

#### Signal-With-Start {#signal-with-start}

Signal-With-Start allows a Client to send a Signal to a Workflow Execution, starting the Execution if it is not already running.
If there's a Workflow running with the given Workflow Id, it will be signaled.
If there isn't, a new Workflow will be started and immediately signaled.
To use Signal-With-Start, call `SignalWithStart` with a lambda expression invoking it:

```csharp
var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
var options = new WorkflowOptions(id: "your-signal-with-start-workflow", taskQueue: "signal-tq");
options.SignalWithStart((GreetingWorkflow wf) => wf.SubmitGreetingAsync("User Signal with Start"));
await client.StartWorkflowAsync((GreetingWorkflow wf) => wf.RunAsync(), options);
```

### Send an Update {#send-update-from-client}

An Update is a synchronous, blocking call that can change Workflow state, control its flow, and return a result.

A Client sending an Update must wait until the Server delivers the Update to a Worker.
Workers must be available and responsive.
If you need a response as soon as the Server receives the request, use a Signal instead.
Also note that you can't send Updates to other Workflow Executions.

- `WorkflowExecutionUpdateAccepted` is added to the Event History when the Worker confirms that the Update passed validation.
- `WorkflowExecutionUpdateCompleted` is added to the Event History when the Worker confirms that the Update has finished.

To send an Update to a Workflow Execution, you can:

- Call the Update method with `ExecuteUpdateAsync` from the Client and wait for the Update to complete.
  This code fetches an Update result:

  ```csharp
  var previousLanguage = await workflowHandle.ExecuteUpdateAsync(
    wf => wf.SetCurrentLanguageAsync(GreetingWorkflow.Language.Chinese));
  ```

- 2. Use `StartUpdateAsync` to receive a handle as soon as the Update is accepted.
     It returns an `UpdateHandle`

  - Use this `UpdateHandle` later to fetch your results.
  - Asynchronous Update handlers normally perform long-running async Activities.
  - `StartUpdateAsync` only waits until the Worker has accepted or rejected the Update, not until all asynchronous operations are complete.

  For example:

  ```csharp
  // Wait until the update is accepted
  var updateHandle = await workflowHandle.StartUpdateAsync(
      wf => wf.SetGreetingAsync(new HelloWorldInput("World")),
      new(waitForStage: WorkflowUpdateStage.Accepted));
  // Wait until the update is completed
  var updateResult = await updateHandle.GetResultAsync();
  ```

  For more details, see the "Async handlers" section.

#### Update-with-Start {#update-with-start}

:::tip

For open source server users, Temporal Server version [Temporal Server version 1.28](https://github.com/temporalio/temporal/releases/tag/v1.28.0) is recommended.

:::

[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:

- 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.

Use `ExecuteUpdateWithStartAsync` to start the Update and wait for the result in one go.

Alternatively, use `StartUpdateWithStartAsync` to start the Update and receive a `WorkflowUpdateHandle`, and then use `await updateHandle.GetResultAsync()` to retrieve the result from the Update.

These calls return once the requested Update wait stage has been reached, or when the request times out.

- You will need to provide a `WithStartWorkflowOperation` to define the Workflow that will be started if necessary, and its arguments.
- You must specify an [IdConflictPolicy](/workflow-execution/workflowid-runid#workflow-id-conflict-policy) when creating the `WithStartWorkflowOperation`.
  Note that a `WithStartWorkflowOperation` can only be used once.

Here's an example taken from the [UpdateWithStartLazyInit](https://github.com/temporalio/samples-dotnet/blob/main/src/UpdateWithStartLazyInit/Program.cs) sample:

```csharp
async Task<AddCartItemResult> AddCartItemAsync(string sessionId, ShoppingCartItem item)
{
    // Issue an update-with-start that will create the workflow if it does not
    // exist before attempting the update

    // Create the start operation
    var startOperation = WithStartWorkflowOperation.Create(
        (ShoppingCartWorkflow wf) => wf.RunAsync(),
        new(id: $"cart-{sessionId}", taskQueue: TaskQueue)
        {
            IdConflictPolicy = Temporalio.Api.Enums.V1.WorkflowIdConflictPolicy.UseExisting,
        });

    // Issue the update-with-start, swallowing item-unavailable failure
    decimal? subtotal;
    try
    {
        subtotal = await client.ExecuteUpdateWithStartWorkflowAsync(
            (ShoppingCartWorkflow wf) => wf.AddItemAsync(item),
            new(startOperation));
    }
    catch (WorkflowUpdateFailedException e) when (
        e.InnerException is ApplicationFailureException appErr && appErr.ErrorType == "ItemUnavailable")
    {
        // Set subtotal to null if item was not found
        subtotal = null;
    }

    return new(await startOperation.GetHandleAsync(), subtotal);
}
```

:::info NON-TYPE SAFE API CALLS

In real-world development, sometimes you may be unable to import Workflow Definition method signatures.
When you don't have access to the Workflow Definition or it isn't written in .NET, you can still use non-type safe APIs and dynamic method invocation.
Pass method names instead of method objects to:

- [`TemporalClient.StartWorkflowAsync`](https://dotnet.temporal.io/api/Temporalio.Client.TemporalClient.html#Temporalio_Client_TemporalClient_StartWorkflowAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowOptions_)
- [`WorkflowHandle.QueryAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_QueryAsync__1_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowQueryOptions_)
- [`WorkflowHandle.SignalAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_SignalAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowSignalOptions_)
- [`WorkflowHandle.ExecuteUpdateAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_ExecuteUpdateAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowUpdateOptions_)
- [`WorkflowHandle.StartUpdateAsync`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowHandle.html#Temporalio_Client_WorkflowHandle_StartUpdateAsync_System_String_System_Collections_Generic_IReadOnlyCollection_System_Object__Temporalio_Client_WorkflowUpdateStartOptions_)

Use non-type safe overloads of these APIs:

- [`TemporalClient.GetWorkflowHandle`](https://dotnet.temporal.io/api/Temporalio.Client.TemporalClient.html#Temporalio_Client_TemporalClient_GetWorkflowHandle_System_String_System_String_System_String_)
- [`Workflow.GetExternalWorkflowHandle`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html#Temporalio_Workflows_Workflow_GetExternalWorkflowHandle_System_String_System_String_)

:::

## Message handler patterns {#message-handler-patterns}

This section covers common write operations, such as Signal and Update handlers.
It doesn't apply to pure read operations, like Queries or Update Validators.

:::tip

For additional information, see [Inject work into the main Workflow](/handling-messages#injecting-work-into-main-workflow) and [Ensuring your messages are processed exactly once](/handling-messages#exactly-once-message-processing).

:::

### Add async handlers to use `await` {#async-handlers}

Signal and Update handlers can be asynchronous as well as blocking.
Using asynchronous calls allows you to `await` Activities, Child Workflows, [`Workflow.DelayAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_DelayAsync_System_Int32_System_Nullable_System_Threading_CancellationToken__) Timers, [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) wait conditions, etc.
This expands the possibilities for what can be done by a handler but it also means that handler executions and your main Workflow method are all running concurrently, with switching occurring between them at await calls.

It's essential to understand the things that could go wrong in order to use asynchronous handlers safely.
See [Workflow message passing](/encyclopedia/workflow-message-passing) for guidance on safe usage of async Signal and Update handlers, and the [Controlling handler concurrency](#control-handler-concurrency) and [Waiting for message handlers to finish](#wait-for-message-handlers) sections below.

The following code executes an Activity that simulates a network call to a remote service:

```csharp
public class MyActivities
{
    private static readonly Dictionary<Language, string> Greetings = new()
    {
        [Language.Arabic] = "مرحبا بالعالم",
        [Language.Chinese] = "你好，世界",
        [Language.English] = "Hello, world",
        [Language.French] = "Bonjour, monde",
        [Language.Hindi] = "नमस्ते दुनिया",
        [Language.Spanish] = "Hola mundo",
    };

    [Activity]
    public async Task<string?> CallGreetingServiceAsync(Language language)
    {
        // Pretend that we are calling a remove service
        await Task.Delay(200);
        return Greetings.TryGetValue(language, out var value) ? value : null;
    }
}
```

The following code modifies a `WorkflowUpdate` for asynchronous use of the preceding Activity:

```csharp
[Workflow]
public class GreetingWorkflow
{
    private readonly Mutex mutex = new();

    // ...

    [WorkflowUpdate]
    public async Task<Language> SetLanguageAsync(Language language)
    {
        // 👉 Use a mutex here to ensure that multiple calls to SetLanguageAsync are processed in order.
        await mutex.WaitOneAsync();
        try
        {
            if (!greetings.ContainsKey(language))
            {
                var greeting = Workflow.ExecuteActivityAsync(
                    (MyActivities acts) => acts.CallGreetingServiceAsync(language),
                    new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
                if (greeting == null)
                {
                    // 👉 An update validator cannot be async, so cannot be used to check that the remote
                    // CallGreetingServiceAsync supports the requested language. Throwing ApplicationFailureException
                    // will fail the Update, but the WorkflowExecutionUpdateAccepted event will still be
                    // added to history.
                    throw new ApplicationFailureException(
                        $"Greeting service does not support {language}");
                }
                greetings[language] = greeting;
            }
            var previousLanguage = CurrentLanguage;
            CurrentLanguage = language;
            return previousLanguage;
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }
}
```

After updating the code for asynchronous calls, your Update handler can schedule an Activity and await the result.
Although an async Signal handler can initiate similar network tasks, using an Update handler allows the Client to receive a result or error once the Activity completes.
This lets your Client track the progress of asynchronous work performed by the Update's Activities, Child Workflows, etc.

### Add wait conditions to block {#block-with-wait}

Sometimes, async Signal or Update handlers need to meet certain conditions before they should continue.
Using a wait condition with [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) sets a function that prevents the code from proceeding until the condition returns `true`.
This is an important feature that helps you control your handler logic.

Here are two important use cases for `Workflow.WaitConditionAsync`:

- Waiting in a handler until it is appropriate to continue.
- Waiting in the main Workflow until all active handlers have finished.

The condition state you're waiting for can be updated by and reflect any part of the Workflow code.
This includes the main Workflow method, other handlers, or child coroutines spawned by the main Workflow method, and so forth.

#### Use wait conditions in handlers {#wait-in-handlers}

Sometimes, async Signal or Update handlers need to meet certain conditions before they should continue.
Using a wait condition with [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) sets a function that prevents the code from proceeding until the condition returns `true`.
This is an important feature that helps you control your handler logic.

Consider a `ReadyForUpdateToExecute` method that runs before your Update handler executes.
The [`Workflow.WaitConditionAsync`](https://dotnet.temporal.io/api/Temporalio.Workflows.Workflow.html?#Temporalio_Workflows_Workflow_WaitConditionAsync_System_Func_System_Boolean__System_Int32_System_Nullable_System_Threading_CancellationToken__) call waits until your condition is met:

```csharp
[WorkflowUpdate]
public async Task<string> MyUpdateAsync(UpdateInput updateInput)
{
    await Workflow.WaitConditionAsync(() => ReadyForUpdateToExecute(updateInput));
    // ...
}
```

Remember: Handlers can execute before the main Workflow method starts.

#### Ensure your handlers finish before the Workflow completes {#wait-for-message-handlers}

Workflow wait conditions can ensure your handler completes before a Workflow finishes.
When your Workflow uses async Signal or Update handlers, your main Workflow method can return or continue-as-new while a handler is still waiting on an async task, such as an Activity result.
The Workflow completing may interrupt the handler before it finishes crucial work and cause Client errors when trying retrieve Update results.
Use `Workflow.AllHandlersFinished` to address this problem and allow your Workflow to end smoothly:

```csharp
[Workflow]
public class MyWorkflow
{
    [WorkflowRun]
    public async Task<string> RunAsync()
    {
        // ...
        await Workflow.WaitConditionAsync(() => Workflow.AllHandlersFinished);
        return "workflow-result";
    }

    // ...
```

By default, your Worker will log a warning when you allow a Workflow Execution to finish with unfinished handler executions.
You can silence these warnings on a per-handler basis by passing the `UnfinishedPolicy` argument to the [`WorkflowSignalAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowSignalAttribute.html) / [`WorkflowUpdateAttribute`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowUpdateAttribute.html) decorator:

```csharp
[WorkflowUpdate(UnfinishedPolicy = HandlerUnfinishedPolicy.Abandon)]
public async Task MyUpdateAsync()
{
    // ...
```

See [Finishing handlers before the Workflow completes](/handling-messages#finishing-message-handlers) for more information.

### Use `[WorkflowInit]` to operate on Workflow input before any handler executes

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 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.
The constructor and `RunAsync` must have the same parameters with the same types:

```csharp
[Workflow]
public class WorkflowInitWorkflow
{
    public record Input(string Name);

    private readonly string nameWithTitle;
    private bool titleHasBeenChecked;

    [WorkflowInit]
    public WorkflowInitWorkflow(Input input) =>
        nameWithTitle = $"Sir {input.Name}";

    [WorkflowRun]
    public async Task<string> RunAsync(Input ignored)
    {
        await Workflow.WaitConditionAsync(() => titleHasBeenChecked);
        return $"Hello, {nameWithTitle}";
    }

    [WorkflowUpdate]
    public async Task<bool> CheckTitleValidityAsync()
    {
        // The handler is now guaranteed to see the workflow input after it has
        // been processed by the constructor.
        var valid = await Workflow.ExecuteActivityAsync(
            (MyActivities acts) -> acts.CheckTitleValidityAsync(nameWithTitle),
            new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
        titleHasBeenChecked = true;
        return valid;
    }
}
```

### Use locks to prevent concurrent handler execution {#control-handler-concurrency}

Concurrent processes can interact in unpredictable ways.
Incorrectly written [concurrent message-passing](/handling-messages#message-handler-concurrency) code may not work correctly when multiple handler instances run simultaneously.
Here's an example of a pathological case:

```csharp
[Workflow]
public class MyWorkflow
{
    // ...

    [WorkflowSignal]
    public async Task BadHandlerAsync()
    {
        var data = await Workflow.ExecuteActivityAsync(
            (MyActivities acts) => acts.FetchDataAsync(),
            new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
        this.x = data.X;
        // 🐛🐛 Bug!! If multiple instances of this handler are executing concurrently, then
        // there may be times when the Workflow has this.x from one Activity execution and this.y from another.
        await Workflow.DelayAsync(1000);
        this.y = data.Y;
    }
}
```

Coordinating access with [`Workflows.Mutex`](https://dotnet.temporal.io/api/Temporalio.Workflows.Mutex.html), a mutual exclusion lock, corrects this code.
Locking makes sure that only one handler instance can execute a specific section of code at any given time:

```csharp
[Workflow]
public class MyWorkflow
{
    private readonly Mutex mutex = new();

    // ...

    [WorkflowSignal]
    public async Task SafeHandlerAsync()
    {
        await mutex.WaitOneAsync();
        try
        {
            var data = await Workflow.ExecuteActivityAsync(
                (MyActivities acts) => acts.FetchDataAsync(),
                new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
            this.x = data.X;
            // ✅ OK: the scheduler may switch now to a different handler execution, or to the main workflow
            // method, but no other execution of this handler can run until this execution finishes.
            await Workflow.DelayAsync(1000);
            this.y = data.Y;
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }
}
```

For additional concurrency options, you can use [`Workflows.Semaphore`](https://dotnet.temporal.io/api/Temporalio.Workflows.Semaphore.html).
Semaphores manage access to shared resources and coordinate the order in which threads or processes execute.

## Message handler troubleshooting {#message-handler-troubleshooting}

When sending a Signal, Update, or Query to a Workflow, your Client might encounter the following errors:

- **The Client can't contact the server**:
  You'll receive a [`Temporalio.Exceptions.RpcException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.html) exception whose `Code` property is [`RpcException.StatusCode`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.StatusCode.html) with a status of `Unavailable` (after some retries).

- **The Workflow does not exist**:
  You'll receive a [`Temporalio.Exceptions.RpcException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.html) exception whose `Code` property is [`RpcException.StatusCode`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.StatusCode.html) with a status of `NotFound`.

See [Exceptions in message handlers](/handling-messages#exceptions) for a non–.NET-specific discussion of this topic.

### Problems when sending a Signal {#signal-problems}

When using Signal, the only exception that will result from your requests during its execution is `RpcException`.
All handlers may experience additional exceptions during the initial (pre-Worker) part of a handler request lifecycle.

For Queries and Updates, the Client waits for a response from the Worker.
If an issue occurs during the handler Execution by the Worker, the Client may receive an exception.

### Problems when sending an Update {#update-problems}

When working with Updates, you may encounter these errors:

- **No Workflow Workers are polling the Task Queue**:
  Your request will be retried by the SDK Client indefinitely.
  Use a `CancellationToken` in your [RPC options](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowUpdateOptions.html#Temporalio_Client_WorkflowUpdateOptions_Rpc) to cancel the Update.
  This raises a [Temporalio.Exceptions.WorkflowUpdateRpcTimeoutOrCanceledException](https://dotnet.temporal.io/api/Temporalio.Exceptions.WorkflowUpdateRpcTimeoutOrCanceledException.html) exception .

- **Update failed**: You'll receive a [`Temporalio.Exceptions.WorkflowUpdateFailedException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.WorkflowUpdateFailedException.html) exception.
  There are two ways this can happen:

  - The Update was rejected by an Update validator defined in the Workflow alongside the Update handler.

  - The Update failed after having been accepted.

  Update failures are like [Workflow failures](/references/failures).
  Issues that cause a Workflow failure in the main method also cause Update failures in the Update handler.
  These might include:

      - A failed Child Workflow
      - A failed Activity (if the Activity retries have been set to a finite number)
      - The Workflow author raising `ApplicationFailure`
      - Any error listed in [`TemporalWorkerOptions.WorkflowFailureExceptionTypes`](https://dotnet.temporal.io/api/Temporalio.Worker.TemporalWorkerOptions.html#Temporalio_Worker_TemporalWorkerOptions_WorkflowFailureExceptionTypes) on the Worker or [`WorkflowAttribute.FailureExceptionTypes`](https://dotnet.temporal.io/api/Temporalio.Workflows.WorkflowAttribute.html#Temporalio_Workflows_WorkflowAttribute_FailureExceptionTypes) on the Workflow (empty by default)

- **The handler caused the Workflow Task to fail**:
  A [Workflow Task Failure](/references/failures) causes the server to retry Workflow Tasks indefinitely. What happens to your Update request depends on its stage:
  - If the request hasn't been accepted by the server, you receive a `FAILED_PRECONDITION` [`Temporalio.Exceptions.RpcException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.html) exception.
  - If the request has been accepted, it is durable.
    Once the Workflow is healthy again after a code deploy, use an [`UpdateHandle`](https://dotnet.temporal.io/api/Temporalio.Client.WorkflowUpdateHandle.html) to fetch the Update result.

- **The Workflow finished while the Update handler execution was in progress**:
  You'll receive a [`Temporalio.Exceptions.RpcException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.html) "workflow execution already completed".

  This will happen if the Workflow finished while the Update handler execution was in progress, for example because

  - The Workflow was canceled or failed.

  - The Workflow completed normally or continued-as-new and the Workflow author did not [wait for handlers to be finished](/handling-messages#finishing-message-handlers).

### Problems when sending a Query {#query-problems}

When working with Queries, you may encounter these errors:

- **There is no Workflow Worker polling the Task Queue**:
  You'll receive a [`Temporalio.Exceptions.RpcException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.html) on which the `Code` is a [`RpcException.StatusCode`](https://dotnet.temporal.io/api/Temporalio.Exceptions.RpcException.StatusCode.html) with a status of `FailedPrecondition`.

- **Query failed**:
  You'll receive a [`Temporalio.Exceptions.WorkflowQueryFailedException`](https://dotnet.temporal.io/api/Temporalio.Exceptions.WorkflowQueryFailedException.html) exception if something goes wrong during a Query.
  Any exception in a Query handler will trigger this error.
  This differs from Signal and Update requests, where exceptions can lead to Workflow Task Failure instead.

- **The handler caused the Workflow Task to fail.**
  This would happen, for example, if the Query handler blocks the thread for too long without yielding.

## Dynamic Handler {#dynamic-handler}

Temporal supports Dynamic Queries, Signals, Updates, Workflows, and Activities.
These are unnamed handlers that are invoked if no other statically defined handler with the given name exists.

Dynamic Handlers provide flexibility to handle cases where the names of Queries, Signals, Updates, Workflows, or Activities, aren't known at run time.

:::caution

Dynamic Handlers should be used judiciously as a fallback mechanism rather than the primary approach.
Overusing them can lead to maintainability and debugging issues down the line.

Instead, Signals, Queries, Workflows, or Activities should be defined statically whenever possible, with clear names that indicate their purpose.
Use static definitions as the primary way of structuring your Workflows.

Reserve Dynamic Handlers for cases where the handler names are not known at compile time and need to be looked up dynamically at runtime.
They are meant to handle edge cases and act as a catch-all, not as the main way of invoking logic.

:::

### Set a Dynamic Query {#set-a-dynamic-query}

A Dynamic Query in Temporal is a Query method that is invoked dynamically at runtime if no other Query with the same name is registered.
A Query can be made dynamic by setting `Dynamic` to `true` on the `[WorkflowQuery]` attribute.
Only one Dynamic Query can be present on a Workflow.

The Query Handler parameters must accept a `string` name and `Temporalio.Converters.IRawValue[]` for the arguments.
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
[WorkflowQuery(Dynamic = true)]
public string DynamicQueryAsync(string queryName, IRawValue[] args)
{
    var input = Workflow.PayloadConverter.ToValue<MyStatusParam>(args.Single());
    return statuses[input.Type];
}
```

### Set a Dynamic Signal {#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.
A Signal can be made dynamic by setting `Dynamic` to `true` on the `[WorkflowSignal]` attribute.
Only one Dynamic Signal can be present on a Workflow.

The Signal Handler parameters must accept a `string` name and `Temporalio.Converters.IRawValue[]` for the arguments.
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
[WorkflowSignal(Dynamic = true)]
public async Task DynamicSignalAsync(string signalName, IRawValue[] args)
{
    var input = Workflow.PayloadConverter.ToValue<DoSomethingParam>(args.Single());
    pendingThings.Add(input);
}
```

### Set a Dynamic Update {#set-a-dynamic-update}

A Dynamic Update in Temporal is an Update that is invoked dynamically at runtime if no other Update with the same input is registered.
An Update can be made dynamic by setting `Dynamic` to `true` on the `[WorkflowUpdate]` attribute.
Only one Dynamic Update can be present on a Workflow.

The Update Handler parameters must accept a `string` name and `Temporalio.Converters.IRawValue[]` for the arguments.
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
[WorkflowUpdate(Dynamic = true)]
public async Task<string> DynamicUpdateAsync(string updateName, IRawValue[] args)
{
    var input = Workflow.PayloadConverter.ToValue<DoSomethingParam>(args.Single());
    pendingThings.Add(input);
    return statuses[input.Type];
}
```

---

## Observability - .NET SDK

This page covers features related to viewing the state of the application, including:

- [Metrics](#metrics)
- [Tracing](#tracing)
- [Logging](#logging)
- [Visibility](#visibility)

The observability feature guide covers the many ways to view the current state of your [Temporal Application](/temporal#temporal-application).
This includes the ways to view which [Workflow Executions](/workflow-execution) are tracked by the [Temporal Platform](/temporal#temporal-platform) and the state of any specified Workflow Execution, either currently or at points of an 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).

- For an overview of Prometheus and Grafana integration, refer to the [Monitoring](/self-hosted-guide/monitoring) guide.
- For a list of metrics, see the [SDK metrics reference](/references/sdk-metrics).
- For an end-to-end example that exposes metrics with the .NET SDK, refer to the [samples-dotnet](https://github.com/temporalio/samples-dotnet/tree/main/src/OpenTelemetry) repo.

Metrics in .NET are configured on the `Metrics` property of the `Telemetry` property on the `TemporalRuntime`. That object should be created globally and should be used for all clients; therefore, you should configure this before any other Temporal code.

### 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
using Temporalio.Client;
using Temporalio.Runtime;

var runtime = new TemporalRuntime(new()
{
    Telemetry = new() { Metrics = new() { Prometheus = new("0.0.0.0:9000") } },
});
var client = await Temporalio.ConnectAsync(new("localhost:7233") { Runtime = runtime });
```

### 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.

```csharp
using System.Diagnostics.Metrics;
using Temporalio.Client;
using Temporalio.Extensions.DiagnosticSource;
using Temporalio.Runtime;

// Create .NET meter
using var meter = new Meter("My.Meter");
// Can create MeterListener or OTel meter provider here...

// Create Temporal runtime with a custom metric meter for that meter
var runtime = new TemporalRuntime(new()
{
    Telemetry = new()
    {
        Metrics = new() { CustomMetricMeter = new CustomMetricMeter(meter) },
    },
});
var client = await Temporalio.ConnectAsync(new("localhost:7233") { Runtime = runtime });
```

## 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.

The [`Temporalio.Extensions.OpenTelemetry.TracingInterceptor`](https://dotnet.temporal.io/api/Temporalio.Extensions.OpenTelemetry.TracingInterceptor.html) class can be set as an interceptor in the client options.

When your Client is connected, spans are created for all Client calls, Activities, and Workflow invocations on the Worker.
Spans are created and serialized through the server to give one trace for a Workflow Execution.

## 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.
During development or troubleshooting, you might use debug or even trace.
In production, you might use info or warn to avoid excessive log volume.

The logger supports the following logging levels:

| Level   | Use                                                                                                       |
| ------- | --------------------------------------------------------------------------------------------------------- |
| `TRACE` | The most detailed level of logging, used for very fine-grained information.                               |
| `DEBUG` | Detailed information, typically useful for debugging purposes.                                            |
| `INFO`  | General information about the application's operation.                                                    |
| `WARN`  | Indicates potentially harmful situations or minor issues that don't prevent the application from working. |
| `ERROR` | Indicates error conditions that might still allow the application to continue running.                    |

The Temporal SDK core normally uses `WARN` as its default logging level.

Logging uses the .NET standard logging APIs.
The `LoggerFactory` can be set in the client.
The following example shows logging on the console and sets the level to `Information`.

```csharp
var client = await TemporalClient.ConnectAsync(new("localhost:7233")
{
    LoggerFactory = LoggerFactory.Create(builder =>
        builder.
            AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] ").
            SetMinimumLevel(LogLevel.Information)),
});
```

You can log from a Workflow using `Workflow.Logger` which is an instance of .NET's `ILogger`.

```csharp
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.

You can do this with [Search Attributes](/search-attribute).

- [Default Search Attributes](/search-attribute#default-search-attribute) like `WorkflowType`, `StartTime` and `ExecutionStatus` are automatically added to Workflow Executions.
- _Custom Search Attributes_ can contain their own domain-specific data (like `customerId` or `numItems`).
- A few [generic Custom Search Attributes](/search-attribute#custom-search-attribute) like `CustomKeywordField` and `CustomIntField` are created by default in Temporal's [Docker Compose](https://github.com/temporalio/docker-compose).

The steps to using custom Search Attributes are:

- Create a new Search Attribute in your Temporal Service in the CLI or Web UI.
  - For example: `temporal operator search-attribute create --name CustomKeywordField --type Text`
    - Replace `CustomKeywordField` with the name of your Search Attribute.
    - Replace `Text` with a type value associated with your Search Attribute: `Text` | `Keyword` | `Int` | `Double` | `Bool` | `Datetime` | `KeywordList`
- Set the value of the Search Attribute for a Workflow Execution:
  - On the Client by including it as an option when starting the Execution.
  - In the Workflow by calling `UpsertTypedSearchAttributes`.
- Read the value of the Search Attribute:
  - On the Client by calling `Describe` on a `WorkflowHandle`.
  - In the Workflow by looking at `WorkflowInfo`.
- Query Workflow Executions by the Search Attribute using a [List Filter](/list-filter):
  - [In the Temporal CLI](/cli/operator#list-2)
  - In code by calling `ListWorkflowsAsync`.

### 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.

```csharp
await foreach (var wf in client.ListWorkflowsAsync("WorkflowType='GreetingWorkflow'"))
{
    Console.WriteLine("Workflow: {0}", wf.Id);
}
```

### 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`.
Typed search attributes are a `SearchAttributeCollection` created with a builder.

```csharp
// This only needs to be created once, so it is common to make it a static readonly even though we
// create inline here for demonstration
var myKeywordAttributeKey = SearchAttributeKey.CreateKeyword("MyKeywordAttribute");

// Start workflow with the search attribute collection
var handle = await client.StartWorkflowAsync(
    (MyWorkflow wf) => wf.RunAsync(),
    new(id: "my-workflow-id", taskQueue: "my-task-queue")
    {
        TypedSearchAttributes = new SearchAttributeCollection.Builder().
            Set(myKeywordAttributeKey, "SomeKeywordValue").
            ToSearchAttributeCollection(),
    });
```

### 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.
Keys can be predefined for reuse.

```csharp
// These only need to be created once, so it is common to make them static readonly even though we
// create inline here for demonstration
var myKeywordAttributeKey = SearchAttributeKey.CreateKeyword("MyKeywordAttribute");
var myTextAttributeKey = SearchAttributeKey.CreateText("MyTextAttribute");

// Add/Update the keyword one and remove the text one
Workflow.UpsertTypedSearchAttributes(
    myKeywordAttributeKey.ValueSet("SomeKeywordValue"),
    myTextAttrbiuteKey.ValueUnset());
```

---

## Schedules - .NET SDK

This page shows how to do the following:

- [Schedule a Workflow](#schedule-a-workflow)
- [Create a Scheduled Workflow](#create-a-workflow)
- [Backfill a Scheduled Workflow](#backfill-a-scheduled-workflow)
- [Delete a Scheduled Workflow](#delete-a-scheduled-workflow)
- [Describe a Scheduled Workflow](#describe-a-scheduled-workflow)
- [List a Scheduled Workflow](#list-a-scheduled-workflow)
- [Pause a Scheduled Workflow](#pause-a-scheduled-workflow)
- [Trigger a Scheduled Workflow](#trigger-a-scheduled-workflow)
- [Update a Scheduled Workflow](#update-a-scheduled-workflow)
- [Use Start Delay](#start-delay)

## 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_)
method on the Client.
Then pass the Schedule ID and the Schedule object to the method to create a Scheduled Workflow Execution.
Set the Schedule's `Action` property to an instance of `ScheduleActionStartWorkflow` to schedule a Workflow Execution.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = await client.CreateScheduleAsync(
    "my-schedule-id",
    new(
        Action: ScheduleActionStartWorkflow.Create(
            (MyWorkflow wf) => wf.RunAsync(),
            new(id: "my-workflow-id", taskQueue: "my-task-queue")),
        Spec: new()
        {
            Intervals = new List<ScheduleIntervalSpec> { new(Every: TimeSpan.FromDays(5)) },
        }));
```

:::tip Schedule Auto-Deletion

Once a Schedule has completed creating all its Workflow Executions, the Temporal Service deletes it since it won’t fire again.
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_)
method on the Schedule Handle.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
var now = DateTime.Now;
await handle.BackfillAsync(new List<ScheduleBackfill>
{
    new(
        StartAt: now - TimeSpan.FromDays(30),
        EndAt: now - TimeSpan.FromDays(20),
        Overlap: ScheduleOverlapPolicy.AllowAll),
});
```

### 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.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
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.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
var desc = await handle.DescribeAsync();
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.
This returns an async enumerable.
If a schedule is added or deleted, it may not be available in the list immediately.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));
await foreach (var desc in client.ListSchedulesAsync())
{
    Console.WriteLine("Schedule info: {0}", desc.Info);
}
```

### 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.
You can pass a note to the `PauseAsync()` method to provide a reason for pausing the schedule.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
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.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
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.
This method accepts a callback that provides input with the current schedule.
A new schedule can be created and returned from that callback to perform the update.

```csharp
using Temporalio.Client;
using Temporalio.Client.Schedules;

var client = await TemporalClient.ConnectAsync(new("localhost:7233"));

var handle = client.GetScheduleHandle("my-schedule-id");
await handle.UpdateAsync(input =>
{
    var newAction =  ScheduleActionStartWorkflow.Create(
        (MyWorkflow wf) => wf.RunAsync(),
        new(id: "my-workflow-id", taskQueue: "my-task-queue"));
    return new(input.Description.Schedule with { Action = newAction });
});
```

## 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.

```csharp
var handle = await client.StartWorkflowAsync(
    (MyWorkflow wf) => wf.RunAsync(),
    new(id: "my-workflow-id", taskQueue: "my-task-queue")
    {
        StartDelay = TimeSpan.FromHours(3),
    });
```

---

## Set up your local with the .NET SDK