All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
⚙ IdempotentAPI.MinimalAPI
v3.2.0
: Add the option to configure special types in theIIdempotencyOptionsProvider
to be excluded from the API actions (e.g.,[FromServices] DbContext context
). This will solve the self-referencing loop issue. For example, we can configure theExcludeRequestSpecialTypes
in the following way. Thank you, @csimonsson, for reporting issue #81 and help improving the code.// Program.cs // ... builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider()); // ...
// IdempotencyOptionsProvider.cs using IdempotentAPI.Core; using IdempotentAPI.MinimalAPI; using IdempotentAPI.TestWebMinimalAPIs.ApiContext; using Microsoft.EntityFrameworkCore; namespace IdempotentAPI.TestWebMinimalAPIs { public class IdempotencyOptionsProvider : IIdempotencyOptionsProvider { private readonly List<Type> ExcludeRequestSpecialTypes = new() { typeof(DbContext), }; public IIdempotencyOptions GetIdempotencyOptions(IHttpContextAccessor httpContextAccessor) { // WARNING: This example implementation shows we can provide different IdempotencyOptions per case. //switch (httpContextAccessor?.HttpContext?.Request.Path) //{ // case "/v6/TestingIdempotentAPI/test": // return new IdempotencyOptions() // { // ExpireHours = 1, // ExcludeRequestSpecialTypes = ExcludeRequestSpecialTypes, // }; //} return new IdempotencyOptions() { ExcludeRequestSpecialTypes = ExcludeRequestSpecialTypes, }; } } }
- 🌟 Support FastEndpoints, a developer-friendly alternative to Minimal APIs and MVC. Thank you, @CaptainPowerTurtle, for reporting issue #72 and @dj-nitehawk for helping me integrate with
FastEndpoints
🙏💪. - ⚙ Add the option to configure the Newtonsoft
SerializerSettings
based on our needs. For example, this will enable us to use NodaTime in our DTOs. Thank you, @angularsen, for reporting the issue #74 and sharing your ideas to improve theIdempotentAPI
library 🙏. - IdempotentAPI.MinimalAPI
v3.1.0
:- ⚙ Configure the idempotent options by implementing the
IIdempotencyOptionsProvider
to provide theIIdempotencyOptions
based on our needs (e.g., per endpoint). For example, we could return theIIdempotencyOptions
based on the requested path and registerIdempotencyOptionsProvider
in theProgram.cs
. Thank you, @JonasLeetTheWay for reporting issue #73 🙏.// Program.cs builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider());
public class IdempotencyOptionsProvider : IIdempotencyOptionsProvider { public IIdempotencyOptions GetIdempotencyOptions(IHttpContextAccessor httpContextAccessor) { switch (httpContextAccessor?.HttpContext?.Request.Path) { case "/v6/TestingIdempotentAPI/test": return new IdempotencyOptions() { ExpireHours = 1, }; } return new IdempotencyOptions(); } }
- ...
- ⚙ Configure the idempotent options by implementing the
-
Add an extension to register the
IIdempotencyOptions
that will enable the use of the[Idempotent(UseIdempotencyOption = true)]
option. In this way, the attribute will use the predefinedIIdempotencyOptions
. -
Thank you, @Jevvry, for your time and implementation. This was an excellent idea (#68) 🙏💪.
// Register the Core service and the `IIdempotencyOptions`. services.AddIdempotentAPI(idempotencyOptions);
// To use the `IIdempotencyOptions`, set the `UseIdempotencyOption` property to `true`. [HttpPost()] [Idempotent(UseIdempotencyOption = true)] public ActionResult AddMyEntity() { // ... }
- 🌟 The
AddIdempotentMinimalAPI(...)
extension is introduced to simplify theIdempotentAPI.MinimalAPI
registration with DI improvements by @hartmark.- ❗ IMPORTANT: To use the new extensions, the BREAKING
IdempotentAPI.MinimalAPI v3.0.0
should be used. - The new extensions register the following services:
public static IServiceCollection AddIdempotentMinimalAPI(this IServiceCollection serviceCollection, IdempotencyOptions idempotencyOptions) { serviceCollection.AddSingleton<IIdempotencyAccessCache, IdempotencyAccessCache>(); serviceCollection.AddSingleton<IIdempotencyOptions>(idempotencyOptions); serviceCollection.AddTransient(serviceProvider => { var distributedCache = serviceProvider.GetRequiredService<IIdempotencyAccessCache>(); var logger = serviceProvider.GetRequiredService<ILogger<Idempotency>>(); var idempotencyOptions = serviceProvider.GetRequiredService<IIdempotencyOptions>(); return new Idempotency( distributedCache, logger, idempotencyOptions.ExpiresInMilliseconds, idempotencyOptions.HeaderKeyName, idempotencyOptions.DistributedCacheKeysPrefix, TimeSpan.FromMilliseconds(idempotencyOptions.DistributedLockTimeoutMilli), idempotencyOptions.CacheOnlySuccessResponses, idempotencyOptions.IsIdempotencyOptional); }); return serviceCollection; }
- ❗ IMPORTANT: To use the new extensions, the BREAKING
- ✅ Fix for Minimal API: When the special types (such as
HttpRequest
) are used as arguments, a Newtonsoft serialization exception for a self-referencing loop is thrown. The primary exception information is the following. Thank you to @hartmark for reporting and investigating this issue (#65) 🙏.Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'ServiceProvider' with type 'Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope'
- 🌟 Configure idempotency to be optional by setting the
IsIdempotencyOptional
option in the attribute. Thank you to @vIceBerg, @morgan35543, and @SebastianBienert for requesting this feature (#56) 🙏❤. - 🌟 Use
double
milliseconds (ExpiresInMilliseconds
) to define cache expiration instead of hours in integer.
- ✅ The request data hash always returns an empty byte array. This results in unwanted behavior because requests with different payloads and the same idempotency key are treated the same way. Thanks to @vaspopinex for identifying and reporting this issue (#58) 🙏.
-
❗ If you are updating from version
1.0.*
, this is a BREAKING change. In such a case, read the2.0.0-RC.1
change log below. -
🌟 All code uses
async
to avoid thread pool starvation in high-load scenarios. Thanks to @dimmy-timmy for implementing it (#47) 🙏. -
🌟The
IdempotentAPI.MinimalAPI
is introduced to useIdempotentAPI
in Minimal APIs. Just add theIdempotentAPIEndpointFilter
in your endpoints. Thank to @hartmark for implementing it (#45) 🙏.-
app.MapPost("/example", ([FromQuery] string yourParam) => { return Results.Ok(new ResponseDTOs()); }) .AddEndpointFilter<IdempotentAPIEndpointFilter>();
-
-
✅ GitHub actions configuration added to run CI build and test on pull requests. Thanks to @dimmy-timmy 💪.
-
✅ Integration Tests are improved to run on CI using
WebApplicationFactory
. Thanks to @dimmy-timmy 💪.
-
✅ There were two cases in which the IdempotentAPI didn't respond as expected. For that reason, we made some corrections and improvements. Thanks to @kvuong for reporting this issue (#37) 💪🙏.
- When the controller returns a non-successful response (4xx, 5xx, etc.), the IdempotentAPI cache the error response. In some cases, maybe we would like this behavior. For that reason, we have added the
CacheOnlySuccessResponses
attribute option to set it per case (default value:True
). - When an exception occurs in the controller, the IdempotentAPI stack is in the in-flight mode by returning a
409 Conflict
response in the subsequent requests. Thus, we have made a fix to remove the in-flight request on exceptions. - However, as long as a request is in inflight mode (running), all other requests will still get a
409 Conflict
response. For that reason, we should be careful when configuring the request timeout.
- When the controller returns a non-successful response (4xx, 5xx, etc.), the IdempotentAPI cache the error response. In some cases, maybe we would like this behavior. For that reason, we have added the
-
✅ There was a bug when a request body was big enough (e.g., 30kb+). The cache couldn't appropriately be fetched because of a different hash string. Thanks, @bernardiego, for taking the time to report and provide a fix for this issue (#38) 🙏❤.
-
✅ Fix a bug in the reconstruction of the
ObjectResult
responses. Thanks to @MohamadTahir for reporting this issue (#39) and providing a workaround 🙏.
-
❗ The
CacheOnlySuccessResponses
attribute option is included (default value:True
) to cache only 2xx HTTP responses. -
🌟 Support idempotency in a Cluster Environment (i.e., a group of multiple server instances) using Distributed Locks. Refactoring has been performed to include additional abstractions and distinguish the Caching (
IIdempotencyCache
), Distributed Locks (IDistributedAccessLockProvider
), and Accessing of them (IIdempotencyAccessCache
). Thanks to @Rast1234 for showing the need for this feature 💪🙏. Currently, we support the following two implementations.- 🌟 The
DistributedLockTimeoutMilli
attribute option is included to configure the time the distributed lock will wait for the lock to be acquired (in milliseconds).
Supported Technologies DI Registration samcook/RedLock.net Redis Redlock services.AddRedLockNetDistributedAccessLock(redisEndpoints);
madelson/DistributedLock Redis, SqlServer, Postgres and many more. services.AddMadelsonDistributedAccessLock();
- 🌟 The
-
❗ IMPORTANT: We should register the IdempotentAPI Core services.
-
services.AddIdempotentAPI();
-
-
❗ IMPORTANT: The
IdempotentAPI.Cache
has been renamed toIdempotentAPI.Cache.Abstractions
. So, you should remove theIdempotentAPI.Cache
NuGet package and use theIdempotentAPI.Cache.Abstractions
when needed. -
Dependency Updates
- Update
Newtonsoft.Json
from12.0.3
to13.0.1
. - Update
Microsoft.Extensions.Caching.Abstractions
from3.1.21
to6.0.0
. - Update
Microsoft.Extensions.DependencyInjection.Abstractions
from3.1.22
to6.0.0
. - Update
ZiggyCreatures.FusionCache
from0.1.7
to0.13.0
. - Update
ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson
from0.1.7
to0.13.0
.
- Update
- Idempotency did not work as expected when the return type on the controller action was a custom object and not an
ActionResult
. (#33) - Thanks to @MohamadTahir for reporting and investigating this issue 🙏.
-
📝 This
CHANGELOG.md
file quickly shows the notable changes we have performed between the project's releases (versions). -
🌟 Support for ASP.NET Core 5.0 and 6.0 by stopping using the obsolete
BinaryFormatter
and using theNewtonsoft JsonSerializer
(recommended action). -
🌟 Define the
IIdempotencyCache
interface to decide which caching implementation is appropriate in each use case. Currently, we support the following two implementations. However, you can use your implementation 😉.Support Concurrent Requests Primary Cache 2nd-Level Cache Advanced features IdempotentAPI.Cache.DistributedCache (Default) ✔️ IDistributedCache ❌ ❌ IdempotentAPI.Cache.FusionCache ✔️ Memory Cache ✔️
(IDistributedCache)✔️ -
🌟 Support the FusionCache, which provides high performance and robust cache with an optional distributed 2nd layer and some advanced features.
FusionCache
also includes some advanced features like a fail-safe mechanism, cache stampede prevention, fine grained soft/hard timeouts with background factory completion, extensive customizable logging, and more.
-
⚙ Configure the logging level of the
IdempotentAPI
logs that we would like to keep. For example, as we can see in the following JSON, we can setIdempotentAPI.Core.Idempotency
in theappsettings.json
.{ "Logging": { "LogLevel": { "Default": "Information", "IdempotentAPI.Core.Idempotency": "Warning" } } }
- The Logger name is changed from
IdempotencyAttributeFilter
toIdempotency
. Thus, in theappsettings.json
file we should configure the logging level using the nameIdempotentAPI.Core.Idempotency
e.g.,"IdempotentAPI.Core.Idempotency": "Information"
. - Dependencies of
IdempotentAPI
:- Remove
Microsoft.AspNetCore (2.2.0)
. - Remove
Microsoft.AspNetCore.Mvc.Abstractions (2.2.0)
. - Remove
Microsoft.Extensions.Caching.Abstractions (2.2.0)
. - Update
Newtonsoft.Json
from12.0.2
to12.0.3
.
- Remove
- 🌟 Prevent concurrent requests in our default caching implementation (IdempotentAPI.Cache.DistributedCache).
- @fjsosa for proposing a workaround to use
IdempotentAPI
in ASP.NET Core 5.0 and 6.0 in the meantime (#17) 🙏. - @william-keller for reporting the #23 and #25 issues 🙏❤.
- Issue: An
Invalid character in chunk size error
occurs on the second request when using the Kestrel server (#21). For that purpose, we are not caching theTransfer-Encoding
in the first request (excluded from cache). - Thanks to @apchenjun and @william-keller for reporting and helping solve this issue 💪🙏.
- Handle inflight (concurrent and long-running) requests. In such cases, the subsequent exact requests will get a
409 Conflict
response.
- Issue: Duplication on concurrent requests with the same key (#19).
- Thanks to @lvzhuye and @RichardGreen-IS2 for reporting and fixing the issue 🙏❤.
- A sample project is added (using .NET Core 3.1).
- Issue: Accessing form data throws an exception when the Content-Type is not supported (#14).
- Thanks to @apchenjun for reporting the issue 🙏.
- Support idempotency by implementing an ASP.NET Core attribute (
[Idempotent()]
) by which any HTTP write operations (POST and PATCH) can have effect only once for the given request data. - Use the
IDistributedCache
to cache the appropriate data. In this way, you can register your implementation, such as Memory Cache, SQL Server cache, Redis cache, etc. - Set different options per controller or/and action method via the
Idempotent
attribute, such as:Enabled
(default: true): Enable or Disable the Idempotent operation on an API Controller's class or method.ExpireHours
(default: 24): The cached idempotent data retention period.HeaderKeyName
(default:IdempotencyKey
): The name of the Idempotency-Key header.DistributedCacheKeysPrefix
(default:IdempAPI_
): A prefix for the key names that we will use in theDistributedCache
.