APIs are often exposed to thousands of requests per second. Without proper safeguards, excessive traffic—whether malicious (DoS/DDoS) or unintentional (clients flooding with requests)—can degrade performance, increase costs, or bring down your system.
Rate limiting and throttling are crucial techniques to control API usage, ensure fair resource distribution, and protect backend services.
In this article, we’ll cover how to implement rate limiting in ASP.NET Core Web APIs using built-in middleware (from .NET 7+), custom solutions, and best practices.
What is rate limiting?
Rate limiting defines how many requests a client (IP, user, or token) can make in a given time window.
For example
What is throttling?
Throttling goes beyond rate limiting. Instead of outright rejecting requests, it can:
Queue or delay requests until capacity is available.
Reduce service quality for heavy users while maintaining fairness.
Apply dynamic limits based on client type (free vs. premium).
Benefits of Rate Limiting & Throttling
Prevent abuse (scraping, brute-force attacks).
Protect backend systems from overload.
Ensure fair use among consumers.
Support tiered plans (e.g., free users: 50 requests/min, premium: 500 requests/min).
Implementing Rate Limiting in ASP.NET Core
1. Using .NET 7+ Built-in Rate Limiting Middleware
ASP.NET Core 7 introduced a dedicated rate-limiting middleware.
Install Package
If you’re on .NET 7+:
dotnet add package Microsoft.AspNetCore.RateLimiting
Configure in Program.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRateLimiter(options =>
{
// Limit each client to 100 requests per minute
options.AddFixedWindowLimiter("Fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2; // Max queued requests
});
});
var app = builder.Build();
app.UseRateLimiter();
app.MapGet("/api/data", () => "This is a rate-limited endpoint")
.RequireRateLimiting("Fixed");
app.Run();
2. Token Bucket Limiter Example
This allows burst traffic while enforcing overall limits.
options.AddTokenBucketLimiter("TokenBucket", opt =>
{
opt.TokenLimit = 50; // Max tokens
opt.QueueLimit = 2;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 10; // Refill rate
opt.AutoReplenishment = true;
});
3. Sliding Window Limiter Example
Smooths out spikes by distributing requests across sliding intervals.
options.AddSlidingWindowLimiter("SlidingWindow", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 6; // 10 sec segments
opt.QueueLimit = 2;
});
4. Per-User or Per-API Key Limiting
You can bind rate limits to users, API keys, or IPs.
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userId = httpContext.User?.Identity?.Name ?? "anonymous";
return RateLimitPartition.GetFixedWindowLimiter(userId, _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 10,
Window = TimeSpan.FromSeconds(30)
});
});
});
Handling Exceeded Limits
When a client exceeds the allowed requests, ASP.NET Core automatically returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
You can customize the response:
options.OnRejected = (context, token) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
return context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.");
};
Implementing Throttling (Delaying Requests)
Instead of rejecting requests, you can queue and delay them.
options.AddFixedWindowLimiter("Throttle", opt =>
{
opt.PermitLimit = 5;
opt.Window = TimeSpan.FromSeconds(10);
opt.QueueLimit = 5;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
Here, extra requests wait in a queue instead of failing immediately.
Best Practices for Rate Limiting
Apply limits per identity, not globally (use API key, user, or IP).
Different plans for different clients (free vs. premium tiers).
Monitor & log 429 responses to detect abuse.
Return Retry-After
headers so clients can back off.
Combine with API Gateway (e.g., YARP, NGINX, Azure API Management) for distributed systems.
Always test under load scenarios to fine-tune values.
Example: Full ASP.NET Core Web API with Rate Limiting
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("ApiLimiter", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueLimit = 0;
});
options.OnRejected = async (context, _) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsync("Too many requests. Slow down!");
};
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseRateLimiter();
app.MapControllers().RequireRateLimiting("ApiLimiter");
app.Run();
Conclusion
Rate limiting and throttling are essential to keep your ASP.NET Core Web API secure, stable, and fair.
With .NET 7’s built-in middleware, you can easily configure fixed, sliding, or token bucket rate limits. For advanced needs, combine them with per-user throttling, API gateways, and monitoring to maintain performance and reliability.