Setting Up Rate Limiting in ASP.NET Core: A Step-by-Step Guide for Developers
The rate limiting feature in ASP.NET Core lets you control server load, protect against DoS attacks, and ensure fair resource distribution. Setup requires two steps: registering services and adding middleware to the pipeline. All dependencies are included in the SDK—no extra packages needed.
In Program.cs, call AddRateLimiter on WebApplicationBuilder.Services:
builder.Services.AddRateLimiter(options =>
{
// Konfiguratsiya politik
});
Then add the middleware:
app.UseRateLimiter();
This is the basic integration. Next up: details on algorithms and policies.
Algorithms and Selective Limiting
ASP.NET Core supports several rate limiting algorithms:
- Fixed Window — fixed time window, simple but prone to load spikes.
- Sliding Window — sliding window, distributes limits more evenly.
- Token Bucket — token bucket, allows burst traffic.
- Concurrency Limiter — limits concurrent requests.
Selectivity determines which requests to limit. The global limiter applies to all requests, while selective ones use keys (IP, user ID).
Example of a global limiter (100 requests per minute):
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
httpContext => RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Request.Headers.Host.ToString(),
factory: partition => new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
);
Grouping and Combining Limiters
Grouping by key is the foundation of selectivity. Use AddFixedWindowLimiter or custom policies.
Key grouping scenarios:
- By IP:
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() - By client:
partitionKey: httpContext.User.Identity?.Name - By API endpoint:
partitionKey: httpContext.Request.Path - Combined key: IP + User-Agent
Combining selective limiters via AddPolicy:
options.AddPolicy("ip", httpContext =>
{
var ip = httpContext.Connection.RemoteIpAddress;
return new FixedWindowRateLimiterOptions
{
PermitLimit = 50,
Window = TimeSpan.FromMinutes(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 10
};
});
New feature: combining non-selective limiters—multiple global limiters with queues.
Queues and Rejection Handling
Enable queues to smooth out spikes:
QueueLimit— maximum in queue.QueueProcessingOrder— order (OldestFirst / NewestFirst).
Rejection actions can be set globally or per policy:
options.OnRejected = (context, token) =>
{
context.HttpContext.Response.StatusCode = 429;
context.HttpContext.Response.ContentType = "application/json";
return context.HttpContext.Response.WriteAsync(
"{"too many requests. try again later.", token);
};
Named and Anonymous Policies
Named policies are applied via attributes to controllers/actions:
[EnableRateLimiting("policy1")]
public class ApiController : ControllerBase { }
Simple registration:
options.AddPolicy("policy1", httpContext =>
new TokenBucketRateLimiterOptions
{
TokenLimit = 100,
QueueLimit = 20,
ReplenishmentPeriod = TimeSpan.FromMinutes(1),
TokensPerPeriod = 100,
AutoReplenishment = true
});
Anonymous policies — a bonus not covered in MS documentation. Implemented via IRateLimiter directly in selectors.
Example:
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext>(
context => new CustomRateLimiter(context));
public class CustomRateLimiter : FixedWindowRateLimiter
{
// Kastomnaya logic
}
Linking to routes: [EnableRateLimiting("api")] or disable with [DisableRateLimiting].
Key Points
- Rate limiting operates at the middleware level, before controllers.
- Always configure
OnRejectedfor 429 Too Many Requests. - Use queues for high-load scenarios.
- Combine policies for granular control (IP + endpoint).
- Anonymous policies extend capabilities beyond the standard API.
— Editorial Team
No comments yet.