BuildingBlocks.Idempotency
1.0.1
dotnet add package BuildingBlocks.Idempotency --version 1.0.1
NuGet\Install-Package BuildingBlocks.Idempotency -Version 1.0.1
<PackageReference Include="BuildingBlocks.Idempotency" Version="1.0.1" />
<PackageVersion Include="BuildingBlocks.Idempotency" Version="1.0.1" />
<PackageReference Include="BuildingBlocks.Idempotency" />
paket add BuildingBlocks.Idempotency --version 1.0.1
#r "nuget: BuildingBlocks.Idempotency, 1.0.1"
#:package BuildingBlocks.Idempotency@1.0.1
#addin nuget:?package=BuildingBlocks.Idempotency&version=1.0.1
#tool nuget:?package=BuildingBlocks.Idempotency&version=1.0.1
BuildingBlocks.Idempotency
ASP.NET Core Idempotency-Key for MVC and Minimal API. Host-owned IDistributedCache, optional Redis lock, opt-in request fingerprint, RFC 9457 ProblemDetails on conflicts, optional ActivitySource.
Requires .NET 8, .NET 9, or .NET 10.
What's new in 1.0.1
- Package icon for NuGet listing parity with other BuildingBlocks
- System.Text.Json for the cache envelope and MVC
ObjectResultbody capture (droppedNewtonsoft.Json). Minimal API already used STJ. No public API change from 1.0.0.
Install
dotnet add package BuildingBlocks.Idempotency
Features
- Shared gate for MVC (
[Idempotent]) and Minimal API (WithIdempotency) - Cache HTTP 2xx envelopes (status, content-type, body) and replay them
- Processing lease (
ProcessingTtl) with abandoned recovery; Completed window (EntryTtl) - Per-endpoint TTL overrides (
ProcessingTtlSeconds/EntryTtlSeconds) - Optional Redis SET NX lock around GetOrCreate (
.UseRedisLock()) - Opt-in fingerprint:
SHA-256(method + "\n" + path + "\n" + body) - Key validation (ULID by default,
MaxKeyLength, reject empty / control characters) DuplicateCompletedBehavior: Replay (default) or Conflict- ProblemDetails errors with stable
typeURIs - Optional telemetry (
.UseTelemetry()); hostAddSource("BuildingBlocks.Idempotency") - System.Text.Json only (cache + MVC + Minimal API)
Quick start
// Host must already register IDistributedCache.
// For UseLock, also register IConnectionMultiplexer.
builder.Services.AddBuildingBlocksIdempotency(o =>
{
o.ProcessingTtl = TimeSpan.FromMinutes(2); // keep longer than worst-case handler
// o.EnableRequestFingerprint = true;
// o.UserIdFallback = "anonymous"; // when NameIdentifier is missing
})
.UseRedisLock() // optional
.UseTelemetry(); // optional
// MVC
[HttpPost]
[Idempotent(useLock: true)]
public async Task<ActionResult<OrderResponse>> Create([FromBody] CreateOrder request) { ... }
// Minimal API
app.MapPost("/orders", CreateAsync).WithIdempotency(useLock: true);
Aliases still work: AddRedisIdempotencyLock(), AddIdempotencyTelemetry().
Behavior
| Situation | Result |
|---|---|
| New key | Execute; on 2xx store Completed envelope |
| Same key, Completed (default) | Replay envelope + X-Idempotent-Response |
Same key, Completed + DuplicateCompletedBehavior.Conflict |
ProblemDetails (default 409) |
| Same key, Processing (lease active) | ProblemDetails (default 409) |
| Same key, Processing expired | Miss — may execute again |
| Fingerprint enabled + method/path/body mismatch | ProblemDetails (default 422) |
| Missing / invalid key | ProblemDetails 400 |
Missing caller identity (no UserIdFallback) |
ProblemDetails 401 |
| Non-2xx or exception | Remove cache entry (retry allowed) |
UseLock and lock not acquired |
ProblemDetails 500 |
Cache key: {KeyPrefix}_{optionalScopeClaims}_{userId}_{key} (default prefix Idempotency).
Replay header name is configurable (CachedResponseHeader, default X-Idempotent-Response).
ProblemDetails
Conflicts and validation failures return application/problem+json. Stable type values:
| Suffix | Typical status |
|---|---|
key-invalid |
400 |
unauthorized |
401 |
processing |
409 |
fingerprint-mismatch |
422 |
duplicate |
409 |
lock-failure |
500 |
Base: https://buildingblocks.dev/errors/idempotency/.
TTL
| Setting | Default | Role |
|---|---|---|
ProcessingTtl |
2 min | In-flight lease |
EntryTtl |
30 min | Completed replay window |
Attribute / WithIdempotency seconds |
0 |
Use global; > 0 overrides |
After ProcessingTtl expires, another request with the same key may run while a slow first request is still in flight. The optional lock covers GetOrCreate only.
Fingerprint
Off by default (same key replays even if the body changes). When EnableRequestFingerprint is true, the hash includes HTTP method, path, and raw body bytes (not canonical JSON).
Options (common)
| Option | Default | Notes |
|---|---|---|
HeaderName |
Idempotency-Key |
Request header |
RequireUlid |
true |
Set false for opaque strings (still length/control checks) |
MaxKeyLength |
256 |
Always enforced |
KeyScopeClaimTypes |
empty | Extra claims in the cache key (e.g. tenant) |
UserIdFallback |
null |
Missing user claim → 401 unless set |
DuplicateCompletedBehavior |
Replay |
Or Conflict |
FingerprintConflictStatusCode |
422 |
Configurable |
ProcessingConflictStatusCode |
409 |
Configurable |
DuplicateConflictStatusCode |
409 |
When Conflict strategy |
Telemetry
.UseTelemetry() registers ActivitySource BuildingBlocks.Idempotency. Outcomes: executed, replayed, processing_conflict, fingerprint_conflict, duplicate_conflict, bad_key, unauthorized, lock_failure. Cache keys are not tagged unless IncludeCacheKeyInTelemetry is true.
This package does not depend on BuildingBlocks.Telemetry — add AddSource("BuildingBlocks.Idempotency") on the host.
Host requirements
IDistributedCache(required)IConnectionMultiplexerwhen using.UseRedisLock()/UseLock: true- No references to BuildingBlocks.Mcp, Mediator, Telemetry, or EventBus
Not this package
MCP tool write idempotency (UseMemoryIdempotency / IMcpIdempotencyStore) is part of BuildingBlocks.Mcp.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- StackExchange.Redis (>= 2.8.16)
- Ulid (>= 1.3.4)
-
net8.0
- StackExchange.Redis (>= 2.8.16)
- Ulid (>= 1.3.4)
-
net9.0
- StackExchange.Redis (>= 2.8.16)
- Ulid (>= 1.3.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.0.1: package icon; System.Text.Json for cache + MVC ObjectResult (drop Newtonsoft.Json). https://github.com/Maxofpower/FeatureFusion/blob/main/CHANGELOG.md