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" />
<PackageReference Include="Zaiets.RateLimiter" />
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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#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
#tool nuget:?package=Zaiets.RateLimiter&version=1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
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 | Versions 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.