ASP.NET Core  

Building High-Performance Rate Limiting in ASP.NET Core 11

Modern web APIs must handle unpredictable traffic patterns. Whether caused by legitimate traffic spikes, aggressive clients, or malicious attacks, excessive requests can overwhelm an application, degrade performance, and impact other users.

Rate limiting is a critical technique for protecting APIs by controlling how many requests a client can make within a given period. ASP.NET Core includes built-in rate limiting middleware that provides flexible and high-performance request throttling without requiring third-party libraries.

In this article, you'll learn how to implement production-ready rate limiting in ASP.NET Core 11, explore different rate limiting algorithms, and understand how to evaluate their effectiveness using a structured testing methodology.

Note: This article focuses on implementation and testing methodology. It does not include fabricated benchmark results.

Why Rate Limiting Matters

Without rate limiting, applications may experience:

  • API abuse

  • Denial-of-service attempts

  • Excessive resource consumption

  • Database overload

  • Unfair resource usage

  • Increased infrastructure costs

Rate limiting helps ensure fair access while protecting backend services.

Common Rate Limiting Algorithms

ASP.NET Core supports multiple algorithms.

AlgorithmBest ForCharacteristics
Fixed WindowSimple APIsEasy to configure
Sliding WindowGeneral APIsSmoother traffic distribution
Token BucketBurst trafficAllows temporary bursts
Concurrency LimiterLong-running requestsLimits simultaneous requests

Choosing the appropriate algorithm depends on your workload.

Create a Web API

dotnet new webapi -n RateLimitingDemo

The built-in rate limiting middleware is available through ASP.NET Core.

Configure Rate Limiting

Register the service.

using System.Threading.RateLimiting;

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("api", config =>
    {
        config.PermitLimit = 100;

        config.Window = TimeSpan.FromMinutes(1);

        config.QueueLimit = 20;

        config.QueueProcessingOrder =
            QueueProcessingOrder.OldestFirst;
    });
});

This configuration allows 100 requests per minute while queuing a limited number of additional requests.

Enable the Middleware

var app = builder.Build();

app.UseRateLimiter();

app.MapControllers();

app.Run();

The middleware should be added before endpoints that require rate limiting.

Apply a Policy

Apply the configured policy to an endpoint.

app.MapGet("/products", () =>
{
    return Results.Ok("Success");
})
.RequireRateLimiting("api");

Only requests matching the configured limits are processed.

Fixed Window Limiter

A fixed window counts requests during a defined time interval.

Example:

Minute 1
██████████ 100 Requests

Minute 2
Counter Reset

It is simple and efficient but may allow bursts near window boundaries.

Sliding Window Limiter

Configure a sliding window.

builder.Services.AddRateLimiter(options =>
{
    options.AddSlidingWindowLimiter("sliding", config =>
    {
        config.PermitLimit = 100;

        config.Window = TimeSpan.FromMinutes(1);

        config.SegmentsPerWindow = 6;
    });
});

Instead of resetting all counters at once, the sliding window gradually expires older requests.

This produces smoother traffic handling.

Token Bucket Limiter

Configure a token bucket.

builder.Services.AddRateLimiter(options =>
{
    options.AddTokenBucketLimiter("token", config =>
    {
        config.TokenLimit = 100;

        config.TokensPerPeriod = 20;

        config.ReplenishmentPeriod =
            TimeSpan.FromSeconds(10);
    });
});

The token bucket algorithm allows short bursts while maintaining a controlled average request rate.

Concurrency Limiter

Limit simultaneous request execution.

builder.Services.AddRateLimiter(options =>
{
    options.AddConcurrencyLimiter("concurrent", config =>
    {
        config.PermitLimit = 25;

        config.QueueLimit = 50;
    });
});

This strategy is particularly useful for expensive operations such as file uploads or report generation.

Rate Limiting by Client

Policies can be partitioned using a client identifier.

builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy("clients", context =>
        RateLimitPartition.GetFixedWindowLimiter(
            context.Connection.RemoteIpAddress?.ToString()
                ?? "unknown",
            _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 50,
                Window = TimeSpan.FromMinutes(1)
            }));
});

Other partition keys may include:

  • API key

  • User ID

  • Tenant ID

  • JWT claim

Choose a partition strategy that aligns with your authentication model.

Custom Rejection Response

Customize responses for rejected requests.

builder.Services.AddRateLimiter(options =>
{
    options.OnRejected = async (context, _) =>
    {
        context.HttpContext.Response.StatusCode = 429;

        await context.HttpContext.Response.WriteAsync(
            "Too many requests.");
    };
});

Returning HTTP 429 clearly communicates that the client has exceeded the configured limit.

End-to-End Request Flow

A typical request follows these steps:

  1. Client sends a request.

  2. Rate limiter identifies the client partition.

  3. Available permits are checked.

  4. If permitted, the request proceeds.

  5. If the limit is exceeded, HTTP 429 is returned.

  6. Permits are replenished according to the configured algorithm.

This protects downstream services from excessive traffic.

Algorithm Comparison

FeatureFixed WindowSliding WindowToken BucketConcurrency
Easy to configureYesModerateModerateYes
Handles burstsLimitedGoodExcellentNo
Fair request distributionModerateExcellentGoodGood
Limits concurrent workNoNoNoYes
Best for APIsYesYesYesSpecific workloads

Testing Methodology

The research brief emphasizes production readiness but does not include benchmark results. To evaluate your implementation:

Test Environment

Maintain consistency for:

  • .NET SDK version

  • Hardware

  • Operating system

  • API configuration

  • Rate limiting policy

Test Scenarios

Evaluate:

  • Single client

  • Multiple clients

  • Burst traffic

  • Sustained traffic

  • Concurrent requests

  • Mixed request patterns

Metrics to Measure

Collect:

  • Requests per second

  • Successful requests

  • Rejected requests (HTTP 429)

  • Average response time

  • CPU utilization

  • Memory usage

  • Queue length

Useful Tools

Useful tools include:

  • k6

  • Bombardier

  • Apache JMeter

  • dotnet-counters

  • dotnet-trace

  • ASP.NET Core logging

Use production-like traffic patterns instead of synthetic single-user tests.

Best Practices

  • Apply rate limiting to public APIs.

  • Partition limits by authenticated identity whenever possible.

  • Return HTTP 429 for rejected requests.

  • Monitor rejection rates.

  • Use sliding window or token bucket for smoother traffic handling.

  • Combine rate limiting with authentication and caching.

  • Document API limits for consumers.

  • Review limits regularly as traffic grows.

Common Mistakes

MistakeImpact
Applying one global limit to every clientUnfair resource allocation
Setting limits too aggressivelyLegitimate requests rejected
Ignoring HTTP 429 handlingPoor client experience
Using IP-based limits behind proxies without configurationIncorrect client identification
Not monitoring rejection metricsHidden traffic issues
Choosing the wrong algorithmReduced effectiveness

Troubleshooting

Legitimate Users Receive HTTP 429

Review:

  • Permit limits

  • Window duration

  • Client partitioning

  • Traffic patterns

Increase limits only after confirming genuine demand.

Rate Limiting Doesn't Apply

Verify:

  • UseRateLimiter() is registered.

  • The correct policy name is used.

  • Endpoints require the configured policy.

Traffic Spikes Still Affect Performance

Check:

  • Algorithm selection

  • Queue limits

  • Downstream bottlenecks

  • Database capacity

Rate limiting protects the application but does not replace efficient application design.

FAQs

Which rate limiting algorithm should I choose?

For most APIs, the sliding window algorithm provides a good balance between fairness and performance. Token bucket is well suited for workloads that require controlled bursts.

Does rate limiting improve security?

It helps mitigate abuse and certain denial-of-service scenarios, but it should be combined with authentication, authorization, firewalls, and other security measures.

Should rate limits be based on IP addresses?

IP-based limits work for anonymous APIs, but authenticated applications often benefit from partitioning by user ID, API key, or tenant.

What HTTP status code should be returned?

The standard response is 429 Too Many Requests.

Can different endpoints have different limits?

Yes. ASP.NET Core allows multiple named policies that can be applied independently to different endpoints or controllers.

Conclusion

Rate limiting is an essential component of building resilient and scalable ASP.NET Core APIs. By controlling request rates, protecting backend resources, and ensuring fair access, it helps applications remain responsive even during traffic spikes or abusive usage.

ASP.NET Core's built-in rate limiting middleware provides flexible algorithms, straightforward configuration, and seamless integration with modern APIs. Combined with proper monitoring and realistic load testing, it enables you to build production-ready services that can handle growing traffic confidently.