Implementing Idempotency in .NET Core
Implementing idempotency in .NET Core can be accomplished with a filter, which I will cover at a high level.
Putting it simply, idempotency is the concept of executing an operation multiple times and getting the same result back, given the same input parameters for each request. In our case, we will be setting up idempotency for an API endpoint.
Idempotence Key
To implement idempotency in your APIs (or any other process), we will need a unique identifier or an idempotence key.
The calling client can send the idempotence key from a browser or another API (backend to backend). In this case, the calling client can send this impotence key via a custom header (i.e. `X-Idempotence-Key`) or in the body of the request.
Another alternative is to use the data already present in the body of the request as the unique identifier. Either by taking the entire request body or a unique identifier within the body.
For the rest of this article, let's say the client is sending a request with the following request body:
{
"clientId": "26622324-b9eb-4d7f-bd24-fb1a1b21e3b0"
"transactionId": "234",
"profile": {
"firstName": "Esau",
"lastName": "Silva",
"email": "esau@silva.com"
},
"achAccount": {
"aba": "123456789",
"checkNumber": "0123",
"accountNumber": "45678903",
"amountInCents": 15000
}
}
Based on this request, we can take the `clientId` and `transactionId` as the idempotency key instead of having the client send us additional data.
Given the nature that `transactionId` is a unique value coming from the client and the `clientId` is a unique value in our system. This combination of request fields will give us a unique identifier across our system.
High-Level Implementation
Say we want to add the idempotency filter to a POST endpoint with the following route: `/deposit/ach`
// Progam.cs
app.MapPost("/deposit/ach", Deposit.PostAch)
.AddEndpointFilter<IdempotencyFilter>();
The implementation of `Deposit.PostAch` does not matter much, but below is the shell of the method
public static class Deposit
{
public static async Task<IResult> PostAch
(
HttpContext context,
RequestModel request,
DepositService depositService,
CancellationToken cancellationToken
)
{
// Implenentation details
var response = await depositService
.PerformDeposit(request, cancellationToken);
return Results.Ok(response);
}
}
Now, the implementation of the Idempotency Filter.
One thing to note is the `isInFlight` flag, which represents a request that has not yet finished processing. Say, the client sends a request for the first time, and the request takes some time to process. There is a network failure that prompts the client to send a subsequent retry request, the `isInFlight` flag would be true and the API would return a 409 Status Code, indicating that the request is still processing.
At this point, the client has the option to wait a second or so before sending the same request again to get the idempotent response.
Originally published to my blog: https://esausilva.com/2025/01/05/implementing-idempotency-in-net-core/