MaksIT.Dapr 2.2.0

dotnet add package MaksIT.Dapr --version 2.2.0
                    
NuGet\Install-Package MaksIT.Dapr -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MaksIT.Dapr" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MaksIT.Dapr" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="MaksIT.Dapr" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MaksIT.Dapr --version 2.2.0
                    
#r "nuget: MaksIT.Dapr, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MaksIT.Dapr@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MaksIT.Dapr&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=MaksIT.Dapr&version=2.2.0
                    
Install as a Cake Tool

MaksIT.Dapr

Line Coverage Branch Coverage Method Coverage

NuGet facade over Dapr for ASP.NET Core: pub/sub, state, invocation, bindings, secrets, configuration, cryptography, sidecar, lock, HA work leases, actors, and workflows — all with MaksIT.Results outcomes.

Table of Contents

Overview

MaksIT.Dapr wraps Dapr building blocks so application code depends on small IDapr*Service facades instead of raw DaprClient calls. Failures surface as MaksIT.Results outcomes that map cleanly to HTTP via ToActionResult().

Need Register Inject
Publish events RegisterPubSub() IDaprPubSubService
Key/value state RegisterStateStore() IDaprStateStoreService
Call another Dapr app RegisterInvocation() IDaprInvocationService
Trigger external systems (queues, cron, …) RegisterBinding() IDaprBindingService
Read secrets from a Component RegisterSecrets() IDaprSecretService
Dynamic config + subscribe RegisterConfiguration() IDaprConfigurationService
Encrypt/decrypt via Dapr crypto Component RegisterCryptography() IDaprCryptographyService
Wait for sidecar / health / metadata RegisterSidecar() IDaprSidecarService
Short-lived distributed mutex RegisterLock() IDaprLockService
Multi-replica exclusive work (leader/bootstrap) RegisterWorkLeases(storeName) IDaprWorkLeaseService
Virtual actors RegisterActors(...) IDaprActorService
Durable workflows RegisterWorkflows(...) IDaprWorkflowService

RegisterDaprClientFacades() registers all DaprClient-backed rows above (not actors, workflows, or work leases).

Result conventions

  • Success / failure: check result.IsSuccess. Map to HTTP with result.ToActionResult().
  • Cancellation: facades rethrow OperationCanceledException (do not wrap as a failed Result).
  • State misses: GetStateAsync / GetStateAndETagAsync return Ok(null) / Ok((null, null)) when the key is absent (including JetStream key not found surfaced as gRPC Internal). !IsSuccess means infrastructure failure.
  • Topic handlers: return Result via ToActionResult()Ok ACK, ServiceUnavailable retry, BadRequest drop.

Getting Started

Ensure that you have the following installed:

Installation

dotnet add package MaksIT.Dapr

Or in your .csproj:

<PackageReference Include="MaksIT.Dapr" Version="2.2.0" />

Registering services

using MaksIT.Dapr.Extensions;

var builder = WebApplication.CreateBuilder(args);

// All DaprClient-backed facades (pub/sub, state, invocation, binding, secrets, …)
builder.Services.RegisterDaprClientFacades();
// Or register individually: RegisterPubSub(), RegisterStateStore(), …

// HA coordination (leases via Dapr state Component — e.g. persistent NATS KV)
builder.Services.RegisterWorkLeases("maksit-cicd-state"); // storeName for LeasedBackgroundService

builder.Services.RegisterActors(options => {
  options.Actors.RegisterActor<MyActor>();
});
builder.Services.RegisterWorkflows();

var app = builder.Build();

// After authentication/authorization middleware
app.RegisterSubscriber();
app.RegisterActorsHandlers();

Services

Pub/Sub — IDaprPubSubService

When to use: fire-and-forget or at-least-once messaging between services (commands, domain events, fan-out). Prefer this over direct broker SDKs so the app stays Component-agnostic (Redis, NATS, Kafka, …).

When not to: request/response RPC → service invocation; long-running orchestrations → workflows; exclusive multi-replica jobs → work leases.

DI RegisterPubSub() (also in RegisterDaprClientFacades())
Pipeline app.RegisterSubscriber() + [Topic("pubsubName", "topic")] on controllers
API PublishEventAsync, PublishByteEventAsync, BulkPublishEventAsync
var result = await pubSub.PublishEventAsync("my-pubsub", "my-topic", command, cancellationToken: ct);
if (!result.IsSuccess) { /* handle */ }

[Topic("my-pubsub", "my-topic")]
[HttpPost("/my-endpoint")]
public IActionResult Receive([FromBody] MyCommand payload) =>
  Result.Ok().ToActionResult(); // ACK; ServiceUnavailable → retry; BadRequest → drop

State store — IDaprStateStoreService

When to use: shared key/value state (caches, documents, coordination metadata) through a Dapr state Component. Also the foundation for work leases.

When not to: primary transactional business data that belongs in your product database; use the app’s own data layer for that.

DI RegisterStateStore()
API Set / Get / Delete, ETag OCC (GetStateAndETagAsync, TrySaveStateAsync, TryDeleteStateAsync), bulk, query, transactions
var save = await stateStore.SetStateAsync("my-store", "my-key", "my-value", cancellationToken: ct);
var get = await stateStore.GetStateAsync<string>("my-store", "my-key", cancellationToken: ct);
if (!get.IsSuccess) { /* infra failure */ }
var value = get.Value; // null when key is missing

Service invocation — IDaprInvocationService

When to use: synchronous HTTP-style calls to another Dapr app id (mTLS, retries, and discovery handled by the sidecars). Good for request/response between microservices without hard-coding URLs.

When not to: broadcast or decoupled events → pub/sub; calling non-Dapr external APIs → bindings or a normal HttpClient.

DI RegisterInvocation()
API InvokeAsync overloads (void / request / response / both) — HTTP POST via CreateInvokableHttpClient
var result = await invocation.InvokeAsync<OrderRequest, OrderResponse>(
  appId: "orders",
  methodName: "create",
  data: request,
  cancellationToken: ct);

Bindings — IDaprBindingService

When to use: invoke an output binding Component (send to a queue, call Twilio, write to blob storage, cron-triggered input handlers on the app side, etc.) without embedding vendor SDKs.

When not to: app-to-app messaging → pub/sub or invocation.

DI RegisterBinding()
API InvokeAsync (fire-and-forget or typed response)
var result = await bindings.InvokeAsync("my-smtp", "create", emailPayload, cancellationToken: ct);

Secrets — IDaprSecretService

When to use: load secrets at runtime from a Dapr secret store Component (Kubernetes secrets, Azure Key Vault, local files, …) when the host should not bind every secret into IConfiguration up front.

When not to: replace standard ASP.NET appsettings / appsecrets.json for ordinary host config — MaksIT hosts still prefer configuration binding for app settings; use this facade for Component-backed secret reads from application code.

DI RegisterSecrets()
API GetAsync, GetBulkAsync
var secret = await secrets.GetAsync("my-secret-store", "connection-string", cancellationToken: ct);

Configuration — IDaprConfigurationService

When to use: read or subscribe to keys from a Dapr configuration Component (feature flags, dynamic settings that change without redeploy).

When not to: static startup configuration already covered by IConfiguration / Options — keep using the host configuration stack for that.

DI RegisterConfiguration()
API GetAsync, SubscribeAsync, UnsubscribeAsync

Cryptography — IDaprCryptographyService

When to use: encrypt/decrypt payloads with keys managed by a Dapr cryptography Component (keys stay in the sidecar/Component, not in app memory as raw key material).

When not to: simple password hashing or app-local crypto libraries when you do not need Dapr-managed keys.

DI RegisterCryptography()
API EncryptAsync, DecryptAsync

Sidecar — IDaprSidecarService

When to use: gate startup or background work until the sidecar is ready; health/outbound checks; inspect metadata; request graceful sidecar shutdown.

Typical scenario: call WaitForSidecarAsync before the first work-lease race or state call on cold start so replicas do not fail while the sidecar is still connecting.

DI RegisterSidecar()
API CheckHealthAsync, CheckOutboundHealthAsync, WaitForSidecarAsync, GetMetadataAsync, ShutdownAsync
var ready = await sidecar.WaitForSidecarAsync(ct);
if (!ready.IsSuccess) { /* abort startup path */ }

Distributed lock — IDaprLockService

When to use: a short-lived distributed mutex on a Dapr lock Component — e.g. protect a critical section across processes for seconds, then unlock. Check TryLockResponse.Success; dispose the response (or call UnlockAsync) when finished. Expiry is in seconds via the Dapr API.

When not to: multi-replica leader election, bootstrap, or long-running exclusive sweeps → prefer HA work leases (state-backed TTL, auto-renew, generation fencing, LeasedBackgroundService / bootstrap helpers). Per-entity serialized logic → actors.

DI RegisterLock() (also in RegisterDaprClientFacades())
API LockAsyncTryLockResponse, UnlockAsyncUnlockResponse
Component Dapr lock store (separate from the state Component used by work leases)
var locked = await locks.LockAsync("my-lock-store", "invoice-42", runtimeInstance.InstanceId, expiryInSeconds: 15, ct);
if (!locked.IsSuccess)
  return; // infra failure

await using var handle = locked.Value; // DisposeAsync unlocks
if (!handle.Success)
  return; // another owner holds the lock

/* critical section */

Or unlock explicitly with UnlockAsync when you are not disposing the TryLockResponse.

HA work leases — IDaprWorkLeaseService

When to use: only one replica among many should run a piece of work (DB migrate/bootstrap, periodic sweep, leader-style job). Leases live in a Dapr state Component (e.g. persistent NATS JetStream KV) — broker-agnostic, ETag concurrency, optional auto-renew and generation fencing.

When not to: short critical sections with an explicit unlock → distributed lock; per-entity serialized state machines → actors; multi-step durable processes → workflows.

Namespace MaksIT.Dapr.Services.WorkLease
DI RegisterWorkLeases() or RegisterWorkLeases("state-component-name")
Also registered IDaprStateStoreService, IDaprRuntimeInstanceId (POD_NAME in Kubernetes)
Helpers TryHoldAsyncDaprWorkLeaseHold, DaprWorkLeaseBootstrap.RunBootstrapUnderLeaseAsync, LeasedBackgroundService
using MaksIT.Dapr.Services.WorkLease;

builder.Services.RegisterWorkLeases("maksit-cicd-state");

await using var hold = (await leases.TryHoldAsync(
  storeName: "maksit-cicd-state",
  workKey: "bootstrap",
  holderId: runtimeInstance.InstanceId,
  ttl: TimeSpan.FromMinutes(5),
  autoRenew: true,
  cancellationToken: ct)).Value;

if (hold is null)
  return; // another replica holds the lease

var stillHeld = await hold.EnsureStillHeldAsync(ct);
if (stillHeld is { IsSuccess: true, Value: true }) {
  /* exclusive work; hold.Generation is a fencing token */
}

// Bootstrap: leader runs under lease; followers poll until ready
await DaprWorkLeaseBootstrap.RunBootstrapUnderLeaseAsync(
  leases,
  "maksit-cicd-state",
  "bootstrap",
  runtimeInstance.InstanceId,
  TimeSpan.FromMinutes(5),
  bootstrap: async ct => { /* migrate / seed */ return Result.Ok(); },
  isReady: async ct => Result<bool>.Ok(await db.IsMigratedAsync(ct)),
  cancellationToken: ct);

For recurring exclusive sweeps, subclass LeasedBackgroundService and implement ExecuteLeasedAsync (uses IDaprWorkLeaseOptions.StoreName from RegisterWorkLeases(storeName)). Before lease races on cold start, call IDaprSidecarService.WaitForSidecarAsync.

Actors — IDaprActorService

When to use: virtual-actor patterns — single-threaded access to an entity id (cart, device, session), turn-based concurrency, reminders/timers. Define actor interfaces and implementations in the product; this facade creates proxies and invokes methods with Result outcomes.

When not to: cluster-wide singleton jobs → work leases; multi-step saga across many services → workflows.

DI RegisterActors(options => options.Actors.RegisterActor<T>())
Pipeline app.RegisterActorsHandlers()
API Create / Create<TActor>, InvokeAsync overloads
builder.Services.RegisterActors(o => o.Actors.RegisterActor<CounterActor>());
app.RegisterActorsHandlers();

var created = actors.Create<ICounterActor>("cart-42", nameof(CounterActor));
if (!created.IsSuccess)
  return created.ToResult().ToActionResult();

var count = await created.Value.IncrementAsync();

Workflows — IDaprWorkflowService

When to use: durable, multi-step business processes (order pipeline, approval flow) with history, wait-for-external-event, suspend/resume, and replay. Author Workflow / WorkflowActivity types in the product (auto-discovered on Dapr SDK 1.18+).

When not to: simple fire-and-forget messages → pub/sub; single exclusive background job → work leases.

DI RegisterWorkflows(configure?)
API ScheduleAsync, get/wait state, RaiseEventAsync, terminate/suspend/resume/purge, list IDs, history, RerunFromEventAsync
builder.Services.RegisterWorkflows();

var scheduled = await workflows.ScheduleAsync(
  workflowName: nameof(OrderProcessingWorkflow),
  input: order,
  instanceId: order.Id,
  cancellationToken: ct);

if (!scheduled.IsSuccess)
  return scheduled.ToResult().ToActionResult();

var completed = await workflows.WaitForCompletionAsync(scheduled.Value, cancellationToken: ct);

Choosing a coordination pattern

Scenario Prefer
One replica runs migrate/bootstrap/sweep Work leases
Short critical section across processes Distributed lock
Per-entity single-threaded logic Actors
Long-running multi-step process with history Workflows
Decoupled async events Pub/sub
Sync call to another Dapr app Invocation

Contributing

See CONTRIBUTING.md for build/test (RepoUtils), commit format, and release scripts.

Contact

License

See LICENSE.md.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 136 8/1/2026
2.1.0 97 7/30/2026
2.0.1 164 6/28/2026
2.0.0 189 2/27/2026
1.0.8 202 11/2/2025
1.0.7 287 5/25/2025
1.0.6 220 5/5/2025
1.0.5 213 5/5/2025
1.0.4 187 11/14/2024