LowCodeHub.Idempotency 0.0.1

There is a newer version of this package available.
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
                    
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="LowCodeHub.Idempotency" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LowCodeHub.Idempotency" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="LowCodeHub.Idempotency" />
                    
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 LowCodeHub.Idempotency --version 0.0.1
                    
#r "nuget: LowCodeHub.Idempotency, 0.0.1"
                    
#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 LowCodeHub.Idempotency@0.0.1
                    
#: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=LowCodeHub.Idempotency&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=LowCodeHub.Idempotency&version=0.0.1
                    
Install as a Cake Tool

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-Key header 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 MaxBodyCaptureSize are not stored; the lock is released.
  • Expired records: Treated as non-existent — the key can be reused after expiration.
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
0.0.4 90 5/18/2026
0.0.3 92 5/12/2026
0.0.2 107 4/23/2026
0.0.1 109 3/26/2026