# ASP.NET Core Rate Limiting Middleware Deep Dive: IRateLimiterPolicy and Endpoint Configuration

> Master ASP.NET Core rate limiting middleware. Learn about IRateLimiterPolicy and endpoint configuration with RateLimiterOptions for robust API protection.

- Repository: [.NET Platform/aspnetcore](https://github.com/dotnet/aspnetcore)
- Tags: deep-dive
- Published: 2026-06-20

---

**ASP.NET Core rate limiting middleware evaluates every request against `PartitionedRateLimiter` instances configured via `RateLimiterOptions`, applying named policies or custom `IRateLimiterPolicy` implementations bound to endpoints through `EnableRateLimitingAttribute` metadata.**

The rate limiting system in the `dotnet/aspnetcore` repository provides a flexible, DI-driven architecture for throttling HTTP requests. Built around the `RateLimiterOptions` configuration object and the extensible `IRateLimiterPolicy` interface, this middleware integrates directly with endpoint routing to enforce limits globally or on specific routes.

## Core Architecture and Service Registration

The middleware relies on services registered through `AddRateLimiter()`, which is defined in [`RateLimiterServiceCollectionExtensions.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimiterServiceCollectionExtensions.cs). This extension method configures `RateLimiterOptions`, registers metrics services, and prepares the policy resolution pipeline.

### RateLimiterOptions and Policy Storage

`RateLimiterOptions` (implemented in [`RateLimiterOptions.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimiterOptions.cs)) serves as the central configuration hub. It stores three critical elements:

- **`GlobalLimiter`** – A `PartitionedRateLimiter<HttpContext>` applied to every request before endpoint-specific evaluation.
- **`PolicyMap`** – A dictionary of activated named policies (`DefaultRateLimiterPolicy` instances).
- **`UnactivatedPolicyMap`** – A dictionary of policy types that implement `IRateLimiterPolicy<TPartitionKey>`, which the middleware instantiates via DI on first use.

You register policies using either `AddPolicy<TPartitionKey>(name, partitioner)` for direct partitioner functions or `AddPolicy<TPartitionKey, TPolicy>(name)` for custom policy classes.

### The IRateLimiterPolicy Interface

Custom policies implement `IRateLimiterPolicy<TPartitionKey>` from [`IRateLimiterPolicy.cs`](https://github.com/dotnet/aspnetcore/blob/main/IRateLimiterPolicy.cs). This interface requires two members:

```csharp
public interface IRateLimiterPolicy<TPartitionKey>
{
    Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
    RateLimitPartition<TPartitionKey> GetPartition(HttpContext httpContext);
}

```

The `GetPartition` method returns a `RateLimitPartition` that determines which rate limiter instance applies to the current request based on the partition key (e.g., client IP, user ID). The optional `OnRejected` callback executes when a request exceeds the limit, allowing custom response headers or logging.

## Configuring Rate Limiting Policies

### Named Policies with Direct Partitioners

For simple scenarios, define a partitioner directly in [`Program.cs`](https://github.com/dotnet/aspnetcore/blob/main/Program.cs):

```csharp
builder.Services.AddRateLimiter(options =>
{
    options.AddPolicy<string>("fixed", ctx =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown",
            replenishPeriod: TimeSpan.FromSeconds(10),
            permitLimit: 5,
            queueLimit: 0));
});

```

This registers a named policy "fixed" that applies a fixed-window limiter keyed by remote IP address.

### Custom Policy Classes

For complex logic or reusable components, implement `IRateLimiterPolicy`:

```csharp
public sealed class SlidingWindowPolicy : IRateLimiterPolicy<string>
{
    public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected => null;

    public RateLimitPartition<string> GetPartition(HttpContext httpContext)
    {
        var ip = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
        return RateLimitPartition.GetSlidingWindowLimiter(
            partitionKey: ip,
            window: TimeSpan.FromSeconds(30),
            permitLimit: 20,
            queueLimit: 5);
    }
}

```

Register the policy type:

```csharp
builder.Services.AddRateLimiter(opts =>
{
    opts.AddPolicy<string, SlidingWindowPolicy>("sliding");
});

```

### Global Rate Limiters

To apply limits to all requests regardless of endpoint metadata, configure the `GlobalLimiter` property:

```csharp
builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(ctx =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: "global",
            replenishPeriod: TimeSpan.FromSeconds(1),
            permitLimit: 100,
            queueLimit: 0));
});

```

## Endpoint Metadata and Middleware Execution

### Applying Policies to Endpoints

The middleware discovers policies through endpoint metadata. The `EnableRateLimitingAttribute` (defined in [`EnableRateLimitingAttribute.cs`](https://github.com/dotnet/aspnetcore/blob/main/EnableRateLimitingAttribute.cs)) links a policy name or instance to an endpoint. You apply this metadata using the `RequireRateLimiting` extension method from [`RateLimiterEndpointConventionBuilderExtensions.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimiterEndpointConventionBuilderExtensions.cs):

```csharp
app.UseRateLimiter();

app.MapGet("/api/data", () => "data")
   .RequireRateLimiting("fixed");

app.MapGet("/api/premium", () => "premium")
   .RequireRateLimiting(new SlidingWindowPolicy());

```

The middleware also supports `DisableRateLimitingAttribute` to explicitly bypass limits for specific endpoints.

### Middleware Request Flow

The `RateLimitingMiddleware` (in [`RateLimitingMiddleware.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimitingMiddleware.cs)) processes requests through the following stages:

1. **Endpoint Resolution** – Retrieves the endpoint via `context.GetEndpoint()`.
2. **Disable Check** – If `DisableRateLimitingAttribute` is present, the request proceeds immediately.
3. **Policy Resolution** – If `EnableRateLimitingAttribute` is found, the middleware resolves the corresponding `PartitionedRateLimiter` from the policy map or falls back to the global limiter.
4. **Lease Acquisition** – Calls `CombinedAcquire` for instant evaluation; if insufficient permits exist, `CombinedWaitAsync` handles queued or delayed acquisition.
5. **Success Path** – Records metrics and invokes the next middleware.
6. **Rejection Path** – Sets the `RejectionStatusCode` (default 503), records failure metrics, and invokes the `OnRejected` callback if configured.

Internally, the middleware converts user-defined partitioners into a unified `PartitionedRateLimiter<DefaultKeyType>` using the `ConvertPartitioner` helper method in [`RateLimiterOptions.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimiterOptions.cs) to prevent key collisions between different policy types.

## Handling Rejected Requests

You can configure rejection behavior globally or per-policy:

```csharp
builder.Services.AddRateLimiter(opts =>
{
    opts.OnRejected = (ctx, ct) =>
    {
        ctx.HttpContext.Response.Headers.Add("Retry-After", "60");
        return ValueTask.CompletedTask;
    };
});

```

For policy-specific rejection handling, return a delegate from the `OnRejected` property in your `IRateLimiterPolicy` implementation.

## Summary

- **Service Registration** – Call `AddRateLimiter()` in [`Program.cs`](https://github.com/dotnet/aspnetcore/blob/main/Program.cs) to configure `RateLimiterOptions` and register required services.
- **Policy Types** – Use `AddPolicy` with a partitioner function for simple cases, or implement `IRateLimiterPolicy<TPartitionKey>` for complex logic with DI support.
- **Endpoint Binding** – Apply `[EnableRateLimiting("policyName")]` or use `RequireRateLimiting()` to attach rate limits to specific routes.
- **Middleware Flow** – The middleware checks `DisableRateLimitingAttribute`, resolves policies from endpoint metadata, and attempts lease acquisition through [`RateLimitingMiddleware.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimitingMiddleware.cs).
- **Extensibility** – Customize rejection responses via `OnRejected` callbacks in `RateLimiterOptions` or within individual policy implementations.

## Frequently Asked Questions

### What is the difference between the GlobalLimiter and endpoint-specific policies?

The `GlobalLimiter` applies to every request that passes through the middleware pipeline, regardless of endpoint metadata. Endpoint-specific policies only activate when an endpoint has `EnableRateLimitingAttribute` metadata. The middleware evaluates the global limiter first, then the endpoint-specific limiter, effectively creating a two-tier throttling system.

### How do I implement a custom IRateLimiterPolicy?

Create a class implementing `IRateLimiterPolicy<TPartitionKey>` (defined in [`IRateLimiterPolicy.cs`](https://github.com/dotnet/aspnetcore/blob/main/IRateLimiterPolicy.cs)), implement the `GetPartition` method to return a `RateLimitPartition`, and optionally provide an `OnRejected` callback. Register it using `options.AddPolicy<TPartitionKey, YourPolicy>("name")` in [`RateLimiterOptions.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimiterOptions.cs). The middleware instantiates your policy via DI when the first request targeting that policy arrives.

### Can I disable rate limiting for specific endpoints?

Yes. Apply the `[DisableRateLimiting]` attribute to controllers, actions, or use the `DisableRateLimiting()` extension method on endpoint conventions. When the middleware in [`RateLimitingMiddleware.cs`](https://github.com/dotnet/aspnetcore/blob/main/RateLimitingMiddleware.cs) detects this metadata, it bypasses all limit checks for that request, including the `GlobalLimiter`.

### What happens when a request exceeds the rate limit?

The middleware sets the response status code to `503 Service Unavailable` (configurable via `RejectionStatusCode`), records the rejection in `RateLimiterMetrics`, and invokes the `OnRejected` callback. If the request was queued (waiting for a permit), the middleware cancels the lease attempt and short-circuits the pipeline.