### Install IdempotentAPI NuGet Package Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Install the IdempotentAPI package using the NuGet Package Manager Console. ```powershell PM> Install-Package IdempotentAPI -Version 2.6.0 ``` -------------------------------- ### Register Madelson Distributed Access Lock (Redis Example) Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Install and register the IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock for cluster environment idempotency. This example uses Redis as the distributed lock technology. ```csharp // Register the distributed lock technology. // For this example, we are using Redis. var redicConnection = ConnectionMultiplexer.Connect("localhost:6379"); services.AddSingleton(_ => new RedisDistributedSynchronizationProvider(redicConnection.GetDatabase())); // Register the IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock services.AddMadelsonDistributedAccessLock(); ``` -------------------------------- ### Example cURL Request for Idempotent Payment API Source: https://context7.com/ikyriak/idempotentapi/llms.txt This example demonstrates how to make a POST request to the idempotent payments API using cURL. It includes the necessary 'Content-Type' and 'Idempotency-Key' headers, along with the JSON payload. The 'Idempotency-Key' should be unique for each logical operation. ```bash # curl example: # curl -X POST https://api.example.com/api/payments \ # -H "Content-Type: application/json" \ # -H "Idempotency-Key: payment-$(uuidgen)" \ # -d '{"customerId": "cust_123", "amount": 99.99, "currency": "USD"}' ``` -------------------------------- ### Idempotent Attribute Configuration Example Source: https://context7.com/ikyriak/idempotentapi/llms.txt Demonstrates how to configure the Idempotent attribute with various options like enabling idempotency, setting cache expiration, header key name, cache prefix, caching only successful responses, distributed lock timeout, and making idempotency optional. ```csharp using IdempotentAPI.Filters; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/[controller]")] public class ConfigExampleController : ControllerBase { [HttpPost] [Idempotent( Enabled = true, // Enable/disable idempotency (default: true) ExpiresInMilliseconds = 86400000, // Cache retention: 24 hours (default) HeaderKeyName = "IdempotencyKey", // Header name for the key (default: "IdempotencyKey") DistributedCacheKeysPrefix = "IdempAPI_", // Prefix for cache keys (default: "IdempAPI_") CacheOnlySuccessResponses = true, // Only cache 2xx responses (default: true) DistributedLockTimeoutMilli = 5000, // Lock timeout for clusters (default: -1, disabled) IsIdempotencyOptional = false )] public IActionResult ConfiguredEndpoint([FromBody] object data) { return Ok(new { Message = "Processed" }); } // Optional idempotency - works with or without IdempotencyKey header // Useful for backward compatibility when adding idempotency to existing APIs [HttpPost("optional")] [Idempotent(IsIdempotencyOptional = true)] public IActionResult OptionalIdempotency([FromBody] object data) { return Ok(new { Message = "Processed" }); } } // With optional idempotency: // curl -X POST https://localhost:5001/api/configexample/optional \ // -H "Content-Type: application/json" \ // -d '{"data": "test"}' // Works without IdempotencyKey header // // curl -X POST https://localhost:5001/api/configexample/optional \ // -H "Content-Type: application/json" \ // -H "IdempotencyKey: abc-123" \ // -d '{"data": "test"}' // Idempotency enforced when header is provided ``` -------------------------------- ### Idempotent API Controller Example Source: https://context7.com/ikyriak/idempotentapi/llms.txt Demonstrates an API controller using Idempotent attribute to handle requests idempotently. It outlines various success and error scenarios, including caching behavior and conflict responses. ```csharp // Controller demonstrating error scenarios [ApiController] [Route("api/[controller]")] [Idempotent(Enabled = true, CacheOnlySuccessResponses = true)] public class ErrorHandlingController : ControllerBase { [HttpPost] public IActionResult Process([FromBody] object data) { // Simulating different scenarios: // 1. Success (200 OK) - Response is cached // 2. Client error (4xx) - Not cached when CacheOnlySuccessResponses = true // 3. Server error (5xx) - Not cached when CacheOnlySuccessResponses = true return Ok(new { Status = "Success" }); } } // HTTP Response scenarios: // // 1. First request - Executes and caches response: // HTTP 200 OK // {"status": "Success"} // // 2. Retry with same IdempotencyKey - Returns cached response: // HTTP 200 OK // {"status": "Success"} // // 3. Missing IdempotencyKey header (when not optional): // HTTP 400 Bad Request // "The Idempotency header key is not found." // // 4. Concurrent request while first is still processing: // HTTP 409 Conflict // // 5. Same IdempotencyKey with different request body: // HTTP 400 Bad Request // "The Idempotency header key value 'xxx' was used in a different request." // // 6. Exception during processing: // Cache entry is removed, subsequent retries will re-execute ``` -------------------------------- ### Configure FusionCache with 2nd-Level Cache (Redis) Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Configure FusionCache for a 2nd-level cache by registering an IDistributedCache implementation (e.g., Redis) and FusionCache serialization. This example uses Redis and NewtonsoftJson. ```csharp // Register an implementation of the IDistributedCache. // For this example, we are using Redis. services.AddStackExchangeRedisCache(options => { options.Configuration = "YOUR CONNECTION STRING HERE, FOR EXAMPLE:localhost:6379"; }); // Register the FusionCache Serialization (e.g. NewtonsoftJson). // This is needed for the a 2nd-level cache. services.AddFusionCacheNewtonsoftJsonSerializer(); // Register the IdempotentAPI.Cache.FusionCache. // Optionally: Configure the FusionCacheEntryOptions. services.AddIdempotentAPIUsingFusionCache(); ``` -------------------------------- ### Register RedLock.net Distributed Access Lock Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Install and register the IdempotentAPI.DistributedAccessLock.RedLockNet for cluster environment idempotency using Redis Redlock. Define Redis endpoints. ```csharp // Define the Redis endpoints: List redisEndpoints = new List() { new DnsEndPoint("localhost", 6379) }; // Register the IdempotentAPI.DistributedAccessLock.RedLockNet: services.AddRedLockNetDistributedAccessLock(redisEndpoints); ``` -------------------------------- ### Register IdempotentAPI Core Services Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Use this in your application's startup configuration to register the necessary IdempotentAPI services. Ensure this is called before other services that depend on IdempotentAPI. ```csharp services.AddIdempotentAPI(); ``` -------------------------------- ### Minimal API with Custom Options Provider Source: https://context7.com/ikyriak/idempotentapi/llms.txt Implement IIdempotencyOptionsProvider to provide different idempotency configurations per endpoint. Exclude specific types like DbContext from request hash generation. ```csharp using IdempotentAPI.Core; using IdempotentAPI.MinimalAPI; using Microsoft.EntityFrameworkCore; public class IdempotencyOptionsProvider : IIdempotencyOptionsProvider { // Exclude EF DbContext and other injected types from request hash generation private readonly List ExcludeRequestSpecialTypes = new() { typeof(DbContext), }; public IIdempotencyOptions GetIdempotencyOptions(IHttpContextAccessor httpContextAccessor) { var path = httpContextAccessor?.HttpContext?.Request.Path.Value; // Custom options for specific endpoints return path switch { "/api/payments" => new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(72).TotalMilliseconds, CacheOnlySuccessResponses = true, ExcludeRequestSpecialTypes = ExcludeRequestSpecialTypes }, "/api/orders" => new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(24).TotalMilliseconds, IsIdempotencyOptional = true, ExcludeRequestSpecialTypes = ExcludeRequestSpecialTypes }, _ => new IdempotencyOptions { ExcludeRequestSpecialTypes = ExcludeRequestSpecialTypes } }; } } ``` ```csharp // Program.cs using IdempotentAPI.MinimalAPI; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDistributedMemoryCache(); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Register with custom options provider builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider()); var app = builder.Build(); app.MapPost("/api/payments", () => Results.Ok(new { PaymentId = Guid.NewGuid() })) .AddEndpointFilter(); app.MapPost("/api/orders", () => Results.Ok(new { OrderId = Guid.NewGuid() })) .AddEndpointFilter(); app.Run(); ``` -------------------------------- ### Register IdempotentAPI Core Services for Minimal APIs Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Register IdempotentAPI services for minimal APIs. Define default options or use a provider for more complex configurations. ```csharp // Option 1: Define the default options: builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptions()); ``` ```csharp // Option 2: Configure the idempotent options by implementing the `IIdempotencyOptionsProvider` to provide the `IIdempotencyOptions` based on our needs (e.g., per endpoint). For additional information, you can read the CHANGELOG.md. builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider()); ``` -------------------------------- ### Register Idempotency Core and Options Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Register the Core service and the `IIdempotencyOptions` to enable idempotency features. ```csharp services.AddIdempotentAPI(idempotencyOptions); ``` -------------------------------- ### Add Idempotent Minimal API Extension Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Introduced `AddIdempotentMinimalAPI(...)` extension for simplifying `IdempotentAPI.MinimalAPI` registration with DI improvements. Requires `IdempotentAPI.MinimalAPI v3.0.0`. ```csharp public static IServiceCollection AddIdempotentMinimalAPI(this IServiceCollection serviceCollection, IdempotencyOptions idempotencyOptions) { serviceCollection.AddSingleton(); serviceCollection.AddSingleton(idempotencyOptions); serviceCollection.AddTransient(serviceProvider => { var distributedCache = serviceProvider.GetRequiredService(); var logger = serviceProvider.GetRequiredService>(); var idempotencyOptions = serviceProvider.GetRequiredService(); return new Idempotency( distributedCache, logger, idempotencyOptions.ExpiresInMilliseconds, idempotencyOptions.HeaderKeyName, idempotencyOptions.DistributedCacheKeysPrefix, TimeSpan.FromMilliseconds(idempotencyOptions.DistributedLockTimeoutMilli), idempotencyOptions.CacheOnlySuccessResponses, idempotencyOptions.IsIdempotencyOptional); }); return serviceCollection; } ``` -------------------------------- ### Configure Production IdempotentAPI with Redis and RedLock Source: https://context7.com/ikyriak/idempotentapi/llms.txt This C# code configures ASP.NET Core services for production use of IdempotentAPI. It sets up Redis for caching, FusionCache with fail-safe mechanisms, and RedLock.net for distributed locking. Ensure your Redis connection string is correctly configured. ```csharp // Program.cs using System.Net; using IdempotentAPI.Core; using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.FusionCache.Extensions.DependencyInjection; using IdempotentAPI.DistributedAccessLock.RedLockNet.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; var builder = WebApplication.CreateBuilder(args); // Configure Redis connection var redisConnectionString = builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379"; // Step 1: Redis as distributed cache builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = redisConnectionString; }); // Step 2: FusionCache serializer builder.Services.AddFusionCacheNewtonsoftJsonSerializer(); // Step 3: FusionCache with fail-safe builder.Services.AddIdempotentAPIUsingFusionCache( cacheEntryOptions: new FusionCacheEntryOptions { Duration = TimeSpan.FromHours(24), IsFailSafeEnabled = true, FailSafeMaxDuration = TimeSpan.FromHours(72), FailSafeThrottleDuration = TimeSpan.FromSeconds(30) }, distributedCacheCircuitBreakerDuration: TimeSpan.FromSeconds(30) ); // Step 4: Distributed locking with RedLock List redisEndpoints = new() { new DnsEndPoint("localhost", 6379) }; builder.Services.AddRedLockNetDistributedAccessLock(redisEndpoints); // Step 5: IdempotentAPI with production options builder.Services.AddIdempotentAPI(new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(24).TotalMilliseconds, HeaderKeyName = "Idempotency-Key", DistributedCacheKeysPrefix = "prod_idempotent_", CacheOnlySuccessResponses = true, DistributedLockTimeoutMilli = 10000, // 10 second lock timeout IsIdempotencyOptional = false }); builder.Services.AddControllers(); var app = builder.Build(); app.UseExceptionHandler("/error"); app.MapControllers(); app.Run(); ``` -------------------------------- ### Configure Idempotency Options Provider per Endpoint Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Implement IIdempotencyOptionsProvider to dynamically configure idempotency options, such as expiration time, based on the request path. Register the provider in Program.cs. ```csharp // Program.cs builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider()); ``` ```csharp 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(); } } ``` -------------------------------- ### Enable Idempotency Options in Attribute Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md To use the `IIdempotencyOptions`, set the `UseIdempotencyOption` property to `true` on the `Idempotent` attribute. ```csharp [HttpPost()] [Idempotent(UseIdempotencyOption = true)] public ActionResult AddMyEntity() { // ... } ``` -------------------------------- ### Register IdempotentAPI Core Services for Controller-Based APIs Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Register IdempotentAPI services for controller-based APIs. Choose between per-controller options or global default options. ```csharp // Option 1: Use the options defined per Controller (i.e., in the attributes). services.AddIdempotentAPI(); ``` ```csharp // Option 2: Register the `IIdempotencyOptions` that will enable the use of the `[Idempotent(UseIdempotencyOption = true)]` option to set global default options. services.AddIdempotentAPI(idempotencyOptions); ``` -------------------------------- ### Register IdempotentAPI with FusionCache Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Register the IdempotentAPI.Cache.FusionCache service. Optionally, configure FusionCacheEntryOptions for advanced features. ```csharp // Register the IdempotentAPI.Cache.FusionCache. // Optionally: Configure the FusionCacheEntryOptions. services.AddIdempotentAPIUsingFusionCache(); ``` -------------------------------- ### Configure IdempotentAPI with FusionCache and Redis Source: https://context7.com/ikyriak/idempotentapi/llms.txt Sets up IdempotentAPI to use FusionCache with Redis as a 2nd-level cache. This configuration enables advanced caching features like fail-safe and cache stampede prevention. ```csharp // Program.cs using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.FusionCache.Extensions.DependencyInjection; using ZiggyCreatures.Caching.Fusion; var builder = WebApplication.CreateBuilder(args); // Step 1: Register Redis as distributed cache (2nd-level cache) builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; }); // Step 2: Register FusionCache JSON serializer (required for 2nd-level cache) builder.Services.AddFusionCacheNewtonsoftJsonSerializer(); // Step 3: Register IdempotentAPI with FusionCache builder.Services.AddIdempotentAPIUsingFusionCache( cacheEntryOptions: new FusionCacheEntryOptions { Duration = TimeSpan.FromHours(24), // Fail-safe: return stale data if Redis is down IsFailSafeEnabled = true, FailSafeMaxDuration = TimeSpan.FromHours(48), FailSafeThrottleDuration = TimeSpan.FromSeconds(30) }, distributedCacheCircuitBreakerDuration: TimeSpan.FromSeconds(30) ); // Step 4: Register IdempotentAPI core services builder.Services.AddIdempotentAPI(); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` -------------------------------- ### Configure Custom Newtonsoft Serializer Settings Source: https://context7.com/ikyriak/idempotentapi/llms.txt Set up custom JsonSerializerSettings to enable support for NodaTime types within IdempotentAPI. This involves configuring the settings before adding the IdempotentAPI services. ```csharp // Program.cs using IdempotentAPI.Core; using IdempotentAPI.Extensions.DependencyInjection; using Newtonsoft.Json; using NodaTime; using NodaTime.Serialization.JsonNet; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDistributedMemoryCache(); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Configure custom serializer settings for NodaTime support var serializerSettings = new JsonSerializerSettings(); serializerSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); builder.Services.AddIdempotentAPI(new IdempotencyOptions { SerializerSettings = serializerSettings, ExpiresInMilliseconds = TimeSpan.FromHours(24).TotalMilliseconds }); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` -------------------------------- ### FastEndpoints Integration with IdempotentAPI Source: https://context7.com/ikyriak/idempotentapi/llms.txt Integrate IdempotentAPI with FastEndpoints for a developer-friendly API development experience. Configure global idempotency options and apply the Idempotent attribute to endpoints. ```csharp // Program.cs using FastEndpoints; using IdempotentAPI.Core; using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDistributedMemoryCache(); builder.Services.AddIdempotentAPIUsingDistributedCache(); builder.Services.AddIdempotentAPI(new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(24).TotalMilliseconds }); builder.Services.AddFastEndpoints(); var app = builder.Build(); app.UseFastEndpoints(); app.Run(); // CreateOrderEndpoint.cs using FastEndpoints; using IdempotentAPI.Filters; public class CreateOrderRequest { public string ProductId { get; set; } public int Quantity { get; set; } } [Serializable] public class CreateOrderResponse { public Guid OrderId { get; set; } public string Status { get; set; } } [Idempotent(Enabled = true)] public class CreateOrderEndpoint : Endpoint { public override void Configure() { Post("/api/orders"); AllowAnonymous(); } public override async Task HandleAsync(CreateOrderRequest req, CancellationToken ct) { var response = new CreateOrderResponse { OrderId = Guid.NewGuid(), Status = "Created" }; await SendAsync(response, cancellation: ct); } } ``` -------------------------------- ### Register IdempotentAPI with Distributed Cache Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Register the IdempotentAPI.Cache.DistributedCache implementation. This requires registering an IDistributedCache service first, such as AddDistributedMemoryCache. ```csharp // Register an implementation of the IDistributedCache. // For this example, we are using a Memory Cache. services.AddDistributedMemoryCache(); // Register the IdempotentAPI.Cache.DistributedCache. services.AddIdempotentAPIUsingDistributedCache(); ``` -------------------------------- ### Configure Idempotency Options Provider in Minimal API Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Register a custom IdempotencyOptionsProvider to configure special types to be excluded from API actions in Minimal APIs. This helps resolve self-referencing loop issues. ```csharp // Program.cs // ... builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptionsProvider()); // ... ``` ```csharp // 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 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, }; } } } ``` -------------------------------- ### Register IdempotentAPI with Global Options Source: https://context7.com/ikyriak/idempotentapi/llms.txt Register IdempotentAPI with custom global options, such as expiration time, header key name, and cache prefix. These options can be reused across controllers. ```csharp using IdempotentAPI.Core; using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDistributedMemoryCache(); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Register with global IdempotencyOptions var idempotencyOptions = new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(48).TotalMilliseconds, HeaderKeyName = "X-Idempotency-Key", DistributedCacheKeysPrefix = "MyApp_", CacheOnlySuccessResponses = true, IsIdempotencyOptional = false }; builder.Services.AddIdempotentAPI(idempotencyOptions); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` -------------------------------- ### Configure IdempotentAPI with RedLock.net for Distributed Locking Source: https://context7.com/ikyriak/idempotentapi/llms.txt Configures IdempotentAPI to use RedLock.net for distributed locking with Redis. This is essential for cluster environments to ensure safe request processing across multiple API instances. ```csharp // Program.cs using System.Net; using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; using IdempotentAPI.DistributedAccessLock.RedLockNet.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Register Redis cache builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; }); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Register RedLock.net distributed lock provider List redisEndpoints = new() { new DnsEndPoint("localhost", 6379) }; builder.Services.AddRedLockNetDistributedAccessLock(redisEndpoints); builder.Services.AddIdempotentAPI(); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); // Controller with distributed lock timeout [ApiController] [Route("api/[controller]")] [Idempotent( Enabled = true, DistributedLockTimeoutMilli = 10000 // Wait up to 10 seconds to acquire lock )] public class DistributedController : ControllerBase { [HttpPost] public IActionResult Process([FromBody] object data) { return Ok(new { Result = "Processed safely in cluster" }); } } ``` -------------------------------- ### Integrate IdempotentAPI with ASP.NET Core Minimal APIs Source: https://context7.com/ikyriak/idempotentapi/llms.txt Add the IdempotentAPIEndpointFilter to Minimal API endpoints to make them idempotent. Configure options like expiration and header name. ```csharp // Program.cs using IdempotentAPI.Core; using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; using IdempotentAPI.MinimalAPI; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDistributedMemoryCache(); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Register Minimal API idempotency with default options builder.Services.AddIdempotentMinimalAPI(new IdempotencyOptions { ExpiresInMilliseconds = TimeSpan.FromHours(24).TotalMilliseconds, HeaderKeyName = "IdempotencyKey", CacheOnlySuccessResponses = true }); var app = builder.Build(); // Response DTO (must be Serializable) [Serializable] public record OrderResponse(Guid OrderId, string Status, DateTime CreatedAt); public record CreateOrderRequest(string ProductId, int Quantity); // Add IdempotentAPIEndpointFilter to make endpoint idempotent app.MapPost("/api/orders", (CreateOrderRequest request) => { var response = new OrderResponse( Guid.NewGuid(), "Created", DateTime.UtcNow ); return Results.Ok(response); }) .AddEndpointFilter(); // Endpoint returning object directly (not IResult) app.MapPost("/api/products", (object request) => { return new { ProductId = Guid.NewGuid(), Status = "Created" }; }) .AddEndpointFilter(); // Non-idempotent endpoint (no filter) app.MapGet("/api/orders/{id}", (Guid id) => { return Results.Ok(new { OrderId = id }); }); app.Run(); // curl -X POST http://localhost:5000/api/orders \ // -H "Content-Type: application/json" \ // -H "IdempotencyKey: order-550e8400-e29b-41d4-a716-446655440000" \ // -d '{"productId": "prod_123", "quantity": 2}' ``` -------------------------------- ### Configure IdempotentAPI Logging Level Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Configure the logging level for IdempotentAPI within your appsettings.json file. This allows you to control the verbosity of logs generated by the IdempotentAPI.Core.Idempotency module. ```json { "Logging": { "LogLevel": { "Default": "Information", "IdempotentAPI.Core.Idempotency": "Warning" } } } ``` -------------------------------- ### Add IdempotentAPI Using Statement Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Include this using statement in your Controller class to access the IdempotentAPI filters. ```csharp using IdempotentAPI.Filters; ``` -------------------------------- ### Implement Idempotent API Endpoint for Payments Source: https://context7.com/ikyriak/idempotentapi/llms.txt This C# code defines an API controller for processing payments using the `[Idempotent]` attribute. It ensures that each payment request, identified by an 'Idempotency-Key' header, is processed exactly once. Ensure the DTOs `PaymentRequest` and `PaymentResponse` are defined. ```csharp // PaymentsController.cs [ApiController] [Route("api/[controller]")] [Consumes("application/json")] [Produces("application/json")] public class PaymentsController : ControllerBase { private readonly ILogger _logger; public PaymentsController(ILogger logger) { _logger = logger; } [HttpPost] [Idempotent(UseIdempotencyOption = true)] public async Task ProcessPayment([FromBody] PaymentRequest request) { _logger.LogInformation("Processing payment for customer {CustomerId}", request.CustomerId); // Your payment processing logic here await Task.Delay(100); // Simulated processing return Ok(new PaymentResponse { TransactionId = Guid.NewGuid().ToString(), Amount = request.Amount, Status = "Completed", ProcessedAt = DateTime.UtcNow }); } } // DTOs public class PaymentRequest { public string CustomerId { get; set; } public decimal Amount { get; set; } public string Currency { get; set; } } [Serializable] public class PaymentResponse { public string TransactionId { get; set; } public decimal Amount { get; set; } public string Status { get; set; } public DateTime ProcessedAt { get; set; } } ``` -------------------------------- ### Apply Idempotent Attribute to Controller Class Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Decorate your entire Controller class with the `[Idempotent(Enabled = true)]` attribute to make all POST and PATCH actions idempotent by default. Ensure `Consumes` and `Produces` attributes are defined on the controller. ```csharp [ApiController] [Route("[controller]")] [Consumes("application/json")] // We should define this. [Produces("application/json")] // We should define this. [Idempotent(Enabled = true)] public class SimpleController : ControllerBase { // ... } ``` -------------------------------- ### Register IdempotentAPI Core Services Source: https://context7.com/ikyriak/idempotentapi/llms.txt Register the core IdempotentAPI services and a distributed cache implementation in your ASP.NET Core application's dependency injection container. This is a prerequisite for using the Idempotent attribute. ```csharp using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Step 1: Register an IDistributedCache implementation (Memory, Redis, SQL Server, etc.) builder.Services.AddDistributedMemoryCache(); // Step 2: Register the IdempotentAPI caching provider builder.Services.AddIdempotentAPIUsingDistributedCache(); // Step 3: Register the IdempotentAPI core services builder.Services.AddIdempotentAPI(); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` -------------------------------- ### Configure Madelson DistributedLock with Redis Source: https://context7.com/ikyriak/idempotentapi/llms.txt Register Madelson DistributedLock with Redis for distributed locking. Ensure StackExchange.Redis is configured. ```csharp // Program.cs using IdempotentAPI.Extensions.DependencyInjection; using IdempotentAPI.Cache.DistributedCache.Extensions.DependencyInjection; using IdempotentAPI.DistributedAccessLock.MadelsonDistributedLock.Extensions.DependencyInjection; using Medallion.Threading; using Medallion.Threading.Redis; using StackExchange.Redis; var builder = WebApplication.CreateBuilder(args); // Register Redis cache builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; }); builder.Services.AddIdempotentAPIUsingDistributedCache(); // Register Madelson DistributedLock with Redis var redisConnection = ConnectionMultiplexer.Connect("localhost:6379"); builder.Services.AddSingleton(_ => new RedisDistributedSynchronizationProvider(redisConnection.GetDatabase())); builder.Services.AddMadelsonDistributedAccessLock(); builder.Services.AddIdempotentAPI(); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); // Alternative: SQL Server distributed lock // using Medallion.Threading.SqlServer; // builder.Services.AddSingleton(_ => // new SqlDistributedSynchronizationProvider(connectionString)); ``` -------------------------------- ### Add IdempotentAPIEndpointFilter to Minimal API Source: https://github.com/ikyriak/idempotentapi/blob/master/CHANGELOG.md Use the IdempotentAPIEndpointFilter in Minimal APIs by adding it to your endpoint configuration. This enables idempotency for Minimal API endpoints. ```csharp app.MapPost("/example", ([FromQuery] string yourParam) => { return Results.Ok(new ResponseDTOs()); }) .AddEndpointFilter(); ``` -------------------------------- ### Decorate DTOs as Serializable Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Decorate response Data Transfer Objects (DTOs) with the [Serializable] attribute to enable caching. This is required before the DTOs can be serialized. ```csharp using System; namespace WebApi_3_1.DTOs { [Serializable] public class SimpleResponse { public int Id { get; set; } public string Message { get; set; } public DateTime CreatedOn { get; set; } } } ``` -------------------------------- ### Apply Idempotent Attribute to Controller Action Source: https://github.com/ikyriak/idempotentapi/blob/master/README.md Apply the `[Idempotent]` attribute to individual HTTP POST or PATCH actions to make only specific actions idempotent. This allows for per-action configuration, such as setting `ExpireHours`. ```csharp [HttpPost] [Idempotent(ExpireHours = 48)] public IActionResult Post([FromBody] SimpleRequest simpleRequest) { // ... } ``` -------------------------------- ### Idempotent Attribute on Individual Actions Source: https://context7.com/ikyriak/idempotentapi/llms.txt Apply the Idempotent attribute to specific action methods to configure idempotency per endpoint. Options include cache expiration and custom header names. ```csharp using IdempotentAPI.Filters; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/[controller]")] [Consumes("application/json")] [Produces("application/json")] public class OrdersController : ControllerBase { // Idempotent with 48-hour cache expiration [HttpPost] [Idempotent(ExpiresInMilliseconds = 172800000)] // 48 hours in milliseconds public IActionResult CreateOrder([FromBody] OrderRequest request) { return Ok(new { OrderId = Guid.NewGuid(), Status = "Created" }); } // Idempotent with custom header name [HttpPost("bulk")] [Idempotent(HeaderKeyName = "X-Request-Id", ExpiresInMilliseconds = 3600000)] public IActionResult CreateBulkOrders([FromBody] List requests) { return Ok(new { BatchId = Guid.NewGuid(), Count = requests.Count }); } // Use globally registered IdempotencyOptions [HttpPatch("{id}")] [Idempotent(UseIdempotencyOption = true)] public IActionResult UpdateOrder(int id, [FromBody] OrderRequest request) { return Ok(new { OrderId = id, Status = "Updated" }); } // Non-idempotent endpoint (no attribute) [HttpGet("{id}")] public IActionResult GetOrder(int id) { return Ok(new { OrderId = id }); } } public class OrderRequest { public string ProductId { get; set; } public int Quantity { get; set; } } ``` -------------------------------- ### Idempotent Attribute on Controller Class Source: https://context7.com/ikyriak/idempotentapi/llms.txt Applying the Idempotent attribute at the controller level makes all POST and PATCH methods idempotent. Clients must include an IdempotencyKey header with a unique value. ```APIDOC ## POST /api/payments ### Description Processes a payment request. This endpoint is idempotent when the `Idempotent` attribute is applied at the controller level. ### Method POST ### Endpoint /api/payments ### Parameters #### Headers - **IdempotencyKey** (string) - Required - A unique value (typically a UUID) to ensure idempotency. #### Request Body - **request** (PaymentRequest) - Required - The payment details. - **customerId** (string) - Required - The ID of the customer. - **amount** (decimal) - Required - The payment amount. ### Request Example ```json { "customerId": "cust_123", "amount": 99.99 } ``` ### Response #### Success Response (200) - **transactionId** (int) - The unique ID of the transaction. - **amount** (decimal) - The processed payment amount. - **status** (string) - The status of the payment (e.g., "Completed"). - **processedAt** (DateTime) - The timestamp when the payment was processed. #### Response Example ```json { "transactionId": 45678, "amount": 99.99, "status": "Completed", "processedAt": "2024-01-15T10:30:00Z" } ``` ## PATCH /api/payments/{id} ### Description Updates an existing payment. This endpoint is idempotent when the `Idempotent` attribute is applied at the controller level. ### Method PATCH ### Endpoint /api/payments/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The ID of the payment to update. #### Headers - **IdempotencyKey** (string) - Required - A unique value (typically a UUID) to ensure idempotency. #### Request Body - **request** (PaymentRequest) - Required - The updated payment details. - **customerId** (string) - Required - The ID of the customer. - **amount** (decimal) - Required - The payment amount. ### Response #### Success Response (200) - **transactionId** (int) - The ID of the updated transaction. - **amount** (decimal) - The updated payment amount. - **status** (string) - The status of the payment (e.g., "Updated"). - **processedAt** (DateTime) - The timestamp when the payment was updated. #### Response Example ```json { "transactionId": 12345, "amount": 100.00, "status": "Updated", "processedAt": "2024-01-15T11:00:00Z" } ``` ``` -------------------------------- ### Idempotent Attribute on Individual Actions Source: https://context7.com/ikyriak/idempotentapi/llms.txt Applying the Idempotent attribute on specific action methods allows for custom idempotency options per endpoint, such as cache expiration time and custom header names. ```APIDOC ## POST /api/orders ### Description Creates a new order. This endpoint is idempotent with a cache expiration of 48 hours. ### Method POST ### Endpoint /api/orders ### Parameters #### Headers - **IdempotencyKey** (string) - Required - A unique value (typically a UUID) to ensure idempotency. #### Request Body - **request** (OrderRequest) - Required - The order details. - **productId** (string) - Required - The ID of the product. - **quantity** (int) - Required - The quantity of the product. ### Response #### Success Response (200) - **orderId** (string) - The unique ID of the created order. - **status** (string) - The status of the order (e.g., "Created"). #### Response Example ```json { "orderId": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "status": "Created" } ``` ## POST /api/orders/bulk ### Description Creates multiple orders in a single request. This endpoint is idempotent with a cache expiration of 1 hour and uses a custom header for the idempotency key. ### Method POST ### Endpoint /api/orders/bulk ### Parameters #### Headers - **X-Request-Id** (string) - Required - A unique value to ensure idempotency. #### Request Body - **requests** (List) - Required - A list of order details. - **productId** (string) - Required - The ID of the product. - **quantity** (int) - Required - The quantity of the product. ### Response #### Success Response (200) - **batchId** (string) - The unique ID of the batch operation. - **count** (int) - The number of orders processed. #### Response Example ```json { "batchId": "f0e9d8c7-b6a5-4321-fedc-ba9876543210", "count": 5 } ``` ## PATCH /api/orders/{id} ### Description Updates an existing order. This endpoint is idempotent and uses globally registered idempotency options. ### Method PATCH ### Endpoint /api/orders/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The ID of the order to update. #### Headers - **IdempotencyKey** (string) - Required - A unique value (typically a UUID) to ensure idempotency. #### Request Body - **request** (OrderRequest) - Required - The updated order details. - **productId** (string) - Required - The ID of the product. - **quantity** (int) - Required - The quantity of the product. ### Response #### Success Response (200) - **orderId** (int) - The ID of the updated order. - **status** (string) - The status of the order (e.g., "Updated"). #### Response Example ```json { "orderId": 123, "status": "Updated" } ``` ## GET /api/orders/{id} ### Description Retrieves an order by its ID. This endpoint is not idempotent. ### Method GET ### Endpoint /api/orders/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The ID of the order to retrieve. ### Response #### Success Response (200) - **orderId** (int) - The ID of the order. #### Response Example ```json { "orderId": 123 } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.