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

Return to the regular view of this page.

Getting started with the Dapr client .NET SDK

How to get up and running with the Dapr .NET SDK

The Dapr client package allows you to interact with other Dapr applications from a .NET application.

Building blocks

The .NET SDK allows you to interface with all of the Dapr building blocks.

Invoke a service

HTTP

You can either use the DaprClient or System.Net.Http.HttpClient to invoke your services.

using var client = new DaprClientBuilder().
                UseTimeout(TimeSpan.FromSeconds(2)). // Optionally, set a timeout
                Build(); 

// Invokes a POST method named "deposit" that takes input of type "Transaction"
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("routing", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");

// To set a timeout on the HTTP client:
client.Timeout = TimeSpan.FromSeconds(2);

var deposit = new Transaction  { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);

gRPC

You can use the DaprClient to invoke your services over gRPC.

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
var invoker = DaprClient.CreateInvocationInvoker(appId: myAppId, daprEndpoint: serviceEndpoint);
var client = new MyService.MyServiceClient(invoker);

var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
await client.MyMethodAsync(new Empty(), options);

Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);

Save & get application state

var client = new DaprClientBuilder().Build();

var state = new Widget() { Size = "small", Color = "yellow", };
await client.SaveStateAsync(storeName, stateKeyName, state, cancellationToken: cancellationToken);
Console.WriteLine("Saved State!");

state = await client.GetStateAsync<Widget>(storeName, stateKeyName, cancellationToken: cancellationToken);
Console.WriteLine($"Got State: {state.Size} {state.Color}");

await client.DeleteStateAsync(storeName, stateKeyName, cancellationToken: cancellationToken);
Console.WriteLine("Deleted State!");

Query State (Alpha)

var query = "{" +
                "\"filter\": {" +
                    "\"EQ\": { \"value.Id\": \"1\" }" +
                "}," +
                "\"sort\": [" +
                    "{" +
                        "\"key\": \"value.Balance\"," +
                        "\"order\": \"DESC\"" +
                    "}" +
                "]" +
            "}";

var client = new DaprClientBuilder().Build();
var queryResponse = await client.QueryStateAsync<Account>("querystore", query, cancellationToken: cancellationToken);

Console.WriteLine($"Got {queryResponse.Results.Count}");
foreach (var account in queryResponse.Results)
{
    Console.WriteLine($"Account: {account.Data.Id} has {account.Data.Balance}");
}

Publish messages

var client = new DaprClientBuilder().Build();

var eventData = new { Id = "17", Amount = 10m, };
await client.PublishEventAsync(pubsubName, "deposit", eventData, cancellationToken);
Console.WriteLine("Published deposit event!");

Interact with output bindings

using var client = new DaprClientBuilder().Build();

// Example payload for the Twilio SendGrid binding
var email = new 
{
    metadata = new 
    {
        emailTo = "customer@example.com",
        subject = "An email from Dapr SendGrid binding",    
    }, 
    data =  "<h1>Testing Dapr Bindings</h1>This is a test.<br>Bye!",
};
await client.InvokeBindingAsync("send-email", "create", email);

Retrieve secrets

var client = new DaprClientBuilder().Build();

// Retrieve a key-value-pair-based secret - returns a Dictionary<string, string>
var secrets = await client.GetSecretAsync("mysecretstore", "key-value-pair-secret");
Console.WriteLine($"Got secret keys: {string.Join(", ", secrets.Keys)}");
var client = new DaprClientBuilder().Build();

// Retrieve a key-value-pair-based secret - returns a Dictionary<string, string>
var secrets = await client.GetSecretAsync("mysecretstore", "key-value-pair-secret");
Console.WriteLine($"Got secret keys: {string.Join(", ", secrets.Keys)}");

// Retrieve a single-valued secret - returns a Dictionary<string, string>
// containing a single value with the secret name as the key
var data = await client.GetSecretAsync("mysecretstore", "single-value-secret");
var value = data["single-value-secret"]
Console.WriteLine("Got a secret value, I'm not going to be print it, it's a secret!");

Get Configuration Keys

var client = new DaprClientBuilder().Build();

// Retrieve a specific set of keys.
var specificItems = await client.GetConfiguration("configstore", new List<string>() { "key1", "key2" });
Console.WriteLine($"Here are my values:\n{specificItems[0].Key} -> {specificItems[0].Value}\n{specificItems[1].Key} -> {specificItems[1].Value}");

// Retrieve all configuration items by providing an empty list.
var specificItems = await client.GetConfiguration("configstore", new List<string>());
Console.WriteLine($"I got {configItems.Count} entires!");
foreach (var item in configItems)
{
    Console.WriteLine($"{item.Key} -> {item.Value}")
}

Subscribe to Configuration Keys

var client = new DaprClientBuilder().Build();

// The Subscribe Configuration API returns a wrapper around an IAsyncEnumerable<IEnumerable<ConfigurationItem>>.
// Iterate through it by accessing its Source in a foreach loop. The loop will end when the stream is severed
// or if the cancellation token is cancelled.
var subscribeConfigurationResponse = await daprClient.SubscribeConfiguration(store, keys, metadata, cts.Token);
await foreach (var items in subscribeConfigurationResponse.Source.WithCancellation(cts.Token))
{
    foreach (var item in items)
    {
        Console.WriteLine($"{item.Key} -> {item.Value}")
    }
}

Distributed lock (Alpha)

Acquire a lock

using System;
using Dapr.Client;

namespace LockService
{
    class Program
    {
        [Obsolete("Distributed Lock API is in Alpha, this can be removed once it is stable.")]
        static async Task Main(string[] args)
        {
            var daprLockName = "lockstore";
            var fileName = "my_file_name";
            var client = new DaprClientBuilder().Build();
     
            // Locking with this approach will also unlock it automatically, as this is a disposable object
            await using (var fileLock = await client.Lock(DAPR_LOCK_NAME, fileName, "random_id_abc123", 60))
            {
                if (fileLock.Success)
                {
                    Console.WriteLine("Success");
                }
                else
                {
                    Console.WriteLine($"Failed to lock {fileName}.");
                }
            }
        }
    }
}

Unlock an existing lock

using System;
using Dapr.Client;

namespace LockService
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var daprLockName = "lockstore";
            var client = new DaprClientBuilder().Build();

            var response = await client.Unlock(DAPR_LOCK_NAME, "my_file_name", "random_id_abc123"));
            Console.WriteLine(response.status);
        }
    }
}

Sidecar APIs

Sidecar Health

The .NET SDK provides a way to poll for the sidecar health, as well as a convenience method to wait for the sidecar to be ready.

Poll for health

This health endpoint returns true when both the sidecar and your application are up (fully initialized).

var client = new DaprClientBuilder().Build();

var isDaprReady = await client.CheckHealthAsync();

if (isDaprReady) 
{
    // Execute Dapr dependent code.
}

Poll for health (outbound)

This health endpoint returns true when Dapr has initialized all its components, but may not have finished setting up a communication channel with your application.

This is best used when you want to utilize a Dapr component in your startup path, for instance, loading secrets from a secretstore.

var client = new DaprClientBuilder().Build();

var isDaprComponentsReady = await client.CheckOutboundHealthAsync();

if (isDaprComponentsReady) 
{
    // Execute Dapr component dependent code.
}

Wait for sidecar

The DaprClient also provides a helper method to wait for the sidecar to become healthy (components only). When using this method, it is recommended to include a CancellationToken to allow for the request to timeout. Below is an example of how this is used in the DaprSecretStoreConfigurationProvider.

// Wait for the Dapr sidecar to report healthy before attempting use Dapr components.
using (var tokenSource = new CancellationTokenSource(sidecarWaitTimeout))
{
    await client.WaitForSidecarAsync(tokenSource.Token);
}

// Perform Dapr component operations here i.e. fetching secrets.

Shutdown the sidecar

var client = new DaprClientBuilder().Build();
await client.ShutdownSidecarAsync();

1 - DaprClient usage

Essential tips and advice for using DaprClient

Lifetime management

A DaprClient holds access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. DaprClient implements IDisposable to support eager cleanup of resources.

Dependency Injection

The AddDaprClient() method will register the Dapr client with ASP.NET Core dependency injection. This method accepts an optional options delegate for configuring the DaprClient and an ServiceLifetime argument, allowing you to specify a different lifetime for the registered resources instead of the default Singleton value.

The following example assumes all default values are acceptable and is sufficient to register the DaprClient.

services.AddDaprClient();

The optional configuration delegates are used to configure DaprClient by specifying options on the provided DaprClientBuilder as in the following example:

services.AddDaprClient(daprBuilder => {
    daprBuilder.UseJsonSerializerOptions(new JsonSerializerOptions {
            WriteIndented = true,
            MaxDepth = 8
        });
    daprBuilder.UseTimeout(TimeSpan.FromSeconds(30));
});

The another optional configuration delegate overload provides access to both the DaprClientBuilder as well as an IServiceProvider allowing for more advanced configurations that may require injecting services from the dependency injection container.

services.AddSingleton<SampleService>();
services.AddDaprClient((serviceProvider, daprBuilder) => {
    var sampleService = serviceProvider.GetRequiredService<SampleService>();
    var timeoutValue = sampleService.TimeoutOptions;
    
    daprBuilder.UseTimeout(timeoutValue);
});

Manual Instantiation

Rather than using dependency injection, a DaprClient can also be built using the static client builder.

For best performance, create a single long-lived instance of DaprClient and provide access to that shared instance throughout your application. DaprClient instances are thread-safe and intended to be shared.

Avoid creating a DaprClient per-operation and disposing it when the operation is complete.

Configuring DaprClient

A DaprClient can be configured by invoking methods on DaprClientBuilder class before calling .Build() to create the client. The settings for each DaprClient object are separate and cannot be changed after calling .Build().

var daprClient = new DaprClientBuilder()
    .UseJsonSerializerSettings( ... ) // Configure JSON serializer
    .Build();

By default, the DaprClientBuilder will prioritize the following locations, in the following order, to source the configuration values:

  • The value provided to a method on the DaprClientBuilder (e.g. UseTimeout(TimeSpan.FromSeconds(30)))
  • The value pulled from an optionally injected IConfiguration matching the name expected in the associated environment variable
  • The value pulled from the associated environment variable
  • Default values

Configuring on DaprClientBuilder

The DaprClientBuilder contains the following methods to set configuration options:

  • UseHttpEndpoint(string): The HTTP endpoint of the Dapr sidecar
  • UseGrpcEndpoint(string): Sets the gRPC endpoint of the Dapr sidecar
  • UseGrpcChannelOptions(GrpcChannelOptions): Sets the gRPC channel options used to connect to the Dapr sidecar
  • UseHttpClientFactory(IHttpClientFactory): Configures the DaprClient to use a registered IHttpClientFactory when building HttpClient instances
  • UseJsonSerializationOptions(JsonSerializerOptions): Used to configure JSON serialization
  • UseDaprApiToken(string): Adds the provided token to every request to authenticate to the Dapr sidecar
  • UseTimeout(TimeSpan): Specifies a timeout value used by the HttpClient when communicating with the Dapr sidecar

Configuring From IConfiguration

Rather than rely on sourcing configuration values directly from environment variables or because the values are sourced from dependency injected services, another options is to make these values available on IConfiguration.

For example, you might be registering your application in a multi-tenant environment and need to prefix the environment variables used. The following example shows how these values can be sourced from the environment variables to your IConfiguration when their keys are prefixed with test_;

var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables("test_"); //Retrieves all environment variables that start with "test_" and removes the prefix when sourced from IConfiguration
builder.Services.AddDaprClient();

Configuring From Environment Variables

The SDK will read the following environment variables to configure the default values:

  • DAPR_HTTP_ENDPOINT: used to find the HTTP endpoint of the Dapr sidecar, example: https://dapr-api.mycompany.com
  • DAPR_GRPC_ENDPOINT: used to find the gRPC endpoint of the Dapr sidecar, example: https://dapr-grpc-api.mycompany.com
  • DAPR_HTTP_PORT: if DAPR_HTTP_ENDPOINT is not set, this is used to find the HTTP local endpoint of the Dapr sidecar
  • DAPR_GRPC_PORT: if DAPR_GRPC_ENDPOINT is not set, this is used to find the gRPC local endpoint of the Dapr sidecar
  • DAPR_API_TOKEN: used to set the API Token

Configuring gRPC channel options

Dapr’s use of CancellationToken for cancellation relies on the configuration of the gRPC channel options and this is enabled by default. If you need to configure these options yourself, make sure to enable the ThrowOperationCanceledOnCancellation setting.

var daprClient = new DaprClientBuilder()
    .UseGrpcChannelOptions(new GrpcChannelOptions { ... ThrowOperationCanceledOnCancellation = true })
    .Build();

Using cancellation with DaprClient

The APIs on DaprClient that perform asynchronous operations accept an optional CancellationToken parameter. This follows a standard .NET idiom for cancellable operations. Note that when cancellation occurs, there is no guarantee that the remote endpoint stops processing the request, only that the client has stopped waiting for completion.

When an operation is cancelled, it will throw an OperationCancelledException.

Understanding DaprClient JSON serialization

Many methods on DaprClient perform JSON serialization using the System.Text.Json serializer. Methods that accept an application data type as an argument will JSON serialize it, unless the documentation clearly states otherwise.

It is worth reading the System.Text.Json documentation if you have advanced requirements. The Dapr .NET SDK provides no unique serialization behavior or customizations - it relies on the underlying serializer to convert data to and from the application’s .NET types.

DaprClient is configured to use a serializer options object configured from JsonSerializerDefaults.Web. This means that DaprClient will use camelCase for property names, allow reading quoted numbers ("10.99"), and will bind properties case-insensitively. These are the same settings used with ASP.NET Core and the System.Text.Json.Http APIs, and are designed to follow interoperable web conventions.

System.Text.Json as of .NET 5.0 does not have good support for all of F# language features built-in. If you are using F# you may want to use one of the converter packages that add support for F#’s features such as FSharp.SystemTextJson.

Simple guidance for JSON serialization

Your experience using JSON serialization and DaprClient will be smooth if you use a feature set that maps to JSON’s type system. These are general guidelines that will simplify your code where they can be applied.

  • Avoid inheritance and polymorphism
  • Do not attempt to serialize data with cyclic references
  • Do not put complex or expensive logic in constructors or property accessors
  • Use .NET types that map cleanly to JSON types (numeric types, strings, DateTime)
  • Create your own classes for top-level messages, events, or state values so you can add properties in the future
  • Design types with get/set properties OR use the supported pattern for immutable types with JSON

Polymorphism and serialization

The System.Text.Json serializer used by DaprClient uses the declared type of values when performing serialization.

This section will use DaprClient.SaveStateAsync<TValue>(...) in examples, but the advice is applicable to any Dapr building block exposed by the SDK.

public class Widget
{
    public string Color { get; set; }
}
...

// Storing a Widget value as JSON in the state store
widget widget = new Widget() { Color = "Green", };
await client.SaveStateAsync("mystatestore", "mykey", widget);

In the example above, the type parameter TValue has its type argument inferred from the type of the widget variable. This is important because the System.Text.Json serializer will perform serialization based on the declared type of the value. The result is that the JSON value { "color": "Green" } will be stored.

Consider what happens when you try to use derived type of Widget:

public class Widget
{
    public string Color { get; set; }
}

public class SuperWidget : Widget
{
    public bool HasSelfCleaningFeature { get; set; }
}
...

// Storing a SuperWidget value as JSON in the state store
Widget widget = new SuperWidget() { Color = "Green", HasSelfCleaningFeature = true, };
await client.SaveStateAsync("mystatestore", "mykey", widget);

In this example we’re using a SuperWidget but the variable’s declared type is Widget. Since the JSON serializer’s behavior is determined by the declared type, it only sees a simple Widget and will save the value { "color": "Green" } instead of { "color": "Green", "hasSelfCleaningFeature": true }.

If you want the properties of SuperWidget to be serialized, then the best option is to override the type argument with object. This will cause the serializer to include all data as it knows nothing about the type.

Widget widget = new SuperWidget() { Color = "Green", HasSelfCleaningFeature = true, };
await client.SaveStateAsync<object>("mystatestore", "mykey", widget);

Error handling

Methods on DaprClient will throw DaprException or a subclass when a failure is encountered.

try
{
    var widget = new Widget() { Color = "Green", };
    await client.SaveStateAsync("mystatestore", "mykey", widget);
}
catch (DaprException ex)
{
    // handle the exception, log, retry, etc.
}

The most common cases of failure will be related to:

  • Incorrect configuration of Dapr component
  • Transient failures such as a networking problem
  • Invalid data, such as a failure to deserialize JSON

In any of these cases you can examine more exception details through the .InnerException property.