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 usage

Essential tips and advice for using DaprWorkflowClient

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 will register the DaprWorkflowClient and associated services using a singleton lifetime. This means that the services will be 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();

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);
        
        //...
    }
}

2 - How to: Author and manage Dapr Workflow in the .NET SDK

Learn how to author and manage Dapr Workflow using the .NET SDK

Let’s create a Dapr workflow and invoke it using the console. In the provided order processing workflow example, the console prompts provide directions on how to both purchase and restock items. In this guide, you will:

  • Deploy a .NET console application (WorkflowConsoleApp).
  • Utilize the .NET workflow SDK and API calls to start and query workflow instances.

In the .NET example project:

  • The main Program.cs file contains the setup of the app, including the registration of the workflow and workflow activities.
  • The workflow definition is found in the Workflows directory.
  • The workflow activity definitions are found in the Activities directory.

Prerequisites

Set up the environment

Clone the .NET SDK repo.

git clone https://github.com/dapr/dotnet-sdk.git

From the .NET SDK root directory, navigate to the Dapr Workflow example.

cd examples/Workflow

Run the application locally

To run the Dapr application, you need to start the .NET program and a Dapr sidecar. Navigate to the WorkflowConsoleApp directory.

cd WorkflowConsoleApp

Start the program.

dotnet run

In a new terminal, navigate again to the WorkflowConsoleApp directory and run the Dapr sidecar alongside the program.

dapr run --app-id wfapp --dapr-grpc-port 4001 --dapr-http-port 3500

Dapr listens for HTTP requests at http://localhost:3500 and internal workflow gRPC requests at http://localhost:4001.

Start a workflow

To start a workflow, you have two options:

  1. Follow the directions from the console prompts.
  2. Use the workflow API and send a request to Dapr directly.

This guide focuses on the workflow API option.

Run the following command to start a workflow.

curl -i -X POST http://localhost:3500/v1.0/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 \
  -H "Content-Type: application/json" \
  -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}'
curl -i -X POST http://localhost:3500/v1.0/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 `
  -H "Content-Type: application/json" `
  -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}'

If successful, you should see a response like the following:

{"instanceID":"12345678"}

Send an HTTP request to get the status of the workflow that was started:

curl -i -X GET http://localhost:3500/v1.0/workflows/dapr/12345678

The workflow is designed to take several seconds to complete. If the workflow hasn’t completed when you issue the HTTP request, you’ll see the following JSON response (formatted for readability) with workflow status as RUNNING:

{
  "instanceID": "12345678",
  "workflowName": "OrderProcessingWorkflow",
  "createdAt": "2023-05-10T00:42:03.911444105Z",
  "lastUpdatedAt": "2023-05-10T00:42:06.142214153Z",
  "runtimeStatus": "RUNNING",
  "properties": {
    "dapr.workflow.custom_status": "",
    "dapr.workflow.input": "{\"Name\": \"Paperclips\", \"TotalCost\": 99.95, \"Quantity\": 1}"
  }
}

Once the workflow has completed running, you should see the following output, indicating that it has reached the COMPLETED status:

{
  "instanceID": "12345678",
  "workflowName": "OrderProcessingWorkflow",
  "createdAt": "2023-05-10T00:42:03.911444105Z",
  "lastUpdatedAt": "2023-05-10T00:42:18.527704176Z",
  "runtimeStatus": "COMPLETED",
  "properties": {
    "dapr.workflow.custom_status": "",
    "dapr.workflow.input": "{\"Name\": \"Paperclips\", \"TotalCost\": 99.95, \"Quantity\": 1}",
    "dapr.workflow.output": "{\"Processed\":true}"
  }
}

When the workflow has completed, the stdout of the workflow app should look like:

info: WorkflowConsoleApp.Activities.NotifyActivity[0]
      Received order 12345678 for Paperclips at $99.95
info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
      Reserving inventory: 12345678, Paperclips, 1
info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
      Processing payment: 12345678, 99.95, USD
info: WorkflowConsoleApp.Activities.NotifyActivity[0]
      Order 12345678 processed successfully!

If you have Zipkin configured for Dapr locally on your machine, then you can view the workflow trace spans in the Zipkin web UI (typically at http://localhost:9411/zipkin/).

Demo

Watch this video demonstrating .NET Workflow:

Next steps