IdempotencyShield 1.0.2

dotnet add package IdempotencyShield --version 1.0.2
                    
NuGet\Install-Package IdempotencyShield -Version 1.0.2
                    
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="IdempotencyShield" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IdempotencyShield" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="IdempotencyShield" />
                    
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 IdempotencyShield --version 1.0.2
                    
#r "nuget: IdempotencyShield, 1.0.2"
                    
#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 IdempotencyShield@1.0.2
                    
#: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=IdempotencyShield&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=IdempotencyShield&version=1.0.2
                    
Install as a Cake Tool

NuGet License: MIT

IdempotencyShield is a high-performance .NET middleware library that makes ASP.NET Core APIs resilient to duplicate requests and network issues (retry storms). It ensures idempotency through distributed locking, response caching, and payload validation.

Features

Easy Integration - Simple attribute-based decoration for controllers and actions
Distributed Locking - Prevents concurrent processing of duplicate requests
Response Caching - Instantly returns cached responses for duplicate requests
Payload Validation - SHA256 hashing ensures idempotency keys aren't reused with different payloads
Failure Resilience - Configurable Fail-Safe and Fail-Open modes with automatic retries
Background Cleanup - Automatic removal of expired records and locks
Thread-Safe - Production-ready concurrent implementation
Extensible - Plugin your own storage backend (Redis, SQL, etc.)
.NET 6+ Compatible - Supports .NET 6 and .NET 8 (LTS)

Installation

dotnet add package IdempotencyShield

📘 New to Idempotency?
Check out our Step-by-Step Beginner's Guide: Guide

Quick Start

1. Register the Services

In your Program.cs or Startup.cs:

using IdempotencyShield.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Add IdempotencyShield with default configuration
builder.Services.AddIdempotencyShield();

// OR with custom configuration
builder.Services.AddIdempotencyShield(options =>
{
    options.HeaderName = "X-Idempotency-Key";
    options.DefaultExpiryMinutes = 120;
    
    // Lock configuration
    options.LockExpirationMilliseconds = 30000;  // Lock TTL (time-to-live)
    options.LockWaitTimeoutMilliseconds = 5000;  // Max wait time to acquire lock
    
    // Resilience configuration
    options.FailureMode = IdempotencyFailureMode.FailSafe;  // or FailOpen
    options.StorageRetryCount = 3;
    options.StorageRetryDelayMilliseconds = 100;
});

var app = builder.Build();

app.UseRouting();

// Add the middleware AFTER UseRouting() so it can access endpoint metadata
app.UseIdempotencyShield();

app.MapControllers();

app.Run();

2. Decorate Your Controllers

using IdempotencyShield.Attributes;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class PaymentsController : ControllerBase
{
    [HttpPost]
    [Idempotent(ExpiryInMinutes = 60, ValidatePayload = true)]
    public async Task<IActionResult> ProcessPayment([FromBody] PaymentRequest request)
    {
        // Your business logic here
        // This will only execute once per unique Idempotency-Key
        
        var result = await _paymentService.ProcessAsync(request);
        return Ok(result);
    }
}

3. Send Requests with Idempotency Key

curl -X POST https://api.example.com/api/payments \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{"amount": 100.00, "currency": "USD"}'

How It Works

sequenceDiagram
    participant Client
    participant Middleware
    participant Cache
    participant Lock
    participant Controller
    
    Client->>Middleware: Request with Idempotency-Key
    Middleware->>Cache: Check for cached response
    
    alt Cache Hit
        Cache-->>Middleware: Return cached response
        Middleware-->>Client: 200 OK (cached)
    else Cache Miss
        Middleware->>Lock: Try acquire lock
        
        alt Lock Failed
            Lock-->>Middleware: Lock held by another request
            Middleware-->>Client: 409 Conflict
        else Lock Acquired
            Middleware->>Controller: Execute business logic
            Controller-->>Middleware: Response
            Middleware->>Cache: Save response
            Middleware->>Lock: Release lock
            Middleware-->>Client: 200 OK (fresh)
        end
    end

Behavior & Status Codes

Scenario Status Code Description
First request 2xx Executes controller, caches response
Duplicate request (same payload) 200 Returns cached response instantly
Duplicate request (different payload) 422 Unprocessable Entity - key reuse with different body
Concurrent requests (same key) 409 Conflict - another request is processing
No idempotency key provided N/A Proceeds normally without idempotency

Configuration Options

IdempotencyOptions

public class IdempotencyOptions
{
    // The HTTP header name for the idempotency key
    public string HeaderName { get; set; } = "Idempotency-Key";
    
    // Default expiry time in minutes for cached responses
    public int DefaultExpiryMinutes { get; set; } = 60;
    
    // Lock expiration (TTL) in milliseconds
    public int LockExpirationMilliseconds { get; set; } = 30000;
    
    // Max time to wait for lock acquisition (0 = no wait, immediate 409 on conflict)
    public int LockWaitTimeoutMilliseconds { get; set; } = 0;
    
    // Failure handling mode
    public IdempotencyFailureMode FailureMode { get; set; } = IdempotencyFailureMode.FailSafe;
    
    // Number of retries for storage operations
    public int StorageRetryCount { get; set; } = 3;
    
    // Delay between retries in milliseconds
    public int StorageRetryDelayMilliseconds { get; set; } = 100;
}

Failure Modes

Fail-Safe (Recommended for Production)

options.FailureMode = IdempotencyFailureMode.FailSafe;
  • Storage failures propagate as exceptions (500 Internal Server Error)
  • Guarantees idempotency integrity
  • Best for critical operations (payments, orders, etc.)

Fail-Open (High Availability)

options.FailureMode = IdempotencyFailureMode.FailOpen;
  • Storage failures are swallowed, request proceeds normally
  • Prioritizes availability over idempotency guarantees
  • Useful for non-critical operations or degraded mode

IdempotentAttribute

[Idempotent(
    ExpiryInMinutes = 60,      // How long to cache the response
    ValidatePayload = true      // Validate request body hash
)]

Custom Storage Backend

The default InMemoryIdempotencyStore is suitable for single-instance development and testing. For production distributed systems, use the Redis implementation:

Install the Redis package:

dotnet add package IdempotencyShield.Redis

Configure in your application:

using IdempotencyShield.Extensions;

// Simple setup
builder.Services.AddIdempotencyShieldWithRedis("localhost:6379");

// Production setup with SSL and resilience
builder.Services.AddIdempotencyShieldWithRedis(
    redisConfiguration: "your-redis-server:6379,password=secret,ssl=true",
    configureOptions: options =>
    {
        options.HeaderName = "Idempotency-Key";
        options.DefaultExpiryMinutes = 120;
    });

Features:

  • ✅ Distributed locking with Redis SET NX
  • ✅ Automatic expiration (TTL)
  • ✅ Supports Redis Cluster and Sentinel
  • ✅ JSON serialization
  • ✅ Production-ready

📖 See the Redis implementation documentation for complete details, examples, and best practices.

Custom Implementation

You can also implement your own store:

using IdempotencyShield.Storage;
using IdempotencyShield.Models;

public class RedisIdempotencyStore : IIdempotencyStore
{
    private readonly IConnectionMultiplexer _redis;
    
    public RedisIdempotencyStore(IConnectionMultiplexer redis)
    {
        _redis = redis;
    }
    
    public async Task<IdempotencyRecord?> GetAsync(string key, CancellationToken ct)
    {
        // Implement Redis GET logic
    }
    
    public async Task SaveAsync(string key, IdempotencyRecord record, int expiryMinutes, CancellationToken ct)
    {
        // Implement Redis SET with expiry
    }
    
    public async Task<bool> TryAcquireLockAsync(string key, CancellationToken ct)
    {
        // Implement Redis distributed lock (e.g., SET NX)
    }
    
    public async Task ReleaseLockAsync(string key, CancellationToken ct)
    {
        // Implement lock release (e.g., DEL)
    }
}

// Register your custom store
builder.Services.AddIdempotencyShield<RedisIdempotencyStore>();

Advanced Features

Background Cleanup Service

For EF Core implementations, enable automatic cleanup of expired records:

using IdempotencyShield.EntityFrameworkCore.Extensions;

builder.Services.AddIdempotencyCleanupService<MyDbContext>(
    cleanupInterval: TimeSpan.FromHours(1));  // Run cleanup every hour

This hosted service automatically removes expired idempotency records and locks, preventing database bloat.

Storage Retry Mechanism

Configurable automatic retries for transient storage failures:

builder.Services.AddIdempotencyShield(options =>
{
    options.StorageRetryCount = 3;                  // Retry up to 3 times
    options.StorageRetryDelayMilliseconds = 100;     // Wait 100ms between retries
});

Lock Configuration

options.LockExpirationMilliseconds = 30000;   // Lock expires after 30s (prevents stuck locks)
options.LockWaitTimeoutMilliseconds = 5000;    // Wait up to 5s for lock acquisition
  • LockExpirationMilliseconds: How long the lock lives (TTL). Prevents locks from staying forever if a process crashes.
  • LockWaitTimeoutMilliseconds: How long to wait/retry if lock is held by another request. Set to 0 for immediate 409 Conflict.

Best Practices

  1. Use UUIDs for Keys - Generate unique idempotency keys on the client (e.g., UUID v4)
  2. Client Retries - Configure exponential backoff for 409 Conflict responses
  3. Key Expiry - Set appropriate expiry times based on your business requirements
  4. Payload Validation - Keep ValidatePayload = true to prevent key reuse attacks
  5. Production Storage - Use distributed stores (Redis, SQL) for multi-instance deployments
  6. Failure Mode - Use FailSafe for critical operations, FailOpen for high availability scenarios
  7. Lock Tuning - Set LockExpirationMilliseconds higher than your longest request duration
  8. Monitoring - Log 409 and 422 responses to track retry storms and misuse
  9. Cleanup Service - Enable for EF Core to prevent database bloat

Thread Safety

IdempotencyShield is fully thread-safe:

  • InMemoryIdempotencyStore uses ConcurrentDictionary and SemaphoreSlim
  • Automatic semaphore cleanup prevents memory leaks
  • Proper lock release in finally blocks ensures no deadlocks

Performance Considerations

  • Cache Hits: Near-instant response (no controller execution)
  • Lock Contention: 409 returned immediately (configurable timeout)
  • Memory: In-memory store grows with unique keys (use expiry)
  • Overhead: Minimal (~1-2ms) for hash computation and cache lookup

License

This project is licensed under the MIT License.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Support

For issues, questions, or feature requests, please open an issue on GitHub.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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 was computed.  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.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on IdempotencyShield:

Package Downloads
IdempotencyShield.Redis

Redis-backed implementation of IIdempotencyStore for IdempotencyShield. Provides distributed caching and locking for multi-instance ASP.NET Core deployments.

IdempotencyShield.EntityFrameworkCore

Entity Framework Core implementation for IdempotencyShield.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 526 12/11/2025
1.0.1 522 12/10/2025
1.0.0 530 12/10/2025