[C#] Fixing Duplicate API Requests by Idempotency-Key
ASP.NET Core
First, ensure that MemoryCache is available by adding it to your project. In Startup.cs or Program.cs, configure memory cache.
public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); // Register Memory Cache services.AddControllers(); }
This middleware will check if an incoming request has an Idempotency-Key and ensure that duplicate requests with the same key are rejected.
public class IdempotencyMiddleware { private readonly RequestDelegate _next; private readonly IMemoryCache _cache; public IdempotencyMiddleware(RequestDelegate next, IMemoryCache cache) { _next = next; _cache = cache; } public async Task InvokeAsync(HttpContext context) { if (!context.Request.Headers.TryGetValue("Idempotency-Key", out var idempotencyKey)) { context.Response.StatusCode = 400; // Bad Request await context.Response.WriteAsync("Idempotency-Key is required."); return; } // Check if the key exists in memory if (_cache.TryGetValue(idempotencyKey, out _)) { context.Response.StatusCode = 409; // Conflict await context.Response.WriteAsync("Duplicate request."); return; } // Store the key in cache to prevent duplicate processing _cache.Set(idempotencyKey, true, TimeSpan.FromMinutes(10)); // Cache for 10 minutes // Proceed to the next middleware or controller await _next(context); } }
In Startup.cs or Program.cs, register the middleware in the request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseMiddleware<IdempotencyMiddleware>(); // Add the middleware to the pipeline app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Here’s a sample API controller that can use this Idempotency middleware.
[ApiController] [Route("api/[controller]")] public class OrdersController : ControllerBase { [HttpPost] public IActionResult CreateOrder([FromBody] OrderRequest orderRequest) { // Simulate order creation logic return Ok(new { Message = "Order created successfully." }); } } public class OrderRequest { public string ProductId { get; set; } public int Quantity { get; set; } }
Explanation:
- Middleware: The IdempotencyMiddleware checks for an Idempotency-Key in the request headers. If it’s missing or duplicated, the request is rejected with an appropriate HTTP status code.
- Cache: The key is stored in MemoryCache for a short period (e.g., 10 minutes). This prevents duplicate requests within that time window.
- Flexibility: You can adjust the expiration time (TimeSpan.FromMinutes(10)) based on how long you want to keep requests idempotent.
ASP.NET Framework
In an ASP.NET Framework project, you can use MemoryCache from System.Runtime.Caching. You’ll need to add this in your Global.asax.cs file for configuration.
using System; using System.Runtime.Caching; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace YourAppNamespace { public class MvcApplication : System.Web.HttpApplication { public static ObjectCache Cache = MemoryCache.Default; protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); } } }
In ASP.NET Framework, you can use Action Filters to implement the Idempotency mechanism.
using System; using System.Runtime.Caching; using System.Web.Mvc; namespace YourAppNamespace.Filters { public class IdempotencyFilter : ActionFilterAttribute { private readonly ObjectCache _cache; public IdempotencyFilter() { _cache = MemoryCache.Default; } public override void OnActionExecuting(ActionExecutingContext filterContext) { var request = filterContext.HttpContext.Request; var response = filterContext.HttpContext.Response; // Check for Idempotency-Key in the headers var idempotencyKey = request.Headers["Idempotency-Key"]; if (string.IsNullOrEmpty(idempotencyKey)) { response.StatusCode = 400; // Bad Request filterContext.Result = new ContentResult { Content = "Idempotency-Key is required." }; return; } // Check if the key exists in cache if (_cache.Contains(idempotencyKey)) { response.StatusCode = 409; // Conflict filterContext.Result = new ContentResult { Content = "Duplicate request." }; return; } // Store the key in cache to prevent duplicate processing _cache.Add(idempotencyKey, true, DateTimeOffset.UtcNow.AddMinutes(10)); // Cache for 10 minutes } } }
Now, you can apply the IdempotencyFilter to your API controller or action method.
using System.Web.Mvc; using YourAppNamespace.Filters; namespace YourAppNamespace.Controllers { public class OrdersController : Controller { // Apply IdempotencyFilter at the action level [HttpPost] [IdempotencyFilter] public ActionResult CreateOrder(OrderRequest orderRequest) { // Simulate order creation logic return Json(new { Message = "Order created successfully." }); } } public class OrderRequest { public string ProductId { get; set; } public int Quantity { get; set; } } }
You can also register this filter globally in FilterConfig.cs if you want it applied to all your controllers.
using System.Web.Mvc; using YourAppNamespace.Filters; public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new IdempotencyFilter()); } }
Explanation:
- MemoryCache: We use MemoryCache from System.Runtime.Caching to store the Idempotency-Key for 10 minutes, preventing duplicate calls within that period.
- Idempotency Filter: The IdempotencyFilter checks the request headers for an Idempotency-Key, ensures the request is not duplicated, and stores the key in the cache.
- Controller: The controller action (CreateOrder) uses the IdempotencyFilter to ensure that duplicate calls with the same key are blocked.