Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nimanikoo/Dotnet-RateLimiter/llms.txt

Use this file to discover all available pages before exploring further.

By the end of this guide you will have a running ASP.NET Core 10 API with cluster-wide, atomic Redis rate limiting protecting your endpoints. You’ll register the services, configure the Redis connection, apply the [RedisRateLimit] attribute to a controller action, and observe the HTTP 429 response — all in under five minutes.

Prerequisites

Before you begin, make sure you have the following available:
  • .NET 10 SDKDownload here
  • A Redis instance — local (localhost:6379) or via Docker (see the tip below)
  • NuGet access — to restore the packages listed in the next step
The fastest way to get Redis running locally is with the included Docker Compose file. Run docker-compose up -d redis from the repo root and Redis will be available on localhost:6379 immediately — no installation required.

Steps

1

Add NuGet packages

The project depends on the following NuGet packages. Add them to your .csproj or install via the CLI:
dotnet add package StackExchange.Redis --version 2.10.1
dotnet add package AspNetCore.HealthChecks.Redis --version 9.0.0
dotnet add package AspNetCore.HealthChecks.UI --version 9.0.0
dotnet add package AspNetCore.HealthChecks.UI.Client --version 9.0.0
dotnet add package AspNetCore.HealthChecks.UI.InMemory.Storage --version 9.0.0
dotnet add package Swashbuckle.AspNetCore --version 10.1.0
Or reference them directly in your project file:
<ItemGroup>
  <PackageReference Include="StackExchange.Redis" Version="2.10.1" />
  <PackageReference Include="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
  <PackageReference Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
  <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
  <PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="9.0.0" />
  <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.0" />
</ItemGroup>
2

Configure appsettings.json

Add the Redis section to your appsettings.json. The ConnectionString key is read by AddCustomRateLimiter to connect to Redis via StackExchange.Redis:
{
  "Redis": {
    "ConnectionString": "localhost:6379,abortConnect=false"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
The abortConnect=false option tells StackExchange.Redis not to throw an exception on startup if Redis is temporarily unavailable. This is important in containerized environments where the API may start before Redis is fully ready.
3

Register services and configure the middleware pipeline

Wire up the rate limiter, Redis connection, health checks, and Swagger in Program.cs using the AddCustomRateLimiter extension method. Then configure the full middleware pipeline:
using Dotnet_RateLimiter.Extensions;
using Dotnet_RateLimiter.Middlewares;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

// Registers: IConnectionMultiplexer, RedisRateLimiter, Health Checks,
// HealthChecks UI (in-memory), Controllers, Swagger, and all
// in-process rate limiter policies (Fixed Window, Sliding Window,
// Token Bucket, Concurrency).
builder.Services.AddCustomRateLimiter(builder.Configuration);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

// Activates the in-process ASP.NET Core rate limiter policies.
app.UseRateLimiter();

app.UseHttpsRedirection();

app.UseAuthorization();

// Activates the distributed Redis rate limiter middleware.
// Must be placed after UseAuthorization so that User.Identity
// is populated before identity resolution runs.
app.UseMiddleware<RedisRateLimitingMiddleware>();

app.MapControllers();

app.MapHealthChecks("/health", new HealthCheckOptions
{
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

app.UseHealthChecksUI(config =>
{
    config.UIPath = "/health-ui";
});

app.Run();
AddCustomRateLimiter internally:
  • Connects to Redis using configuration.GetSection("Redis:ConnectionString"), falling back to "localhost:6379" if the key is absent.
  • Registers IConnectionMultiplexer and RedisRateLimiter as singletons.
  • Adds a Redis health check tagged ["db", "cache", "redis"].
  • Configures the Health Checks UI to poll http://localhost:8080/health every 5 seconds with in-memory storage.
  • Registers all four in-process limiter policies, each with a rejection status code of 429.
4

Protect an endpoint with [RedisRateLimit]

Decorate any controller action with [RedisRateLimit(maxRequests, windowSeconds)] to enable distributed, atomic Redis rate limiting on that endpoint. Here is the real example from TestController.cs:
using Dotnet_RateLimiter.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;

[ApiController, Route("[controller]")]
public class TestController : ControllerBase
{
    // Distributed Redis rate limit: 2 requests per 10-second window,
    // scoped per authenticated user ID or anonymous IP + path.
    [HttpGet("limited")]
    [RedisRateLimit(maxRequests: 2, windowSeconds: 10)]
    public IActionResult GetLimited() => Ok("This is a limited endpoint!");

    // No rate limiting applied.
    [HttpGet("unlimited")]
    public IActionResult GetUnlimited() => Ok("This is unlimited! Sky is the limit.");

    // In-process Fixed Window: 5 requests per 5 seconds.
    [HttpGet("fixedwindow"), EnableRateLimiting("FixedWindowPolicy")]
    public IEnumerable<WeatherForecast> TestFixedWindow() => /* ... */;

    // In-process Sliding Window: 5 requests per 10-second rolling window.
    [HttpGet("slidingwindow"), EnableRateLimiting("SlidingWindowPolicy")]
    public IEnumerable<WeatherForecast> TestSlidingWindow() => /* ... */;

    // In-process Token Bucket: 5 tokens, refills 3 per 5 seconds.
    [HttpGet("bucket"), EnableRateLimiting("BucketPolicy")]
    public IEnumerable<WeatherForecast> TestBucket() => /* ... */;

    // In-process Concurrency: max 7 simultaneous requests.
    [HttpGet("concurrency"), EnableRateLimiting("ConcurrencyPolicy")]
    public IEnumerable<WeatherForecast> TestConcurrency() => /* ... */;
}
The RedisRateLimitAttribute stores the MaxRequests and WindowSeconds values:
public class RedisRateLimitAttribute : Attribute
{
    public int MaxRequests { get; }
    public int WindowSeconds { get; }

    public RedisRateLimitAttribute(int maxRequests, int windowSeconds)
    {
        MaxRequests = maxRequests;
        WindowSeconds = windowSeconds;
    }
}
The middleware reads this attribute from the endpoint metadata on every request and passes MaxRequests and WindowSeconds directly into the Lua script execution.
5

Run the app and test the rate limit

Start the application:
dotnet run
Then hit the limited endpoint more than twice within a 10-second window:
curl -i http://localhost:8080/test/limited
# HTTP/1.1 200 OK — first request

curl -i http://localhost:8080/test/limited
# HTTP/1.1 200 OK — second request

curl -i http://localhost:8080/test/limited
# HTTP/1.1 429 Too Many Requests
# Retry-After: 10
You can also browse to http://localhost:8080/swagger to call endpoints interactively via the Swagger UI.

HTTP 429 Response Behavior

When the Lua script returns 0 (limit exceeded), the RedisRateLimitingMiddleware sets the response status to 429 Too Many Requests and writes a Retry-After header containing the window duration in seconds. The response body is a human-readable message that varies based on the resolved client identity:
Client TypeResponse Message
Authenticated userDear user, you've reached your limit. Take a breath!
Anonymous guestGuest limit reached. Please sign up for more quota!
The Retry-After header value is exactly the windowSeconds value from the attribute — for [RedisRateLimit(maxRequests: 2, windowSeconds: 10)], the header will be Retry-After: 10.
The Retry-After header is set using context.Response.Headers.RetryAfter. Clients and API gateways that respect this standard header will automatically back off and retry after the specified number of seconds.

Build docs developers (and LLMs) love