How to implement Rate Limiting with Polly in .NET applications?

Asked 21 days ago Updated 18 days ago 120 views

3

Rate limiting controls the consumption rate of resources by limiting the number of operations that can execute within a specific time frame. This protects downstream dependencies from traffic spikes.

Using Rate Limiter in Polly v8

Polly v8 integrates directly with System.Threading.RateLimiting primitives.

Code Example

var rateLimiterOptions = new SlidingWindowRateLimiterOptions
{
    PermitLimit = 100,
    Window = TimeSpan.FromMinutes(1),
    SegmentsPerWindow = 4,
    QueueLimit = 10
};

var pipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new RateLimiterStrategyOptions
    {
        DefaultRateLimiter = new SlidingWindowRateLimiter(rateLimiterOptions)
    })
    .Build();

await pipeline.ExecuteAsync(async cancellationToken =>
{
    await SendApiRequestAsync(cancellationToken);
});

1 Answer


2

If you are building a .NET application that calls external APIs, you may eventually face a common problem:

"The API is getting too many requests. Slow down!"

Many third-party APIs have rate limits. For example, an API might allow only 10 requests per second.

If your application sends 50 requests per second, the API may start returning errors such as:

429 Too Many Requests

This is where rate limiting with Polly can help.

What Is Rate Limiting?

Rate limiting simply means:

Control how many requests your application sends within a certain amount of time.

For example:

Maximum: 5 requests per second

If your application needs to make 20 requests, instead of sending all 20 immediately, the rate limiter controls the requests.

Without rate limiting:

Request 1 ----\
Request 2 -----\
Request 3 ------> External API
Request 4 -----/
Request 5 ----/

With rate limiting:

Request 1 ---> API
Request 2 ---> API
Request 3 ---> API
Request 4 ---> Wait
Request 5 ---> Wait

The goal is to avoid overwhelming the external service.

What Is Polly?

Polly is a .NET resilience library.

It provides mechanisms for handling problems such as:

  • Rate limiting
  • Retries
  • Timeouts
  • Circuit breakers
  • Fallbacks
  • Other resilience scenarios

The important thing to understand is:

Polly doesn't make the external API faster. It helps your application behave better when communicating with external systems.

A Simple Real-World Example

Imagine your application calls a payment API.

The payment provider says:

Maximum: 10 requests per second

But your application receives 100 orders at the same time.

If you immediately send all 100 requests:

.NET Application
     |
     +---- Request 1
     +---- Request 2
     +---- Request 3
     ...
     +---- Request 100
     |
     v
Payment API

The payment API may reject many requests.

With rate limiting:

.NET Application
     |
     v
Polly Rate Limiter
     |
     +---- Request 1
     +---- Request 2
     +---- Request 3
     ...
     |
     v
Payment API

Polly controls the flow.

Installing Polly

For modern .NET applications using HttpClient, install the Polly HTTP resilience integration:

# Adds Polly-based resilience support for HttpClient.
dotnet add package Microsoft.Extensions.Http.Resilience

This package integrates resilience strategies with IHttpClientFactory.

You can also use Polly directly when you need more control.

The Basic Idea

Let's say we want to allow only 5 requests every second.

Conceptually:

5 requests
    |
    v
 1 second

If more requests arrive, they must wait or be rejected depending on the rate-limiting strategy.

Using Polly Rate Limiting with HttpClient

A common approach in an ASP.NET Core application is to configure a named or typed HttpClient.

For example:

using Polly;
using Polly.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

// Register an HttpClient with Polly resilience.
builder.Services
   .AddHttpClient("ExternalApi")
   .AddResilienceHandler("rate-limiter", pipeline =>
   {
       // Add a rate limiter to the pipeline.
       pipeline.AddRateLimiter(new HttpRateLimiterStrategyOptions
       {
           // Allow 5 requests in the configured window.
           PermitLimit = 5,

           // Time window for the limit.
           Window = TimeSpan.FromSeconds(1)
       });
   });

var app = builder.Build();

app.Run();

The exact API available can vary with the Polly and Microsoft.Extensions.Http.Resilience package versions, so always check the version-specific documentation when implementing this in a production project.

Let's Understand the Code

The important part is:

PermitLimit = 5

This means we are limiting the number of permits available.

And:

Window = TimeSpan.FromSeconds(1)

means the limit is applied over a one-second window.

So conceptually:

5 requests
within
1 second

Using the HttpClient

Now we can use the registered client.

For example:

app.MapGet("/products", async (IHttpClientFactory factory) =>
{
   // Get the HttpClient that we configured earlier.
   var client = factory.CreateClient("ExternalApi");

   // Call the external API.
   var response = await client.GetAsync(
       "https://api.example.com/products");

   // Return the response status.
   return response.StatusCode.ToString();
});

The important thing is that the request goes through the configured resilience pipeline.

Your API
  |
  v
HttpClient
  |
  v
Polly Rate Limiter
  |
  v
External API

What Happens When the Limit Is Reached?

Suppose the limit is:

5 requests / second

And your application tries to send 10 requests.

The first five can acquire permits.

The remaining requests depend on how your rate limiter is configured.

They may:

  • Wait for a permit
  • Be rejected
  • Fail with a rate-limit-related exception

This is important because rate limiting is not the same thing as retrying.

Rate Limiting vs Retry

These two concepts are easy to confuse.

Rate limiting

Rate limiting controls:

How quickly should I send requests?

Example:

Maximum 5 requests per second

Retry

Retry means:

The request failed. Should I try again?

For example:

Request
  |
  v
API
  |
  v
503 Service Unavailable
  |
  v
Wait
  |
  v
Retry

They solve different problems.

Why Combine Rate Limiting and Retry?

Imagine the external API has a rate limit.

Your application sends too many requests and receives:

429 Too Many Requests

A retry strategy can retry the request later.

But blindly retrying can make the problem worse:

Request
 |
 v
429
 |
 v
Retry immediately
 |
 v
429
 |
 v
Retry immediately
 |
 v
429

Now your application is sending even more requests.

A better approach is to use rate limiting and intelligent retry behavior together.

Example: Rate Limiter + Retry

Conceptually, a resilience pipeline can contain multiple strategies:

pipeline.AddRateLimiter(...);

pipeline.AddRetry(...);

pipeline.AddTimeout(...);

Think of it as:

Application
   |
   v
Rate Limiter
   |
   v
Retry
   |
   v
Timeout
   |
   v
External API

Each strategy handles a different problem.

A Simple Retry Example

For example:

pipeline.AddRetry(new HttpRetryStrategyOptions
{
   // Try the request up to 3 times.
   MaxRetryAttempts = 3,

   // Wait between retries.
   Delay = TimeSpan.FromSeconds(1)
});

The idea is:

First attempt
    |
  fails
    |
  wait
    |
Second attempt
    |
  fails
    |
  wait
    |
Third attempt

In real applications, retry configuration should be based on which errors are safe to retry.

For example, retrying a GET request is generally very different from blindly retrying a payment operation.

A Better Beginner Architecture

For an external API, you might have:

             Your .NET App
                   |
                   v
               HttpClient
                   |
                   v
            Polly Pipeline
                   |
         +---------+---------+
         |         |         |
         v         v         v
     Rate Limit  Retry    Timeout
         |         |         |
         +---------+---------+
                   |
                   v
             External API

This is a simple way to think about resilience.

What If I Don't Want Requests to Wait?

Sometimes you don't want requests to wait for a permit.

For example, imagine a high-traffic application where waiting requests could consume too many resources.

In that case, you can configure the rate limiter to reject requests when the limit is reached.

The application can then return an appropriate response.

For example:

Request
  |
  v
Rate Limiter
  |
  +---- Permit available ---> External API
  |
  +---- No permit ----------> Reject

The exact rejection handling depends on how your pipeline and application are configured.

Rate Limiting Is Not Only for External APIs

You can use rate limiting in several scenarios.

For example:

External API

Your Application ---> Third-party API

Internal API

Service A ---> Service B

Expensive operation

Application ---> Expensive database operation

Login endpoint

You may also want to limit repeated requests to sensitive endpoints to reduce abuse.

However, application-level and infrastructure-level rate limiting solve different problems, so choose the right layer for your use case.

Polly Rate Limiting vs ASP.NET Core Rate Limiting

This is another important distinction.

ASP.NET Core has its own rate-limiting middleware.

It is commonly used when you want to control incoming requests to your application.

For example:

Client
 |
 v
Your ASP.NET Core API
 |
 v
Rate Limiter
 |
 v
Controller

Polly is especially useful when you're building a resilient outgoing HTTP client.

For example:

Your ASP.NET Core API
       |
       v
    HttpClient
       |
       v
     Polly
       |
       v
External API

So ask yourself:

"Am I limiting requests coming into my API, or controlling requests going out to another service?"

That answer will often tell you which approach you need.

A Beginner Example

Let's imagine a weather application.

Your application calls:

Weather API

The provider allows:

10 requests per second

You configure your client with a rate limiter.

builder.Services
   .AddHttpClient("WeatherApi", client =>
   {
       // Set the base address of the external API.
       client.BaseAddress =
           new Uri("https://api.example.com/");
   })
   .AddResilienceHandler("weather-resilience", pipeline =>
   {
       // Limit outgoing requests.
       pipeline.AddRateLimiter(
           new HttpRateLimiterStrategyOptions
           {
               // Maximum number of requests allowed.
               PermitLimit = 10,

               // Apply the limit over one second.
               Window = TimeSpan.FromSeconds(1)
           });
   });

Then use it:

app.MapGet("/weather", async (
   IHttpClientFactory factory) =>
{
   // Get the configured Weather API client.
   var client = factory.CreateClient("WeatherApi");

   // Make the external API request.
   var response = await client.GetAsync("weather");

   // Return the external response.
   return Results.StatusCode(
       (int)response.StatusCode);
});

The flow is:

User
|
v
Your API
|
v
Weather HttpClient
|
v
Polly Rate Limiter
|
v
Weather API

Important: Rate Limiting Doesn't Mean "5 Requests Total"

If you configure:

PermitLimit = 5

that does not necessarily mean your application can make only five requests in its entire lifetime.

It means the limiter allows a certain number of permits according to the configured rate-limiting algorithm and window.

For example:

5 requests
|
+---- 1 second ----+
                  |
                  v
             New permits

The exact behavior depends on the rate-limiter algorithm you choose.

Common Beginner Mistakes

1. Confusing Rate Limiting with Retry

They are different.

Rate Limiting = Control request speed
Retry = Try a failed request again

2. Retrying 429 Immediately

If an API says:

429 Too Many Requests
  • immediately retrying repeatedly can make the situation worse.
  • Respect the API's rate-limit information and use sensible retry delays.

3. Limiting Incoming Requests with the Wrong Tool

  • If your goal is to protect your ASP.NET Core API from too many incoming requests, look at the built-in ASP.NET Core rate-limiting middleware.
  • If your goal is to control calls from your application to another API, Polly's HTTP resilience pipeline can be a good fit.

4. Forgetting Timeouts

  • A rate limiter does not protect you from an external API that simply hangs.
  • Consider a timeout as part of your resilience strategy.
Rate Limiter
    +
Retry
    +
Timeout

When Should You Use Polly Rate Limiting?

Polly rate limiting is a good choice when:

  • Your application calls third-party APIs.
  • The third-party API has request limits.
  • You want centralized resilience policies for HttpClient.
  • You need rate limiting together with retry and timeout behavior.
  • You want to prevent your application from overwhelming another service.

Final Takeaway

If you're a beginner, remember this simple idea:

Polly rate limiting controls how quickly your .NET application sends requests to another service.

The basic flow is:

.NET Application
      |
      v
   HttpClient
      |
      v
 Polly Pipeline
      |
      v
Rate Limiter
      |
      v
External API

And remember the difference:

Rate Limiting
"Don't send too many requests."

Retry
"The request failed; try again."

Timeout
"Don't wait forever."

Circuit Breaker
"Stop calling a service that is consistently failing."

These strategies can be combined to build a much more resilient .NET application.

For a beginner, start with rate limiting + timeout, understand how they behave, and then add retry or circuit breaker behavior based on your application's actual needs.

Write Your Answer