MaksIT.Dapr
2.2.0
dotnet add package MaksIT.Dapr --version 2.2.0
NuGet\Install-Package MaksIT.Dapr -Version 2.2.0
<PackageReference Include="MaksIT.Dapr" Version="2.2.0" />
<PackageVersion Include="MaksIT.Dapr" Version="2.2.0" />
<PackageReference Include="MaksIT.Dapr" />
paket add MaksIT.Dapr --version 2.2.0
#r "nuget: MaksIT.Dapr, 2.2.0"
#:package MaksIT.Dapr@2.2.0
#addin nuget:?package=MaksIT.Dapr&version=2.2.0
#tool nuget:?package=MaksIT.Dapr&version=2.2.0
MaksIT.Dapr
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
- Result conventions
- Getting Started
- Installation
- Registering services
- Services
- Pub/Sub —
IDaprPubSubService - State store —
IDaprStateStoreService - Service invocation —
IDaprInvocationService - Bindings —
IDaprBindingService - Secrets —
IDaprSecretService - Configuration —
IDaprConfigurationService - Cryptography —
IDaprCryptographyService - Sidecar —
IDaprSidecarService - Distributed lock —
IDaprLockService - HA work leases —
IDaprWorkLeaseService - Actors —
IDaprActorService - Workflows —
IDaprWorkflowService
- Pub/Sub —
- Choosing a coordination pattern
- Contributing
- Contact
- License
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 withresult.ToActionResult(). - Cancellation: facades rethrow
OperationCanceledException(do not wrap as a failedResult). - State misses:
GetStateAsync/GetStateAndETagAsyncreturnOk(null)/Ok((null, null))when the key is absent (including JetStreamkey not foundsurfaced as gRPCInternal).!IsSuccessmeans infrastructure failure. - Topic handlers: return
ResultviaToActionResult()—OkACK,ServiceUnavailableretry,BadRequestdrop.
Getting Started
Ensure that you have the following installed:
- .NET 10 SDK
- Dapr CLI (for local sidecar runs)
- Docker (optional; used by RepoUtils Linux test validation)
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 | LockAsync → TryLockResponse, UnlockAsync → UnlockResponse |
| 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 | TryHoldAsync → DaprWorkLeaseHold, 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
- Email: maksym.sadovnychyy@gmail.com
License
See LICENSE.md.
| Product | Versions 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. |
-
net10.0
- Dapr.Actors.AspNetCore (>= 1.18.5)
- Dapr.AspNetCore (>= 1.18.5)
- Dapr.Workflow (>= 1.18.5)
- MaksIT.Core (>= 1.6.8)
- MaksIT.Results (>= 2.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.