From 6738cfda2fb07dca7fd982c249730d4b6b61364c Mon Sep 17 00:00:00 2001 From: Ilya Shumilin Date: Thu, 7 Mar 2024 16:12:32 +0500 Subject: [PATCH 1/4] Add UseIdempotencyOption --- .../Filters/IdempotencyAttribute.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/IdempotentAPI/Filters/IdempotencyAttribute.cs b/src/IdempotentAPI/Filters/IdempotencyAttribute.cs index 9310dec..2fd4019 100644 --- a/src/IdempotentAPI/Filters/IdempotencyAttribute.cs +++ b/src/IdempotentAPI/Filters/IdempotencyAttribute.cs @@ -2,6 +2,7 @@ using IdempotentAPI.AccessCache; using IdempotentAPI.Core; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace IdempotentAPI.Filters @@ -47,25 +48,32 @@ public double ExpiresInMilliseconds /// public bool IsIdempotencyOptional { get; set; } = DefaultIdempotencyOptions.IsIdempotencyOptional; + /// + /// By default, idempotency settings are taken from the attribute properties. + /// When this flag is set to true, the settings will be taken from the registered in the ServiceCollection + /// + public bool UseIdempotencyOption { get; set; } = false; + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var distributedCache = (IIdempotencyAccessCache)serviceProvider.GetService(typeof(IIdempotencyAccessCache)); var loggerFactory = (ILoggerFactory)serviceProvider.GetService(typeof(ILoggerFactory)); + var idempotencyOptions = UseIdempotencyOption ? serviceProvider.GetRequiredService() : this; - TimeSpan? distributedLockTimeout = DistributedLockTimeoutMilli >= 0 - ? TimeSpan.FromMilliseconds(DistributedLockTimeoutMilli) + TimeSpan? distributedLockTimeout = idempotencyOptions.DistributedLockTimeoutMilli >= 0 + ? TimeSpan.FromMilliseconds(idempotencyOptions.DistributedLockTimeoutMilli) : null; return new IdempotencyAttributeFilter( distributedCache, loggerFactory, Enabled, - ExpiresInMilliseconds, - HeaderKeyName, - DistributedCacheKeysPrefix, + idempotencyOptions.ExpiresInMilliseconds, + idempotencyOptions.HeaderKeyName, + idempotencyOptions.DistributedCacheKeysPrefix, distributedLockTimeout, - CacheOnlySuccessResponses, - IsIdempotencyOptional); + idempotencyOptions.CacheOnlySuccessResponses, + idempotencyOptions.IsIdempotencyOptional); } } -} +} \ No newline at end of file From 63b8df2ac286baa6683fff4be520532e0b18887a Mon Sep 17 00:00:00 2001 From: Ioannis Kyriakidis Date: Sun, 24 Mar 2024 10:00:10 +0200 Subject: [PATCH 2/4] New IdempotentAPI DI Extension to support IdempotencyOptions --- .../IdempotentAPIExtensions.cs | 17 ++++++++++- .../SingleApiTests.cs | 27 +++++++++++++++++ ...TestingIdempotentAPIPerMethodController.cs | 30 +++++++++++++++++++ tests/IdempotentAPI.TestWebAPIs/Startup.cs | 8 ++++- 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/IdempotentAPI.TestWebAPIs/Controllers/TestingIdempotentAPIPerMethodController.cs diff --git a/src/IdempotentAPI/Extensions/DependencyInjection/IdempotentAPIExtensions.cs b/src/IdempotentAPI/Extensions/DependencyInjection/IdempotentAPIExtensions.cs index bced899..ed0edf3 100644 --- a/src/IdempotentAPI/Extensions/DependencyInjection/IdempotentAPIExtensions.cs +++ b/src/IdempotentAPI/Extensions/DependencyInjection/IdempotentAPIExtensions.cs @@ -9,7 +9,7 @@ namespace IdempotentAPI.Extensions.DependencyInjection public static class IdempotentAPIExtensions { /// - /// Register the Core services that are required by the IdempotentAPI. Currently, it only registers the service to access the cache (). + /// Register the Core service that is required by the IdempotentAPI (). /// /// /// @@ -20,10 +20,25 @@ public static IServiceCollection AddIdempotentAPI(this IServiceCollection servic return serviceCollection; } + /// + /// Register the Core service that is required by the IdempotentAPI () and register the that will enable the use of the . + /// + /// + /// It will enable the use of the . So, afterward, you could use the: [Idempotent(UseIdempotencyOption = true)] + /// + public static IServiceCollection AddIdempotentAPI(this IServiceCollection serviceCollection, IdempotencyOptions idempotencyOptions) + { + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(idempotencyOptions); + + return serviceCollection; + } + /// /// Register the Core services that are required by the IdempotentAPI for Minimal APIs. /// /// + /// /// public static IServiceCollection AddIdempotentMinimalAPI(this IServiceCollection serviceCollection, IdempotencyOptions idempotencyOptions) { diff --git a/tests/IdempotentAPI.IntegrationTests/SingleApiTests.cs b/tests/IdempotentAPI.IntegrationTests/SingleApiTests.cs index 024bc15..317f968 100644 --- a/tests/IdempotentAPI.IntegrationTests/SingleApiTests.cs +++ b/tests/IdempotentAPI.IntegrationTests/SingleApiTests.cs @@ -59,6 +59,33 @@ public async Task PostTest_ShouldReturnCachedResponse(int httpClientIndex) content1.Should().Be(content2); } + + [Fact] + public async Task PostTest_WhenUsingIdempotencyOptionOnWebApiClient_ShouldReturnCachedResponse() + { + // Arrange + var guid = Guid.NewGuid().ToString(); + + int httpClientIndex = WebApiClientIndex; + _httpClients[httpClientIndex].DefaultRequestHeaders.Clear(); + _httpClients[httpClientIndex].DefaultRequestHeaders.Add("IdempotencyKey", guid); + + // Act + var response1 = await _httpClients[httpClientIndex].PostAsync("v6/TestingIdempotentAPIPerMethod/testUseIdempotencyOption", null); + var response2 = await _httpClients[httpClientIndex].PostAsync("v6/TestingIdempotentAPIPerMethod/testUseIdempotencyOption", null); + + // Assert + var content1 = await response1.Content.ReadAsStringAsync(); + var content2 = await response2.Content.ReadAsStringAsync(); + _testOutputHelper.WriteLine($"content1: {Environment.NewLine}{content1}"); + _testOutputHelper.WriteLine($"content2: {Environment.NewLine}{content2}"); + + response1.StatusCode.Should().Be(HttpStatusCode.OK, content1); + response2.StatusCode.Should().Be(HttpStatusCode.OK, content2); + + content1.Should().Be(content2); + } + [Theory] [InlineData(WebApiClientIndex)] [InlineData(WebMinimalApiClientIndex)] diff --git a/tests/IdempotentAPI.TestWebAPIs/Controllers/TestingIdempotentAPIPerMethodController.cs b/tests/IdempotentAPI.TestWebAPIs/Controllers/TestingIdempotentAPIPerMethodController.cs new file mode 100644 index 0000000..d31da9c --- /dev/null +++ b/tests/IdempotentAPI.TestWebAPIs/Controllers/TestingIdempotentAPIPerMethodController.cs @@ -0,0 +1,30 @@ +using IdempotentAPI.Filters; +using IdempotentAPI.TestWebAPIs.DTOs; +using Microsoft.AspNetCore.Mvc; + +namespace IdempotentAPI.TestWebAPIs.Controllers +{ + [ApiController] + [ApiVersion("6.0")] + [Route("v{version:apiVersion}/[controller]")] + + [Consumes("application/json")] + [Produces("application/json")] + + public class TestingIdempotentAPIPerMethodController : ControllerBase + { + private readonly ILogger _logger; + + public TestingIdempotentAPIPerMethodController(ILogger logger) + { + _logger = logger; + } + + [HttpPost("testUseIdempotencyOption")] + [Idempotent(UseIdempotencyOption = true)] + public ActionResult TestUseIdempotencyOption() + { + return Ok(new ResponseDTOs()); + } + } +} \ No newline at end of file diff --git a/tests/IdempotentAPI.TestWebAPIs/Startup.cs b/tests/IdempotentAPI.TestWebAPIs/Startup.cs index 7908587..44bf55d 100644 --- a/tests/IdempotentAPI.TestWebAPIs/Startup.cs +++ b/tests/IdempotentAPI.TestWebAPIs/Startup.cs @@ -1,6 +1,7 @@ using System.Net; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; using IdempotentAPI.Cache.FusionCache.Extensions.DependencyInjection; +using IdempotentAPI.Core; using IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock.Extensions.DependencyInjection; using IdempotentAPI.DistributedAccessLock.RedLockNet.Extensions.DependencyInjection; using IdempotentAPI.Extensions.DependencyInjection; @@ -33,9 +34,14 @@ public virtual void ConfigureServices(IServiceCollection services) // Add services to the container. services.AddApiVersioningConfigured(); + IdempotencyOptions idempotencyOptions = new() + { + CacheOnlySuccessResponses = true, + DistributedLockTimeoutMilli = 2000, + }; // Register the IdempotentAPI Core services. - services.AddIdempotentAPI(); + services.AddIdempotentAPI(idempotencyOptions); services.AddSwaggerGen(x => x.SwaggerDoc("v6", new OpenApiInfo { Title = "IdempotentAPI.TestWebAPIs1 - Swagger", Version = "v6" })); From 16281dc6a77ab9af3a84fff94966ae7ef495ea50 Mon Sep 17 00:00:00 2001 From: Ioannis Kyriakidis Date: Tue, 2 Apr 2024 09:52:55 +0300 Subject: [PATCH 3/4] Update the README and CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++++ README.md | 10 ++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82065d2..371469b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.0] - 2024-04-04 +- 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 predefined `IIdempotencyOptions`. + ```c# + // Register the Core service and the `IIdempotencyOptions`. + services.AddIdempotentAPI(idempotencyOptions); + ``` + + ```c# + // To use the `IIdempotencyOptions`, set the `UseIdempotencyOption` property to `true`. + [HttpPost()] + [Idempotent(UseIdempotencyOption = true)] + public ActionResult AddMyEntity() + { + // ... + } + ``` + ## [2.3.0] - 2024-03-07 - Minimal APIs (IdempotentAPI.MinimalAPI v3.0.0) - 🌟 The `AddIdempotentMinimalAPI(...)` extension is introduced to simplify the `IdempotentAPI.MinimalAPI` registration with DI improvements by [@hartmark](https://github.com/hartmark). - ❗ IMPORTANT: To use the new extensions, the **BREAKING** `IdempotentAPI.MinimalAPI v3.0.0` should be used. diff --git a/README.md b/README.md index fd115f1..b844896 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Idempotent API (v2.3.0) +# Idempotent API (v2.4.0) @@ -121,7 +121,7 @@ The following figure shows a simplified example of the `IdempotentAPI` library f Let's see how we could use the NuGet packages in a Web API project. For more examples and code, you can check the [sample projects](https://github.com/ikyriak/IdempotentAPI/tree/master/samples). The `IdempotentAPI` can be installed via the NuGet UI or the NuGet package manager console: ```powershell -PM> Install-Package IdempotentAPI -Version 2.3.0 +PM> Install-Package IdempotentAPI -Version 2.4.0 ``` and, register the **IdempotentAPI Core services** for either controller-based APIs or minimal APIs. @@ -129,6 +129,12 @@ and, register the **IdempotentAPI Core services** for either controller-based AP ```c# // For Controller-Based APIs: services.AddIdempotentAPI(); + +// OR + +// For Controller-Based APIs: Register the `IIdempotencyOptions` that will enable the use of the `[Idempotent(UseIdempotencyOption = true)]` option. +services.AddIdempotentAPI(idempotencyOptions); + ``` OR From 89b8bf65e68239200b55994a39fc40c2e9a6f73a Mon Sep 17 00:00:00 2001 From: Ioannis Kyriakidis Date: Wed, 3 Apr 2024 09:29:52 +0300 Subject: [PATCH 4/4] Set IdempotentAPI v2.4.0 --- CHANGELOG.md | 2 +- src/IdempotentAPI/IdempotentAPI.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371469b..ef56554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.4.0] - 2024-04-04 +## [2.4.0] - 2024-04-03 - 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 predefined `IIdempotencyOptions`. ```c# // Register the Core service and the `IIdempotencyOptions`. diff --git a/src/IdempotentAPI/IdempotentAPI.csproj b/src/IdempotentAPI/IdempotentAPI.csproj index e330117..f8308fa 100644 --- a/src/IdempotentAPI/IdempotentAPI.csproj +++ b/src/IdempotentAPI/IdempotentAPI.csproj @@ -4,7 +4,7 @@ netstandard2.0;netstandard2.1 Ioannis Kyriakidis ikyriakidis.com - 2.3.0 + 2.4.0 IdempotentAPI is an ASP.NET Core attribute by which any HTTP write operations (POST and PATCH) can have effect only once for the given request data. idempotent api;idempotency;asp.net core;attribute;middleware;rest https://github.com/ikyriak/IdempotentAPI.git