Round #348

08 May 2026, 17:08· csharp· claude-sonnet-4-6· r13-clean-hints
Complexity
band 0
ZP / UO / OBS
4 / 0 / 0
Journey weight
294
Lightshift ΔL
+0.38

Prompt

Implement idempotency key middleware that deduplicates POST requests

Zero-point index (what the engine surfaced)

ZPI-ZPI-0002 | 2026-05-08T17:08:19Z Implement idempotency key middleware that deduplicates POST requests ZERO POINT — 4 entries (structural invariants) ● hsl(314.84, 100%, 29%) IMiddleware [contract] ● hsl(280.69, 100%, 29%) RequestDelegate [contract] ● hsl(329.61, 100%, 18%) IDistributedCache [contract] ● hsl(136.10, 100%, 29%) DbContext [contract] UNOBSERVED — 0 entries (implied truths) OBSERVED — 0 entries (explicit in request) COMPLEXITY │ 4 entries │ spread: 0° │ band: LOW

Baseline response (no ZPI)

# Idempotency Key Middleware for ASP.NET Core ## Overview This implementation provides a complete idempotency key middleware that: - Intercepts POST requests with an `Idempotency-Key` header - Caches responses for duplicate requests - Returns cached responses without re-executing the handler - Handles concurrent duplicate requests safely - Supports configurable TTL and storage backends ## Implementation ### 1. Core Models ```csharp // Models/IdempotencyModels.cs using System.Net; namespace IdempotencyMiddleware.Models; public class CachedResponse { public int StatusCode { get; set; } public Dictionary<string, string[]> Headers { get; set; } = new(); public byte[] Body { get; set; } = Array.Empty<byte>(); public string ContentType { get; set; } = string.Empty; public DateTimeOffset CachedAt { get; set; } public DateTimeOffset ExpiresAt { get; set; } public string RequestFingerprint { get; set; } = string.Empty; public bool IsComplete { get; set; } } public class IdempotencyOptions { public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(24); public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(30); public int MaxKeyLength { get; set; } = 255; public bool EnforceRequestFingerprint { get; set; } = true; public string HeaderName { get; set; } = "Idempotency-Key"; public List<string> ApplicableMethods { get; set; } = new() { "POST", "PATCH" }; public List<string> ExcludedPaths { get; set; } = new(); public Func<HttpContext, bool>? CustomFilter { get; set; } } public enum IdempotencyStatus { New, Processing, Completed, Expired } ``` ### 2. Cache Store Interface & Implementations ```csharp // Store/IIdempotencyStore.cs namespace IdempotencyMiddleware.Store; public interface IIdempotencyStore { Task<CachedResponse?> GetAsync(string key, CancellationToken ct = default); Task<bool> SetAsync(string key, CachedResponse response, TimeSpan ttl, CancellationToken ct = default); Task<bool> AcquireLockAsync(string key, TimeSpan timeout, CancellationToken ct = default); Task ReleaseLockAsync(string key, CancellationToken ct = default); Task<bool> ExistsAsync(string key, CancellationToken ct = default); } ``` ```csharp // Store/InMemoryIdempotencyStore.cs using System.Collections.Concurrent; using IdempotencyMiddleware.Models; namespace IdempotencyMiddleware.Store; public class InMemoryIdempotencyStore : IIdempotencyStore, IDisposable { private readonly ConcurrentDictionary<string, (CachedResponse Response, DateTimeOffset Expires)> _cache = new(); private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(); private readonly Timer _cleanupTimer; public InMemoryIdempotencyStore() { // Cleanup expired entries every 5 minutes _cleanupTimer = new Timer(Cleanup, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } public Task<CachedResponse?> GetAsync(string key, CancellationToken ct = default) { if (_cache.TryGetValue(key, out var entry) && entry.Expires > DateTimeOffset.UtcNow) return Task.FromResult<CachedResponse?>(entry.Response); _cache.TryRemove(key, out _); return Task.FromResult<CachedResponse?>(null); } public Task<bool> SetAsync(string key, CachedResponse response, TimeSpan ttl, CancellationToken ct = default) { var expires = DateTimeOffset.UtcNow.Add(ttl); _cache[key] = (response, expires); return Task.FromResult(true); } public async Task<bool> AcquireLockAsync(string key, TimeSpan timeout, CancellationToken ct = default) { var semaphore = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1)); return await semaphore.WaitAsync(timeout, ct); } public Task ReleaseLockAsync(string key, CancellationToken ct = default) { if (_locks.TryGetValue(key, out var semaphore)) semaphore.Release(); return Task.CompletedTask; } public Task<bool> ExistsAsync(string key, CancellationToken ct = default) { var exists = _cache.TryGetValue(key, out var entry) && entry.Expires > DateTimeOffset.UtcNow; return Task.FromResult(exists); } private void Cleanup(object? state) { var now = DateTimeOffset.UtcNow; foreach (var key in _cache.Keys) { if (_cache.TryGetValue(key, out var entry) && entry.Expires <= now) _cache.TryRemove(key, out _); } } public void Dispose() { _cleanupTimer.Dispose(); foreach (var semaphore in _locks.Values) semaphore.Dispose(); } } ``` ```csharp // Store/RedisIdempotencyStore.cs using System.Text.Json; using IdempotencyMiddleware.Models; using StackExchange.Redis; namespace IdempotencyMiddleware.Store; public class RedisIdempotencyStore : IIdempotencyStore { private readonly IConnectionMultiplexer _redis; private readonly IDatabase _db; private const string LockPrefix = "idempotency:lock:"; private const string CachePrefix = "idempotency:cache:"; public RedisIdempotencyStore(IConnectionMultiplexer redis) { _redis = redis; _db = redis.GetDatabase(); } public async Task<CachedResponse?> GetAsync(string key, CancellationToken ct = default) { var value = await _db.StringGetAsync(CachePrefix + key); return value.HasValue ? JsonSerializer.Deserialize<CachedResponse>(value!) : null; } public async Task<bool> SetAsync(string key, CachedResponse response, TimeSpan ttl, CancellationToken ct = default) { var serialized = JsonSerializer.Serialize(response); return await _db.StringSetAsync(CachePrefix + key, serialized, ttl); } public async Task<bool> AcquireLockAsync(string key, TimeSpan timeout, CancellationToken ct = default) { var lockKey = LockPrefix + key; var deadline = DateTimeOffset.UtcNow.Add(timeout); var retryDelay = TimeSpan.FromMilliseconds(50); while (DateTimeOffset.UtcNow < deadline) { // SET NX EX — atomic lock acquisition var acquired = await _db.StringSetAsync( lockKey, Environment.MachineName, timeout, When.NotExists ); if (acquired) return true; await Task.Delay(retryDelay, ct); retryDelay = TimeSpan.FromMilliseconds(Math.Min(retryDelay.TotalMilliseconds * 2, 500)); } return false; } public async Task ReleaseLockAsync(string key, CancellationToken ct = default) {

Enhanced response (with ZPI)

# Idempotency Key Middleware ## Overview This implementation intercepts POST requests, checks for an `Idempotency-Key` header, and returns cached responses for duplicate requests — preventing double-charges, duplicate orders, etc. --- ## NuGet Packages ```xml <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" /> <PackageReference Include="System.Text.Json" Version="8.0.0" /> ``` --- ## 1. Models ```csharp // Models/IdempotencyRecord.cs using System.ComponentModel.DataAnnotations; namespace IdempotencyMiddleware.Models; public sealed class IdempotencyRecord { [Key] public Guid Id { get; init; } = Guid.NewGuid(); [Required, MaxLength(256)] public string IdempotencyKey { get; init; } = default!; [Required, MaxLength(512)] public string RequestPath { get; init; } = default!; [Required, MaxLength(32)] public string RequestMethod { get; init; } = default!; /// <summary>MD5 hash of request body to detect key reuse with different payloads.</summary> [MaxLength(64)] public string? RequestBodyHash { get; init; } public int ResponseStatusCode { get; set; } public string? ResponseBody { get; set; } public string? ResponseHeaders { get; set; } public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow; public DateTimeOffset ExpiresAt { get; init; } public IdempotencyStatus Status { get; set; } = IdempotencyStatus.Processing; } public enum IdempotencyStatus { Processing = 0, Completed = 1, Failed = 2 } ``` ```csharp // Models/CachedResponse.cs namespace IdempotencyMiddleware.Models; public sealed record CachedResponse( int StatusCode, string Body, Dictionary<string, string[]> Headers ); ``` --- ## 2. Database Context ```csharp // Data/IdempotencyDbContext.cs using IdempotencyMiddleware.Models; using Microsoft.EntityFrameworkCore; namespace IdempotencyMiddleware.Data; public sealed class IdempotencyDbContext(DbContextOptions<IdempotencyDbContext> options) : DbContext(options) { public DbSet<IdempotencyRecord> IdempotencyRecords => Set<IdempotencyRecord>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<IdempotencyRecord>(entity => { entity.HasIndex(e => e.IdempotencyKey) .IsUnique() .HasDatabaseName("IX_IdempotencyRecords_Key"); entity.HasIndex(e => e.ExpiresAt) .HasDatabaseName("IX_IdempotencyRecords_ExpiresAt"); entity.Property(e => e.Status) .HasConversion<string>(); }); } } ``` --- ## 3. Options ```csharp // Options/IdempotencyOptions.cs namespace IdempotencyMiddleware.Options; public sealed class IdempotencyOptions { public const string SectionName = "Idempotency"; /// <summary>How long idempotency keys are retained.</summary> public TimeSpan KeyExpiry { get; set; } = TimeSpan.FromHours(24); /// <summary>Maximum wait time for an in-flight (Processing) request to complete.</summary> public TimeSpan LockTimeout { get; set; } = TimeSpan.FromSeconds(30); /// <summary>Polling interval when waiting for an in-flight request.</summary> public TimeSpan LockPollingInterval { get; set; } = TimeSpan.FromMilliseconds(250); /// <summary>HTTP methods subject to idempotency checks.</summary> public HashSet<string> ApplicableMethods { get; set; } = new(StringComparer.OrdinalIgnoreCase) { HttpMethods.Post }; /// <summary>Route prefixes to exclude (e.g. health checks, auth).</summary> public HashSet<string> ExcludedPaths { get; set; } = new(StringComparer.OrdinalIgnoreCase) { "/health", "/metrics" }; /// <summary>Response headers that should NOT be replayed.</summary> public HashSet<string> ExcludedReplayHeaders { get; set; } = new(StringComparer.OrdinalIgnoreCase) { "Transfer-Encoding", "Content-Length", "Date", "Set-Cookie" }; /// <summary>Require the header — reject requests that omit it.</summary> public bool EnforceIdempotencyKey { get; set; } = false; /// <summary>Cache layer: Redis preferred, falls back to in-memory.</summary> public bool UseDistributedCache { get; set; } = true; } ``` --- ## 4. Idempotency Store (abstraction + two implementations) ```csharp // Store/IIdempotencyStore.cs using IdempotencyMiddleware.Models; namespace IdempotencyMiddleware.Store; public interface IIdempotencyStore { /// <summary> /// Atomically inserts a Processing record. /// Returns (true, null) → caller owns the request, must call Complete/Fail. /// Returns (false, record) → a prior record exists; caller should replay it. /// </summary> Task<(bool IsOwner, IdempotencyRecord? ExistingRecord)> TryAcquireAsync( string key, string path, string method, string? bodyHash, TimeSpan expiry, CancellationToken ct = default); Task<IdempotencyRecord?> GetAsync(string key, CancellationToken ct = default); Task CompleteAsync(string key, CachedResponse response, CancellationToken ct = default); Task FailAsync(string key, CancellationToken ct = default); } ``` ```csharp // Store/DistributedCacheIdempotencyStore.cs using System.Text.Json; using IdempotencyMiddleware.Models; using Microsoft.Extensions.Caching.Distributed; namespace IdempotencyMiddleware.Store; /// <summary>Redis-backed store. Uses a "lock" key to achieve atomic acquire semantics.</summary> public sealed class DistributedCacheIdempotencyStore(IDistributedCache cache) : IIdempotencyStore { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); private static string LockKey(string key) => $"idempotency:lock:{key}"; private static string RecordKey(string key) => $"idempotency:record:{key}"; public async Task<(bool IsOwner, IdempotencyRecord? ExistingRecord)> TryAcquireAsync( string key, string path, string method, string? bodyHash, TimeSpan expiry, CancellationToken ct = default) { // Check for existing completed/in-flight record first var existing = await GetAsync(key, ct); if (existing is not null) return (false, existing); // Try to set a distributed lock (NX semantics via SetAsync with condition) // Redis does not expose NX directly through IDistributedCache, so we use // a short absolute-expiry "lock" key as a best-effort compare-and-set. var lockEntry = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.

Grader chain

Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.

V1claude-sonnet-4-6
Better
V4qwen2.5-coder-7b-instruct
Better

Comments

Our grader said what it said. What do you say? Comment as a guest below.

No comments yet. Be the first to say what you make of this round.

← back to all rounds