Zaiets.RateLimiter 1.0.0

dotnet add package Zaiets.RateLimiter --version 1.0.0
                    
NuGet\Install-Package Zaiets.RateLimiter -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Zaiets.RateLimiter" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Zaiets.RateLimiter" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Zaiets.RateLimiter" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Zaiets.RateLimiter --version 1.0.0
                    
#r "nuget: Zaiets.RateLimiter, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Zaiets.RateLimiter@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Zaiets.RateLimiter&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Zaiets.RateLimiter&version=1.0.0
                    
Install as a Cake Tool

Zaiets.RateLimiter

Token bucket, sliding window, and fixed window rate limiter for .NET 10.
Per-client, distributed (IDistributedCache), and ASP.NET Core middleware — all in one package.

Author: Vladyslav Zaiets — sarmkadan.com


Installation

dotnet add package Zaiets.RateLimiter

Algorithms at a glance

Algorithm Burst-friendly Memory Best for
Token Bucket Yes O(clients) APIs that allow short bursts
Sliding Window No O(clients × rate) Smooth, strict request rates
Fixed Window Slight (window boundary) O(clients) Simple per-minute / per-hour caps
Distributed No (fixed window) Cache-backed Multi-node / microservices

Quick start — standalone (no DI)

using Zaiets.RateLimiter;

// Token bucket: 10 requests/second, burst up to 50
var limiter = RateLimiterFactory.CreateTokenBucket(capacity: 50, refillRatePerSecond: 10);

var result = await limiter.AcquireAsync("user-42");
if (result.IsAllowed)
{
    Console.WriteLine($"Allowed — {result.Remaining} remaining");
}
else
{
    Console.WriteLine($"Denied — retry after {result.RetryAfter.TotalSeconds:F1}s");
}

ASP.NET Core middleware

1. Register + configure

// Program.cs
using Zaiets.RateLimiter;

var builder = WebApplication.CreateBuilder(args);

// Choose one algorithm:
builder.Services.AddTokenBucketRateLimiter(opts =>
{
    opts.Capacity = 100;
    opts.RefillRate = 20; // tokens per second
});

// builder.Services.AddSlidingWindowRateLimiter(opts =>
// {
//     opts.PermitLimit = 60;
//     opts.Window = TimeSpan.FromMinutes(1);
// });

// builder.Services.AddFixedWindowRateLimiter(opts =>
// {
//     opts.PermitLimit = 1000;
//     opts.Window = TimeSpan.FromHours(1);
// });

var app = builder.Build();

app.UseZaietsRateLimiter(opts =>
{
    // Extract client ID from API key header, fall back to IP
    opts.ClientIdExtractor = ctx =>
        ctx.Request.Headers.TryGetValue("X-Api-Key", out var key)
            ? key.ToString()
            : ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown";

    // Optional: customise the 429 response
    opts.OnRejected = async (ctx, result) =>
    {
        ctx.Response.StatusCode = 429;
        await ctx.Response.WriteAsJsonAsync(new
        {
            error = "Rate limit exceeded",
            retryAfterSeconds = (int)result.RetryAfter.TotalSeconds
        });
    };
});

app.MapGet("/api/data", () => "Hello!");
app.Run();

2. Response headers emitted automatically

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1717200060
Retry-After: 42          ← only on 429

Distributed rate limiting (multi-node)

Pair with any IDistributedCache provider — Redis is recommended for production.

// Redis
builder.Services.AddStackExchangeRedisCache(opts =>
{
    opts.Configuration = "localhost:6379";
});

// In-memory (single-process testing)
// builder.Services.AddDistributedMemoryCache();

builder.Services.AddDistributedRateLimiter(opts =>
{
    opts.PermitLimit = 500;
    opts.Window = TimeSpan.FromMinutes(1);
    opts.KeyPrefix = "myapp:rl";
});

Per-client token bucket — direct usage

var limiter = new TokenBucketRateLimiter(new TokenBucketOptions
{
    Capacity = 20,
    RefillRate = 5, // 5 tokens/sec → 1 request per 200 ms sustained
    StartFull = true
});

// Per-user throttling
foreach (var userId in new[] { "alice", "bob", "alice" })
{
    var r = await limiter.AcquireAsync(userId);
    Console.WriteLine($"{userId}: {(r.IsAllowed ? "OK" : "DENIED")} — {r.Remaining}/{r.Limit}");
}

Sliding window — direct usage

var limiter = new SlidingWindowRateLimiter(new SlidingWindowOptions
{
    PermitLimit = 10,
    Window = TimeSpan.FromSeconds(30)
});

var result = await limiter.AcquireAsync("user-99");
// result.ResetAt → UTC time when the oldest request in the window falls off

Fixed window — direct usage

var limiter = new FixedWindowRateLimiter(new FixedWindowOptions
{
    PermitLimit = 1000,
    Window = TimeSpan.FromHours(1),
    AlignToCalendar = true  // resets at the top of each hour
});

Checking without consuming a permit

bool wouldBeAllowed = await limiter.IsAllowedAsync("client-id");

Resetting a client

await limiter.ResetAsync("client-id"); // clears all state for that client

API reference

IRateLimiter

Method Description
AcquireAsync(clientId, ct) Checks the limit and consumes one permit if allowed.
IsAllowedAsync(clientId, ct) Checks without consuming a permit (read-only).
ResetAsync(clientId, ct) Removes all rate limit state for the given client.

RateLimiterResult

Property Type Description
IsAllowed bool Whether this request may proceed.
Remaining int Permits left in the current window/bucket.
Limit int Total permit budget.
RetryAfter TimeSpan How long to wait before retrying (zero when allowed).
ResetAt DateTimeOffset UTC time the window/bucket resets.
Reason string? Human-readable denial reason.
Algorithm RateLimiterAlgorithm Algorithm that produced the result.

RateLimiterFactory (static)

RateLimiterFactory.CreateTokenBucket(capacity, refillRatePerSecond)
RateLimiterFactory.CreateTokenBucket(configure)
RateLimiterFactory.CreateSlidingWindow(permitLimit, window)
RateLimiterFactory.CreateSlidingWindow(configure)
RateLimiterFactory.CreateFixedWindow(permitLimit, window)
RateLimiterFactory.CreateFixedWindow(configure)
RateLimiterFactory.CreateDistributed(cache, configure)

License

MIT — see LICENSE for details.

Copyright (c) 2025 Vladyslav Zaiets

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 93 5/3/2026

Initial release: token bucket, sliding window, fixed window, distributed (IDistributedCache), ASP.NET Core middleware.