9:16 AM Edit This 0 Comments »
Understanding Idempotency in .NET Core APIs: Building Reliable and
Duplicate-Safe Applications
Introduction
In modern distributed systems, network failures, retries,
and user actions such as repeatedly clicking a submit button can result in the
same request being sent multiple times. Without proper safeguards, duplicate
requests may create duplicate records, process payments multiple times, or
generate inconsistent data.
This is where idempotency becomes essential.
In this blog, we'll explore what idempotency is, why it
matters, and how to implement it effectively in ASP.NET Core Web APIs.
What is Idempotency?
An operation is idempotent if performing it multiple
times produces the same result as performing it once.
Example: POST /api/orders
Consider an API that creates an order:
Without idempotency:
- First
request → Order created
- Retry
request → Another order created
Result: Duplicate orders.
With idempotency:
- First
request → Order created
- Retry
request → Previously created order returned
Result: Only one order exists.
Why Do We Need Idempotency?
Idempotency helps solve several real-world challenges:
1. Network Timeouts
A client may not receive a response because of network
issues and may resend the request.
2. Client Retries
Mobile applications and frontend applications often
automatically retry failed requests.
3. User Errors
Users may click the Submit button multiple times.
4. Distributed Systems
Microservices frequently communicate through asynchronous
and potentially unreliable networks.
Without idempotency, these scenarios can cause:
- Duplicate
payments
- Duplicate
orders
- Duplicate
database records
- Inconsistent
business data
HTTP Methods and Idempotency
HTTP specification classifies methods based on idempotent behaviour.
|
Method |
Idempotent |
|
GET |
✅ Yes |
|
PUT |
✅ Yes |
|
DELETE |
✅ Yes |
|
HEAD |
✅ Yes |
|
POST |
❌ No |
|
PATCH |
Depends |
Implementing Idempotency in ASP.NET Core
The most common approach is using an Idempotency Key.
What is an Idempotency Key?
An idempotency key is a unique identifier generated by the
client and sent with every request.
The server stores this key along with the response.
If a request arrives with the same key:
- Server
checks existing records
- Returns
the previously stored response
- Avoids
executing business logic again
Step 1: Create an Idempotency Entity
public class IdempotencyRecord
{
public Guid Id { get; set; }
public string Key { get; set; }
public string ResponseBody { get;
set; }
public int StatusCode { get; set;
}
public DateTime CreatedAt { get;
set; }
}
Step 2: Configure DbContext
public class ApplicationDbContext : DbContext
{
public
DbSet<IdempotencyRecord> IdempotencyRecords { get; set; }
public
ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Step 3: Create Idempotency Filter
public class IdempotencyFilter : IAsyncActionFilter
{
private readonly
ApplicationDbContext _dbContext;
public
IdempotencyFilter(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task
OnActionExecutionAsync(
ActionExecutingContext
context,
ActionExecutionDelegate
next)
{
if
(!context.HttpContext.Request.Headers.TryGetValue(
"Idempotency-Key",
out var key))
{
await next();
return;
}
var existingRecord = await
_dbContext.IdempotencyRecords
.FirstOrDefaultAsync(x =>
x.Key == key);
if (existingRecord != null)
{
context.Result
= new ContentResult
{
Content =
existingRecord.ResponseBody,
StatusCode =
existingRecord.StatusCode,
ContentType =
"application/json"
};
return;
}
var executedContext = await next();
if (executedContext.Result is ObjectResult result)
{
var response = JsonSerializer.Serialize(result.Value);
_dbContext.IdempotencyRecords.Add(
new IdempotencyRecord
{
Key = key,
ResponseBody = response,
StatusCode = result.StatusCode ?? 200,
CreatedAt = DateTime.UtcNow
});
await _dbContext.SaveChangesAsync();
}
}
}
Step 4: Register Filter
builder.Services.AddScoped<IdempotencyFilter>();
Step 5: Apply to Controller
[HttpPost]
[ServiceFilter(typeof(IdempotencyFilter))]
public async Task<IActionResult>
CreateOrder(CreateOrderRequest request)
{
var order = new Order
{
Id = Guid.NewGuid(),
Amount = request.Amount
};
return Ok(order);
}
Common Use Cases
·
Payment Processing
·
Order Creation
·
Ticket Booking
Conclusion
Idempotency
is a critical design principle for building resilient and reliable APIs. While
HTTP methods like GET,
PUT, and DELETE are naturally idempotent, POST operations often
require additional mechanisms such as Idempotency
Keys to prevent duplicate processing.
In
ASP.NET Core, implementing idempotency using middleware, action filters, Redis,
or database-backed storage helps ensure that retries, network failures, and
user errors do not compromise the integrity of your system.
By
adopting idempotency, you can build APIs that are more reliable, scalable, and
production-ready—especially in domains like payments, e-commerce, booking
platforms, and financial services.
·


"If a row is added to a table..."
Should be: "If a column is added to a table..."