This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Dapr Workflow .NET SDK

Get up and running with Dapr Workflow and the Dapr .NET SDK

1 - DaprWorkflowClient lifetime management and registration

Learn how to configure the DaprWorkflowClient lifetime management and dependency injection

Lifetime management

A DaprWorkflowClient holds access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar as well as other types used in the management and operation of Workflows. DaprWorkflowClient implements IAsyncDisposable to support eager cleanup of resources.

Dependency Injection

The AddDaprWorkflow() method will register the Dapr workflow services with ASP.NET Core dependency injection. This method requires an options delegate that defines each of the workflows and activities you wish to register and use in your application.

Singleton Registration

By default, the AddDaprWorkflow method registers the DaprWorkflowClient and associated services using a singleton lifetime. This means that the services are instantiated only a single time.

The following is an example of how registration of the DaprWorkflowClient as it would appear in a typical Program.cs file:

builder.Services.AddDaprWorkflow(options => {
    options.RegisterWorkflow<YourWorkflow>();
    options.RegisterActivity<YourActivity>();
});

var app = builder.Build();
await app.RunAsync();

Scoped Registration

While this may generally be acceptable in your use case, you may instead wish to override the lifetime specified. This is done by passing a ServiceLifetime argument in AddDaprWorkflow. For example, you may wish to inject another scoped service into your ASP.NET Core processing pipeline that needs context used by the DaprClient that wouldn’t be available if the former service were registered as a singleton.

This is demonstrated in the following example:

builder.Services.AddDaprWorkflow(options => {
    options.RegisterWorkflow<YourWorkflow>();
    options.RegisterActivity<YourActivity>();
}, ServiceLifecycle.Scoped);

var app = builder.Build();
await app.RunAsync();

Transient Registration

Finally, Dapr services can also be registered using a transient lifetime meaning that they will be initialized every time they’re injected. This is demonstrated in the following example:

builder.Services.AddDaprWorkflow(options => {
    options.RegisterWorkflow<YourWorkflow>();
    options.RegisterActivity<YourActivity>();
}, ServiceLifecycle.Transient);

var app = builder.Build();
await app.RunAsync();

Create a DaprWorkflowClient instance

In an ASP.Net Core application, you can inject the DaprWorkflowClient into methods or controllers via method or constructor injection. This example demonstrates method injection in a minimal API scenario:

app.MapPost("/start", async (
    [FromServices] DaprWorkflowClient daprWorkflowClient,
    Order order
    ) => {
        var instanceId = await daprWorkflowClient.ScheduleNewWorkflowAsync(
            nameof(OrderProcessingWorkflow),
            input: order);

        return Results.Accepted(instanceId);
});

To create a DaprWorkflowClient instance in a console app, retrieve it from the ServiceProvider:

using var scope = host.Services.CreateAsyncScope();
var daprWorkflowClient = scope.ServiceProvider.GetRequiredService<DaprWorkflowClient>();

Now, you can use this client to perform workflow management operations such as starting, pausing, resuming, and terminating a workflow instance. See Workflow management operations with DaprWorkflowClient for more information on these operations.

Injecting Services into Workflow Activities

Workflow activities support the same dependency injection that developers have come to expect of modern C# applications. Assuming a proper registration at startup, any such type can be injected into the constructor of the workflow activity and available to utilize during the execution of the workflow. This makes it simple to add logging via an injected ILogger or access to other Dapr building blocks by injecting DaprClient or DaprJobsClient, for example.

internal sealed class SquareNumberActivity : WorkflowActivity<int, int>
{
    private readonly ILogger _logger;
    
    public MyActivity(ILogger logger)
    {
        this._logger = logger;
    }
    
    public override Task<int> RunAsync(WorkflowActivityContext context, int input) 
    {
        this._logger.LogInformation("Squaring the value {number}", input);
        var result = input * input;
        this._logger.LogInformation("Got a result of {squareResult}", result);
        
        return Task.FromResult(result);
    }
}

Using ILogger in Workflow

Because workflows must be deterministic, it is not possible to inject arbitrary services into them. For example, if you were able to inject a standard ILogger into a workflow and it needed to be replayed because of an error, subsequent replay from the event source log would result in the log recording additional operations that didn’t actually take place a second or third time because their results were sourced from the log. This has the potential to introduce a significant amount of confusion. Rather, a replay-safe logger is made available for use within workflows. It will only log events the first time the workflow runs and will not log anything whenever the workflow is being replaced.

This logger can be retrieved from a method present on the WorkflowContext available on your workflow instance and otherwise used precisely as you might otherwise use an ILogger instance.

An end-to-end sample demonstrating this can be seen in the .NET SDK repository but a brief extraction of this sample is available below.

public class OrderProcessingWorkflow : Workflow<OrderPayload, OrderResult>
{
    public override async Task<OrderResult> RunAsync(WorkflowContext context, OrderPayload order)
    {
        string orderId = context.InstanceId;
        var logger = context.CreateReplaySafeLogger<OrderProcessingWorkflow>(); //Use this method to access the logger instance

        logger.LogInformation("Received order {orderId} for {quantity} {name} at ${totalCost}", orderId, order.Quantity, order.Name, order.TotalCost);
        
        //...
    }
}

Next steps

2 - Workflow management operations with DaprWorkflowClient

Learn how to use the DaprWorkflowClient to manage workflows

Workflow management operations with DaprWorkflowClient

The DaprWorkflowClient class provides methods to manage workflow instances. Below are the operations you can perform using the DaprWorkflowClient.

Schedule a new workflow instance

To start a new workflow instance, use the ScheduleNewWorkflowAsync method. This method requires the workflow type name and an input required by the workflow. The workflow instancedId is an optional argument; if not provided, a new GUID is generated by the DaprWorkflowClient. The final optional argument is a startTime of type DateTimeOffset which can be used to define when the workflow instance should start. The method returns the instanceId of the scheduled workflow which is used for other workflow management operations.

var instanceId = $"order-workflow-{Guid.NewGuid().ToString()[..8]}";
var input = new Order("Paperclips", 1000, 9.95);
await daprWorkflowClient.ScheduleNewWorkflowAsync(
  nameof(OrderProcessingWorkflow),
  instanceId,
  input);

Retrieve the status of a workflow instance

To get the current status of a workflow instance, use the GetWorkflowStateAsync method. This method requires the instance ID of the workflow and returns a WorkflowStatus object containing details about the workflow’s current state.

var workflowStatus = await daprWorkflowClient.GetWorkflowStateAsync(instanceId);

Raise an event to a running workflow instance

To send an event to a running workflow instance that is waiting for an external event, use the RaiseEventAsync method. This method requires the instance ID of the workflow, the name of the event, and optionally the event payload.

await daprWorkflowClient.RaiseEventAsync(instanceId, "Approval", true);

Suspend a running workflow instance

A running workflow instance can be paused using the SuspendWorkflowAsync method. This method requires the instance ID of the workflow. You can optionally provide a reason for suspending the workflow.

await daprWorkflowClient.SuspendWorkflowAsync(instanceId);

Resume a suspended workflow instance

A suspended workflow instance can be resumed using the ResumeWorkflowAsync method. This method requires the instance ID of the workflow. You can optionally provide a reason for resuming the workflow.

await daprWorkflowClient.ResumeWorkflowAsync(instanceId);

Terminate a workflow instance

To terminate a workflow instance, use the TerminateWorkflowAsync method. This method requires the instance ID of the workflow. You can optionally provide an output argument of type string. Terminating a workflow instance will also terminal all child workflow instances but it has no impact on in-flight activity executions.

await daprWorkflowClient.TerminateWorkflowAsync(instanceId);

Purge a workflow instance

To remove the workflow instance history from the Dapr Workflow state store, use the PurgeWorkflowAsync method. This method requires the instance ID of the workflow. Only completed, failed, or terminated workflow instances can be purged.

await daprWorkflowClient.PurgeWorkflowAsync(instanceId);

Next steps

3 - .NET Workflow Examples

Explore Dapr Workflow code examples on GitHub

Workflow tutorials in the Dapr Quickstarts repository

The Dapr Quickstarts repository on GitHub includes many workflow tutorials that showcase the various workflow patterns and how to use the workflow management operations. You can find these tutorials in the quickstarts/tutorials/workflow/csharp folder.

Workflow examples in the .NET SDK repository

The Dapr .NET SDK repository on GitHub contains several examples demonstrating how to use Dapr Workflows with .NET. You can find these examples in the examples/Workflow folder.

Next steps