LowCodeHub.Idempotency
0.0.1
See the version list below for details.
dotnet add package LowCodeHub.Idempotency --version 0.0.1
NuGet\Install-Package LowCodeHub.Idempotency -Version 0.0.1
<PackageReference Include="LowCodeHub.Idempotency" Version="0.0.1" />
<PackageVersion Include="LowCodeHub.Idempotency" Version="0.0.1" />
<PackageReference Include="LowCodeHub.Idempotency" />
paket add LowCodeHub.Idempotency --version 0.0.1
#r "nuget: LowCodeHub.Idempotency, 0.0.1"
#:package LowCodeHub.Idempotency@0.0.1
#addin nuget:?package=LowCodeHub.Idempotency&version=0.0.1
#tool nuget:?package=LowCodeHub.Idempotency&version=0.0.1
LowCodeHub.Idempotency
LowCodeHub.Idempotency is a DI-first .NET library that provides end-to-end idempotency for ASP.NET Core Minimal APIs.
Why Idempotency
Clients can retry requests due to network timeouts, load-balancer retries, or user double-clicks. Without idempotency, each retry re-executes the operation — creating duplicate payments, orders, or bookings.
- Idempotency key: The client sends a unique
Idempotency-Keyheader with every write request. - First request: The endpoint executes normally and the full response (status, headers, body) is stored.
- Subsequent requests: The stored response is replayed without re-execution.
- Concurrent duplicates: A second request arriving while the first is still processing gets a
409 Conflict. - Fingerprint enforcement: Reusing a key with a different request body returns
422 Unprocessable Entity.
What You Get
- Middleware that only activates for marked endpoints — zero overhead on unrelated routes.
- Per-endpoint
RequireIdempotency()extension with optional TTL override. - Full response replay: status code, headers, and body.
- Atomic lock semantics for multi-node safety.
- Pluggable storage backends: in-memory (default), Redis, SQL Server, PostgreSQL.
- Request fingerprint enforcement to prevent key misuse.
- Server error (5xx) recovery: lock is released so the client can safely retry.
- OpenTelemetry counters, histograms, and activities for observability.
- Health checks for each storage backend.
- Admin diagnostics endpoint for inspecting stored records.
Install
dotnet add package LowCodeHub.Idempotency
Register Services
using LowCodeHub.Idempotency.Extensions;
builder.Services.AddIdempotency(options =>
{
options.DefaultExpiration = TimeSpan.FromHours(24);
options.LockDuration = TimeSpan.FromMinutes(5);
options.EnforceRequestFingerprint = true;
options.MaxBodyCaptureSize = 256 * 1024; // 256 KB
});
Or through configuration:
builder.Services.AddIdempotency(builder.Configuration);
{
"Idempotency": {
"HeaderName": "Idempotency-Key",
"DefaultExpiration": "1.00:00:00",
"LockDuration": "00:05:00",
"MaxBodyCaptureSize": 262144,
"EnforceRequestFingerprint": true
}
}
Add Middleware
app.UseIdempotency();
Place UseIdempotency() after routing and authentication, before endpoint execution:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseIdempotency();
Mark Endpoints
app.MapPost("/orders", CreateOrder)
.RequireIdempotency();
app.MapPost("/payments", CreatePayment)
.RequireIdempotency(TimeSpan.FromHours(48)); // custom TTL
Unmarked endpoints pass through unchanged. GET/DELETE and other safe methods are skipped automatically even if marked (configurable via AllowedMethods).
Storage Backends
In-Memory (Default)
Registered automatically by AddIdempotency(). Suitable for development and single-node deployments. Records are lost on process restart.
Redis
builder.Services.AddIdempotencyRedis(options =>
{
options.ConnectionString = "localhost:6379";
options.KeyPrefix = "idempotency:";
});
Or through configuration:
builder.Services.AddIdempotencyRedis(builder.Configuration);
{
"Idempotency": {
"Redis": {
"ConnectionString": "localhost:6379",
"KeyPrefix": "idempotency:"
}
}
}
Uses SET NX EX for atomic lock acquisition. Ideal for high-throughput APIs.
SQL Server
builder.Services.AddIdempotencySqlServer(options =>
{
options.ConnectionString = "Server=...;Database=...;";
options.Schema = "dbo";
options.Table = "IdempotencyRecords";
});
Requires a table with the following schema:
CREATE TABLE [dbo].[IdempotencyRecords] (
[Key] NVARCHAR(256) NOT NULL,
[State] INT NOT NULL,
[RequestFingerprint] NVARCHAR(128) NOT NULL,
[StatusCode] INT NULL,
[ResponseHeaders] NVARCHAR(MAX) NULL,
[ResponseBody] VARBINARY(MAX) NULL,
[CreatedAtUtc] DATETIMEOFFSET(7) NOT NULL,
[ExpiresAtUtc] DATETIMEOFFSET(7) NOT NULL,
CONSTRAINT [PK_IdempotencyRecords] PRIMARY KEY ([Key]),
INDEX [IX_IdempotencyRecords_ExpiresAtUtc] ([ExpiresAtUtc])
);
PostgreSQL
builder.Services.AddIdempotencyPostgreSql(options =>
{
options.ConnectionString = "Host=...;Database=...;";
options.Schema = "public";
options.Table = "idempotency_records";
});
Requires a table with the following schema:
CREATE TABLE public.idempotency_records (
key VARCHAR(256) NOT NULL,
state INTEGER NOT NULL,
request_fingerprint VARCHAR(128) NOT NULL,
status_code INTEGER NULL,
response_headers JSONB NULL,
response_body BYTEA NULL,
created_at_utc TIMESTAMPTZ NOT NULL,
expires_at_utc TIMESTAMPTZ NOT NULL,
CONSTRAINT pk_idempotency_records PRIMARY KEY (key)
);
CREATE INDEX ix_idempotency_records_expires ON public.idempotency_records (expires_at_utc);
Admin Diagnostics
app.MapIdempotencyDiagnostics("/admin/idempotency")
.RequireAuthorization(); // recommended
Query parameters: keyPrefix, state, createdAfter, createdBefore, maxResults.
Returns record metadata (no response bodies) for operational visibility.
OpenTelemetry Metrics
The library emits the following metrics under the LowCodeHub.Idempotency meter:
| Metric | Type | Description |
|---|---|---|
idempotency.requests.total |
Counter | Total requests processed |
idempotency.cache.hit |
Counter | Responses replayed from store |
idempotency.cache.miss |
Counter | New requests (no existing record) |
idempotency.cache.conflict |
Counter | Concurrent duplicates (409) |
idempotency.cache.fingerprint_mismatch |
Counter | Key reuse with different body (422) |
idempotency.store.errors |
Counter | Storage backend errors |
idempotency.request.duration |
Histogram | Request processing duration (ms) |
Activities: idempotency.check, idempotency.store.
Health Checks
Each storage backend registers a health check:
- Redis:
idempotency-redis(tags:idempotency,redis,readiness) - SQL Server:
idempotency-sqlserver(tags:idempotency,sqlserver,readiness) - PostgreSQL:
idempotency-postgresql(tags:idempotency,postgresql,readiness)
HTTP Response Codes
| Status | When |
|---|---|
| 200 (or original) | First request completes / replay of stored response |
| 400 | Missing Idempotency-Key header on a marked endpoint |
| 409 | Concurrent duplicate — another request with the same key is in progress |
| 422 | Key reused with a different request body (fingerprint mismatch) |
Behavior Details
- Server errors (5xx): The lock is released so the client can retry with the same key.
- Client errors (4xx): The response IS stored and replayed (consistent with Stripe convention).
- Exceptions: The lock is released and the exception propagates normally.
- Body size limit: Responses exceeding
MaxBodyCaptureSizeare not stored; the lock is released. - Expired records: Treated as non-existent — the key can be reused after expiration.
| 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
- Microsoft.Data.SqlClient (>= 6.1.4)
- Npgsql (>= 10.0.1)
- StackExchange.Redis (>= 2.11.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.