• Blog
  • How to Implement Rate Limiting in ASP.NET Core

How to Implement Rate Limiting in ASP.NET Core

Find practical recommendations for working with rate limiting algorithm

Publish date:
Discover more of what matters to you

Rate limiting is a technique that limits the number of requests in a system, usually within a specific time frame. It can be used with both APIs and websites. Why would you need that?

There are different goals that you can achieve with it:

  1. Reduction of the unnecessary server load. When the server is not overloaded, you can ensure better performance and reliability.
  2. Usage limits. If clients are charged per request, rate limiting can help you to make sure that they are using only the resources they have paid for.
  3. Protection against DDoS attacks. This technique can successfully block excessive traffic attempts that drive server corruption.
  4. Prevention of unnecessary resource usage. This can guarantee performance and cost optimization.

Rate Limiting Algorithms

Here are 4 common types of rate limiting algorithms that can be used to control the flow of requests.

  • Fixed window limiter. This algorithm presupposes that there is a fixed time window to limit requests. For example, we don’t allow more than 10 requests per minute. After that period, the timer resets and a new time begins.
  • Concurrency limiter. This algorithm is easier to use than the previous one. The time parameter is not involved. We can just limit the number of concurrent requests.
  • Sliding window limiter. Instead of having a static long time window, we can divide it into multiple time segments. Unlike fixed window rate limiting, the sliding window approach offers smoother handling of requests near the boundary of a time window. When a segment expires, the requests from the previous segment carry over to the current one.
  • Token bucket limiter. It is similar to the sliding window limiter. But instead of adding back the requests taken from the expired segment, a fixed number of them are added after each specified period, mentioned as the replenishment period.

Stock Market API

Let’s take a look at a simple stock market API scenario to analyze how rate limiting works. In the beginning, we have a .NET Core 8 API application that has a single endpoint providing stock market data.

Fixed Window Limiter

Let’s imagine that we have launched a new app and need to attract the attention of new clients to it. That’s why the API will be publicly available to everyone. However, we don’t want to exceed our budget limits. That’s why we will implement the fixed window limiter algorithm to restrict access by IP Address.

Starting from .NET 7 and over, there is a built-in rate limiting middleware in the Microsoft.AspNetCore.RateLimiting namespace.

Still Using .NET 6 Apps?
Consider migrating to a newer .NET version to address performance and security challenges. Learn more about the benefits.
Read the article

The implementation of a rate limiting policy will be quite simple. This process will involve the registration of rate limiting services and the enabling of the middleware.

We will use RateLimitPartition to apply the policy per user IP address rather than globally.

1234567891011
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("fixed-by-ip", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100, // Limit to 100 requests
Window = TimeSpan.FromHours(1), // Per 1-hour window
}));
});

The RateLimitPartition will ensure that rate limits are applied individually to each client based on their IP address. The FixedWindowRateLimiterOptions will allow us to configure the request limit and window duration.

After registering the rate limiter service, we will enable the rate limiting middleware. Remember that the order of the middleware registration matters, so call it before your minimal API endpoints or controllers.

1
app.UseRateLimiter();

All API consumers have no more than 100 requests per hour. But how can we apply it in a minimal API endpoint?

It is possible to do it by calling RequireRateLimiting with the fixed-by-ip policy name:

123456789101112
app.MapGet("/api/data", () =>
{
var stockMarketData = new List<object>
{
new { Symbol = "AAPL", Price = 175.64, Change = +1.23 },
new { Symbol = "GOOGL", Price = 2843.66, Change = -15.44 },
new { Symbol = "AMZN", Price = 3491.15, Change = +23.89 },
new { Symbol = "TSLA", Price = 753.42, Change = -5.76 }
};
return Results.Json(stockMarketData);
}).RequireRateLimiting("fixed-by-ip");

After testing the hourly limit, the user will receive an HTTP 503 (Service Unavailable) error, which doesn’t clearly communicate the case. To improve the user experience, we will modify the rejection status code to 429 (Too Many Requests).

123456789101112
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("fixed-by-ip", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: _ => new SlidingWindowRateLimiterOptions
{
PermitLimit = 100, // Limit to 100 requests
Window = TimeSpan.FromHours(1), // Per 1-hour window
}));
});

Sliding Window Limiter

With the previous implementation, users can run out of all available requests at once and be blocked until the window resets. To offer them an enhanced user experience, we will use a sliding window limiter that distributes requests more evenly across the time window.

12345678910111213141516
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("sliding-by-ip", httpContext =>
RateLimitPartition.GetSlidingWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: _ => new SlidingWindowRateLimiterOptions
{
PermitLimit = 100, // Limit to 100 requests
Window = TimeSpan.FromHours(1), // 1-hour window
SegmentsPerWindow = 6, // 6 segments (10 minutes each)
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 20 // Allow up to 20 queued requests
}));
});

The 1-hour window will be divided into 6 segments of 10 minutes each. If users make many requests in one interval, they will not immediately regain full access after the window resets.

Instead, requests will be gradually provided as time moves forward.

With QueueProcessingOrder and QueueLimit, requests will be queued if the limit exceeds. They will be processed in a First-In-First-Out (FIFO) manner, with 20 additional requests to be queued after the limit is hit.

Let’s apply the rate limiting policy to our endpoint:

1234567891011
app.MapGet("/api/data", () =>
{
var stockMarketData = new List<object>
{
new { Symbol = "AAPL", Price = 175.64, Change = +1.23 },
new { Symbol = "GOOGL", Price = 2843.66, Change = -15.44 },
new { Symbol = "AMZN", Price = 3491.15, Change = +23.89 },
new { Symbol = "TSLA", Price = 753.42, Change = -5.76 }
};
return Results.Json(stockMarketData);
}).RequireRateLimiting("sliding-by-ip");

Token Bucket Limiter

As the traffic grows, we want to give some further flexibility to users. Instead of just adding the requests for the expired segment, the Token Bucket algorithm adds a fixed number of tokens each replenishment period.

1234567891011121314151617
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("token-bucket-by-ip", httpContext =>
RateLimitPartition.GetTokenBucketLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(),
factory: _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 20, // Maximum of 20 requests at a time
ReplenishmentPeriod = TimeSpan.FromMinutes(10), // Replenish requests every 10 minutes
TokensPerPeriod = 20, // Add 20 requests every 10 minutes
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5, // Allow up to 5 queued requests,
AutoReplenishment = true // Automatically add requests at the replenishment period
}));
});

Every 10 minutes, 20 requests are replenished. This means users can burst up to 20 requests after waiting for the replenishment period to pass, with QueueLimit of 5.

Public and Premium Users

The demand for the product increases over time. Sooner or later it will be high time to monetize it. We can create a login system to identify users and assign a role to each of them based on the chosen plan. Public users will have a limit of 20 requests per 10 minutes, while premium users will have 40 requests per 10 minutes. To do this, we will use the token bucket algorithm.

Let’s differentiate roles and give more requests to premium users:

1234567891011121314151617181920212223242526272829303132333435363738
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("token-bucket-by-role", httpContext =>
{
var role = httpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value ?? "Publi
c";
if (role == "Premium")
{
return RateLimitPartition.GetTokenBucketLimiter(
partitionKey: role,
factory: _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 40, // Premium users get 40 requests per 10 minutes
ReplenishmentPeriod = TimeSpan.FromMinutes(10),
TokensPerPeriod = 40,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 10,
AutoReplenishment = true
});
}
else
{
return RateLimitPartition.GetTokenBucketLimiter(
partitionKey: role,
factory: _ => new TokenBucketRateLimiterOptions
{
TokenLimit = 20, // Public users get 20 requests per 10 minutes
ReplenishmentPeriod = TimeSpan.FromMinutes(10),
TokensPerPeriod = 20,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5,
AutoReplenishment = true
});
}
});
});

The partition key is assigned by role now, which is extracted from the claims using ClaimTypes.Role. If the role is missing or invalid, it defaults to Public.

Let’s change the policy one more time:

12345678910111213
app.MapGet("/api/data", () =>
{
var stockMarketData = new List<object>
{
new { Symbol = "AAPL", Price = 175.64, Change = +1.23 },
new { Symbol = "GOOGL", Price = 2843.66, Change = -15.44 },
new { Symbol = "AMZN", Price = 3491.15, Change = +23.89 },
new { Symbol = "TSLA", Price = 753.42, Change = -5.76 }
};
return Results.Json(stockMarketData);
}).RequireRateLimiting("token-bucket-by-role");

Conclusion

With a well-structured plan, it is quite straightforward to implement rate limiting in your NET API. In our example with the Stock Market API, we covered 3 algorithms using the built-in middleware. This way, you can not only protect your product from excessive load but also create specific use cases for monetization.

If you need any help in working with your ASP.NET Core projects, do not hesitate to contact us. At Softacom, we have solid expertise in using this technology for building software products of different types and complexity. Schedule a free consultation with our specialists to learn more about our experience and services.

Subscribe to our newsletter and get amazing content right in your inbox.

This field is required
This field is required Invalid email address
By submitting data, I agree to the Privacy Policy

Thank you for subscribing!
See you soon... in your inbox!

confirm your subscription, make sure to check your promotions/spam folder

Get in touch
Our benefits
  • 17+ years of expertise in legacy software modernization
  • AI Migration Tool:
    faster timelines, lower costs, better accuracy (99.9%)
  • Accelerate release cycles by 30–50% compared to manual migration
  • 1–2 business day turnaround for detailed estimates
  • Trusted by clients across the USA, UK, Germany, and other European countries
Review
Thanks to Softacom's efforts, the solutions they delivered are already in use and have increased revenue streams.
  • Niels Thomassen
  • Microcom A/S
This field is required
This field is required Invalid email address Invalid business email address
This field is required
By submitting data, I agree to the Privacy Policy
We’ll reply within 24 hours — no sales talk, no spam