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 theIIdempotencyOptionsProviderto be excluded from the API actions (e.g.,[FromServices] DbContext context). This will solve the self-referencing loop issue. For example, we can configure theExcludeRequestSpecialTypesin 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
SerializerSettingsbased 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 theIdempotentAPIlibrary 🙏. - IdempotentAPI.MinimalAPI
v3.1.0:- ⚙ Configure the idempotent options by implementing the
IIdempotencyOptionsProviderto provide theIIdempotencyOptionsbased on our needs (e.g., per endpoint). For example, we could return theIIdempotencyOptionsbased on the requested path and registerIdempotencyOptionsProviderin 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
IIdempotencyOptionsthat 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.MinimalAPIregistration with DI improvements by @hartmark.- ❗ IMPORTANT: To use the new extensions, the BREAKING
IdempotentAPI.MinimalAPI v3.0.0should 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
IsIdempotencyOptionaloption in the attribute. Thank you to @vIceBerg, @morgan35543, and @SebastianBienert for requesting this feature (#56) 🙏❤. - 🌟 Use
doublemilliseconds (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.1change log below. -
🌟 All code uses
asyncto avoid thread pool starvation in high-load scenarios. Thanks to @dimmy-timmy for implementing it (#47) 🙏. -
🌟The
IdempotentAPI.MinimalAPIis introduced to useIdempotentAPIin Minimal APIs. Just add theIdempotentAPIEndpointFilterin 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
CacheOnlySuccessResponsesattribute 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 Conflictresponse 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 Conflictresponse. 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
ObjectResultresponses. Thanks to @MohamadTahir for reporting this issue (#39) and providing a workaround 🙏.
-
❗ The
CacheOnlySuccessResponsesattribute 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
DistributedLockTimeoutMilliattribute 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.Cachehas been renamed toIdempotentAPI.Cache.Abstractions. So, you should remove theIdempotentAPI.CacheNuGet package and use theIdempotentAPI.Cache.Abstractionswhen needed. -
Dependency Updates
- Update
Newtonsoft.Jsonfrom12.0.3to13.0.1. - Update
Microsoft.Extensions.Caching.Abstractionsfrom3.1.21to6.0.0. - Update
Microsoft.Extensions.DependencyInjection.Abstractionsfrom3.1.22to6.0.0. - Update
ZiggyCreatures.FusionCachefrom0.1.7to0.13.0. - Update
ZiggyCreatures.FusionCache.Serialization.NewtonsoftJsonfrom0.1.7to0.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.mdfile 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
BinaryFormatterand using theNewtonsoft JsonSerializer(recommended action). -
🌟 Define the
IIdempotencyCacheinterface 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.
FusionCachealso 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
IdempotentAPIlogs that we would like to keep. For example, as we can see in the following JSON, we can setIdempotentAPI.Core.Idempotencyin theappsettings.json.{ "Logging": { "LogLevel": { "Default": "Information", "IdempotentAPI.Core.Idempotency": "Warning" } } }
- The Logger name is changed from
IdempotencyAttributeFiltertoIdempotency. Thus, in theappsettings.jsonfile we should configure the logging level using the nameIdempotentAPI.Core.Idempotencye.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.Jsonfrom12.0.2to12.0.3.
- Remove
- 🌟 Prevent concurrent requests in our default caching implementation (IdempotentAPI.Cache.DistributedCache).
- @fjsosa for proposing a workaround to use
IdempotentAPIin 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 erroroccurs on the second request when using the Kestrel server (#21). For that purpose, we are not caching theTransfer-Encodingin 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 Conflictresponse.
- 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
IDistributedCacheto 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
Idempotentattribute, 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.