Rate limiting middleware in ASP.NET Core

Tohid haghighi
4 min readSep 11, 2023

Rate Limiting is the process of restricting the number of requests for a resource within a specific time window.

Rate limit

In summary, rate limiting is a method of controlling traffic flow to a service or server by restricting the number of requests that can be made within a certain time frame. It is an essential technique for preventing resource abuse, ensuring fair use of services, and protecting against DDoS attacks.

A service provider offering an API for consumers will have limitations on requests made within a specified window of time. For instance, each unique user/IP Address/Client key will have a limitation on the number of requests to an API endpoint.

Rate limit

Why We Use Rate Limiting?

Public APIs use rate-limiting for commercial purposes to generate revenue. A common business model is to pay a certain subscription amount for leveraging the API. So, they can only make so many API calls before paying more for an upgraded plan.

Rate Limiting helps protect against malicious bot attacks. For example, a hacker can use bots to make repeated calls to an API endpoint. Hence, rendering the service unavailable to anyone else. This is known as the Denial of Service (DoS) attack.

Another purpose of rate limiting is to regulate traffic to the API according to infrastructure availability. Such usage is more relevant to the cloud-based API services that utilize a “pay as you go” IaaS strategy with cloud providers.

Configuring Rate Limiting

ASP.NET Core 7 introduced built-in rate limiting middleware in the Microsoft.AspNetCore.RateLimiting namespace.

To add rate limiting to your application, you first need to register the rate limiting services:

builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

// We'll talk about adding specific rate limiting policies later.
});

I suggest updating the RejectionStatusCode to 429 (Too Many Requests) because it's more correct. The default value is 503 (Service Unavailable).

And you also have to apply the RateLimitingMiddleware:

app.UseRateLimiter();

That’s everything you’ll need.

Let’s see the rate limiting algorithms we can use.

Rate limiter algorithms

The RateLimiterOptionsExtensions class provides the following extension methods for rate limiting:

Fixed Window Limiter

The AddFixedWindowLimiter method configures a fixed window limiter.

The Window value determines the time window.

When a time window expires, a new one starts, and the request limit is reset.

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.AddFixedWindowLimiter("fixed", options =>
{
options.PermitLimit = 10;
options.Window = TimeSpan.FromSeconds(10);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 5;
});
});

Sliding Window Limiter

The sliding window algorithm is similar to the fixed window, but it introduces segments in a window.

Here’s how it works:

  • Each time window is divided into multiple segments
  • The window slides one segment each segment interval
  • The segment interval is (window_time)/(segments_per_window)
  • When a segment expires, the requests taken in that segment are added to the current segment

The AddSlidingWindowLimiter method configures a sliding window limiter.

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.AddSlidingWindowLimiter("sliding", options =>
{
options.PermitLimit = 10;
options.Window = TimeSpan.FromSeconds(10);
options.SegmentsPerWindow = 2;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 5;
});
});

Token Bucket Limiter

The token bucket algorithm is similar to the sliding window, but instead of adding back the requests from the expired segment, a fixed number of tokens are added after each replenishment period.

The total number of tokens can never exceed the token limit.

The AddTokenBucketLimiter method configures a token bucket limiter.

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.AddTokenBucketLimiter("token", options =>
{
options.TokenLimit = 100;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 5;
options.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
options.TokensPerPeriod = 20;
options.AutoReplenishment = true;
});
});

When AutoReplenishment is true, an internal timer will execute every ReplenishmentPeriod and replenish the tokens.

Concurrency Limiter

The concurrency limiter is the most straightforward algorithm, and it just limits the number of concurrent requests.

The AddConcurrencyLimiter method configures a concurrency limiter.

builder.Services.AddRateLimiter(rateLimiterOptions =>
{
rateLimiterOptions.AddConcurrencyLimiter("concurrency", options =>
{
options.PermitLimit = 10;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 5;
});
});

There’s no time component involved in this case. The only parameter is the number of concurrent requests.

Using Rate Limiting In Your API

Now that we have configured our rate limiting policies, let’s see how we can use them in our API.

There are slight differences between controllers and minimal API endpoints, so I’ll cover them in separate examples.

Controllers

To add rate limiting on a controller we use the EnableRateLimiting and DisableRateLimiting attributes.

EnableRateLimiting can be applied on the controller or on the individual endpoints.

[EnableRateLimiting("fixed")]
public class TransactionsController
{
private readonly ISender _sender;

public TransactionsController(ISender sender)
{
_sender = sender;
}

[EnableRateLimiting("sliding")]
public async Task<IActionResult> GetTransactions()
{
return Ok(await _sender.Send(new GetTransactionsQuery()));
}

[DisableRateLimiting]
public async Task<IActionResult> GetTransactionById(int id)
{
return Ok(await _sender.Send(new GetTransactionByIdQuery(id)));
}
}

In the previous example:

  • All endpoints in the TransactionsController will use a fixed window policy
  • The GetTransactions endpoint will use a sliding window policy
  • The GetTransactionById endpoint won't have any rate limiting applied

Minimal APIs

In a Minimal API endpoint, you can configure the rate limit policy by calling RequireRateLimiting and specifying the policy name.

We’re using the token bucket policy in this example.

app.MapGet("/transactions", async (ISender sender) =>
{
return Results.Ok(await sender.Send(new GetTransactionsQuery()));
})
.RequireRateLimiting("token");

--

--

Tohid haghighi

Full-Stack Developer | C# | .NET Core | Vuejs | TDD | Javascript