ASP.NET Core  

Rate Limiting and Throttling in ASP.NET Core Web API

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

  • Limit: 100 requests per minute per client.

  • If exceeded, the API returns HTTP 429 Too Many Requests.

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

  1. Apply limits per identity, not globally (use API key, user, or IP).

  2. Different plans for different clients (free vs. premium tiers).

  3. Monitor & log 429 responses to detect abuse.

  4. Return Retry-After headers so clients can back off.

  5. Combine with API Gateway (e.g., YARP, NGINX, Azure API Management) for distributed systems.

  6. 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.