DaprStateManagementClient usage
Lifetime management
A DaprStateManagementClient holds long-lived network resources (gRPC channels, HTTP connections). For best performance:
- Register a single shared instance and reuse it for the lifetime of the application.
- Avoid creating and disposing a client per operation — this can lead to socket exhaustion.
- The DI registration via
AddDaprStateManagementClient()defaults toServiceLifetime.Singleton, which is the recommended approach for most applications.
The client is thread-safe and can be shared across concurrent requests.
Client builder configuration
DaprStateManagementClientBuilder inherits from DaprGenericClientBuilder<DaprStateManagementClient> and supports the same configuration surface as the other Dapr .NET client builders.
Builder methods
| Method | Description |
|---|---|
UseGrpcEndpoint(string) | Sets the gRPC endpoint for the Dapr sidecar. |
UseHttpEndpoint(string) | Sets the HTTP endpoint for the Dapr sidecar. |
UseJsonSerializationOptions(JsonSerializerOptions) | Configures custom JSON serializer options. |
UseDaprApiToken(string) | Sets the API token for authenticating with the Dapr sidecar. |
UseGrpcChannelOptions(GrpcChannelOptions) | Provides custom gRPC channel options. |
UseTimeout(TimeSpan) | Configures an HTTP request timeout. |
Environment variables
The builder reads the following environment variables automatically:
| Variable | Description |
|---|---|
DAPR_GRPC_ENDPOINT | gRPC endpoint address for the Dapr sidecar |
DAPR_HTTP_ENDPOINT | HTTP endpoint address for the Dapr sidecar |
DAPR_API_TOKEN | API token for authenticating with the Dapr sidecar |
DAPR_GRPC_PORT | gRPC port (used as fallback if DAPR_GRPC_ENDPOINT is not set) |
DAPR_HTTP_PORT | HTTP port (used as fallback if DAPR_HTTP_ENDPOINT is not set) |
Values explicitly set on the builder take precedence over environment variables.
gRPC channel options
For fine-grained control over gRPC behavior:
builder.Services.AddDaprStateManagementClient((sp, clientBuilder) =>
{
clientBuilder.UseGrpcChannelOptions(new GrpcChannelOptions
{
MaxReceiveMessageSize = 16 * 1024 * 1024, // 16 MB
});
});
Cancellation tokens
All asynchronous methods on DaprStateManagementClient and IDaprStateStoreClient accept a CancellationToken parameter. Passing a token allows you to cancel long-running operations in response to timeouts or user cancellation:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var widget = await client.GetStateAsync<Widget>("statestore", "my-widget",
cancellationToken: cts.Token);
Cancellation behavior in SDK 1.19.0
This release catches caller-initiated cancellation in the state gRPC client. Single reads can return default values, bulk/query reads can return empty results, conditional writes/deletes can returnfalse, and other
writes can complete without confirming a remote change. These outcomes are not sufficient evidence that a
key is absent, an ETag conflicted, or a write succeeded. Check the caller’s token after an operation if you need
to distinguish cancellation, and reconcile uncertain writes with the store. Cancellation never guarantees
rollback of a request already accepted by the runtime.Dependency injection registration
Registration overloads
The AddDaprStateManagementClient method has the following signature:
public static IDaprStateManagementBuilder AddDaprStateManagementClient(
this IServiceCollection services,
Action<IServiceProvider, DaprStateManagementClientBuilder>? configure = null,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
The returned IDaprStateManagementBuilder enables chaining typed state store registrations:
builder.Services
.AddDaprStateManagementClient()
.WithWidgetStore()
.WithCacheStore();
Chaining source generator registrations
Each [StateStore]-annotated interface generates an extension method on IDaprStateManagementBuilder. The methods return IDaprStateManagementBuilder, so they chain naturally:
builder.Services
.AddDaprStateManagementClient((sp, clientBuilder) =>
{
clientBuilder.UseGrpcEndpoint("http://localhost:50001");
})
.WithWidgetStore()
.WithCacheStore()
.WithUserPreferencesStore();
JSON serialization
By default, the client uses System.Text.Json with JsonSerializerDefaults.Web, which provides:
camelCaseproperty naming (PropertyNamingPolicy = JsonNamingPolicy.CamelCase)- Case-insensitive property name matching (
PropertyNameCaseInsensitive = true) - Strings read as numbers allowed (
NumberHandling = JsonNumberHandling.AllowReadingFromString)
These defaults match the DaprClient package, ensuring serialization compatibility — data written by one client can be read correctly by the other.
To customize:
builder.Services.AddDaprStateManagementClient((sp, clientBuilder) =>
{
clientBuilder.UseJsonSerializationOptions(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
});
});
Note: Changing serializer settings can break compatibility with data previously written using default settings. Test thoroughly when customizing.
API response shapes
GetStateAsync
GetStateAsync<TValue> returns TValue?. If the key does not exist in the store, default(T) is returned (i.e., null for reference types).
GetStateAndETagAsync
GetStateAndETagAsync<TValue> returns (TValue? Value, string? ETag). The ETag can be passed to TrySaveStateAsync or TryDeleteStateAsync for optimistic concurrency control.
GetBulkStateAsync
GetBulkStateAsync<TValue> returns IReadOnlyList<BulkStateItem<TValue>>. Each BulkStateItem<TValue> contains:
| Property | Type | Description |
|---|---|---|
Key | string | The state key |
Value | TValue? | The deserialized value, or null if the key was not found |
ETag | string | The ETag for optimistic concurrency control |
TrySaveStateAsync / TryDeleteStateAsync
Both return bool — true if the operation succeeded, false on an ETag conflict. In 1.19.0,
caller cancellation can also return false; check cancellation before treating it as a concurrency conflict.
QueryStateAsync
QueryStateAsync<TValue> returns StateQueryResponse<TValue>, which contains:
| Property | Type | Description |
|---|---|---|
Results | IReadOnlyList<StateQueryItem<TValue>> | The list of matching items |
Token | string? | Pagination token for continuing the query, or null if no more results |
Metadata | IReadOnlyDictionary<string, string> | Additional metadata returned by the state store |
Each StateQueryItem<TValue> contains:
| Property | Type | Description |
|---|---|---|
Key | string | The state key |
Data | TValue? | The deserialized value |
ETag | string? | The ETag for the item |
Error | string? | An error message if the item could not be retrieved, or null on success |
Although StateQueryItem<TValue> has an Error property, the 1.19.0 client throws DaprException
when the runtime returns per-item query errors, rather than returning a successful response containing those
failed items. Do not rely on partial results being returned in that case.
StateOptions
The StateOptions class controls consistency and concurrency behavior for individual operations:
var options = new StateOptions
{
Consistency = ConsistencyMode.Strong,
Concurrency = ConcurrencyMode.FirstWrite,
};
await client.SaveStateAsync("statestore", "my-widget", widget, stateOptions: options);
| Property | Values | Description |
|---|---|---|
Consistency | Eventual, Strong | Controls whether reads reflect the latest write. null uses the store default. |
Concurrency | FirstWrite, LastWrite | Controls conflict resolution. FirstWrite fails on ETag mismatch; LastWrite always overwrites. null uses the store default. |
Error handling
Methods on DaprStateManagementClient throw DaprException for runtime failures. Invalid argument values
raise standard exceptions such as ArgumentNullException or ArgumentException. Caller cancellation has
the release-specific behavior described above.
The most common cases of failure will be related to:
- Incorrect argument formatting (e.g. an empty store name or key)
- Transient failures such as a networking problem
- The specified state store component not being configured or available
- ETag mismatches when using optimistic concurrency (for
TrySaveStateAsyncandTryDeleteStateAsync, these returnfalserather than throwing)
In any of these cases, you can examine more exception details through the .InnerException property.
Migration from DaprClient
If you are migrating from the DaprClient state management methods to the new Dapr.StateManagement package, note
the following:
- The API surface is very similar. Methods like
GetStateAsync,SaveStateAsync,DeleteStateAsync,GetBulkStateAsync, andExecuteStateTransactionAsyncare all present with the same semantics. - The default JSON serialization settings (
JsonSerializerDefaults.Web) are identical, so data written byDaprClientis fully compatible withDaprStateManagementClientand vice versa. - The new package additionally provides
IDaprStateStoreClientand the[StateStore]source generator for strongly-typed, store-scoped access. This reduces boilerplate and eliminating store name strings at call sites. DaprStateManagementClientis registered independently fromDaprClient. Both can coexist in the same application during a gradual migration.
APIs that remain on DaprClient
The dedicated client’s public surface is typed state access. It does not expose these DaprClient helpers:
| Legacy method | Purpose |
|---|---|
SaveByteStateAsync, TrySaveByteStateAsync | Save already-encoded bytes, optionally conditional on an ETag. |
GetByteStateAsync, GetByteStateAndETagAsync | Retrieve raw state bytes, optionally with an ETag. |
Non-generic GetBulkStateAsync | Retrieve bulk entries without deserializing values to an application type. |
GetStateEntryAsync<T> | Return a StateEntry<T> tracking value, key/store, and ETag for save/delete helper operations. |
Use DaprClient where these APIs are needed; do not assume substituting byte[] into a JSON generic
method preserves raw bytes. JSON serialization encodes a byte array rather than bypassing serialization.
For large files and chunked streaming, use the separate binary store building
block instead of buffering file content in state.
await legacyClient.SaveByteStateAsync("statestore", "encoded-record", encodedBytes,
cancellationToken: cancellationToken);
var (bytes, etag) = await legacyClient.GetByteStateAndETagAsync(
"statestore", "encoded-record", cancellationToken: cancellationToken);
The two client families also use types in different namespaces. Dedicated state operations use
Dapr.StateManagement option/operation models; older APIs use their Dapr.Client counterparts.
Check types as well as method names when migrating, and qualify overlapping names when using both.