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.

AddCustomRateLimiter is a single extension method that wires up every service Dotnet-RateLimiter needs in one call. It connects to Redis, registers the RedisRateLimiter singleton, configures all four built-in ASP.NET Core rate limiting policies, sets up health checks with a live UI dashboard, and registers controllers with Swagger — so your Program.cs stays clean and focused on the middleware pipeline.

NuGet Dependencies

Add the following packages to your project (versions match those declared in Dotnet-RateLimiter.csproj):
PackageVersion
StackExchange.Redis2.10.1
AspNetCore.HealthChecks.Redis9.0.0
AspNetCore.HealthChecks.UI9.0.0
AspNetCore.HealthChecks.UI.Client9.0.0
AspNetCore.HealthChecks.UI.InMemory.Storage9.0.0
Swashbuckle.AspNetCore10.1.0
Microsoft.AspNetCore.OpenApi10.0.1
Install them via the .NET 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
dotnet add package Microsoft.AspNetCore.OpenApi --version 10.0.1

The AddCustomRateLimiter Extension Method

AddCustomRateLimiter lives in Extensions/RateLimiterExtensions.cs. Call it once from Program.cs and pass in IConfiguration so it can read the Redis connection string from appsettings.json.
using System.Threading.RateLimiting;
using Dotnet_RateLimiter.Services;
using Microsoft.AspNetCore.RateLimiting;
using StackExchange.Redis;

namespace Dotnet_RateLimiter.Extensions;

public static class RateLimiterExtensions
{
    public static IServiceCollection AddCustomRateLimiter(this IServiceCollection services,
        IConfiguration configuration)
    {
        var redisConnectionString = configuration.GetSection("Redis:ConnectionString").Value
                                    ?? "localhost:6379";

        var redis = ConnectionMultiplexer.Connect(redisConnectionString);
        services.AddSingleton<IConnectionMultiplexer>(redis);
        services.AddHealthChecks()
            .AddRedis(
                redisConnectionString: configuration.GetSection("Redis:ConnectionString").Value!,
                name: "Redis Server",
                tags: ["db", "cache", "redis"]
            );
        
        services.AddHealthChecksUI(settings =>
        {
            settings.AddHealthCheckEndpoint("Main App", "http://localhost:8080/health"); 
            settings.SetEvaluationTimeInSeconds(5);
        }).AddInMemoryStorage(); 
        
        services.AddSingleton<RedisRateLimiter>();
        services.AddControllers();
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
        services.AddRateLimiter(options =>
        {
            options.AddFixedWindowLimiter("FixedWindowPolicy", opt =>
            {
                opt.Window = TimeSpan.FromSeconds(5);
                opt.PermitLimit = 5;
                opt.QueueLimit = 10;
                //LIFO approach : Last in First out 
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
            }).RejectionStatusCode = 429;

            options.AddSlidingWindowLimiter("SlidingWindowPolicy", opt =>
            {
                opt.Window = TimeSpan.FromSeconds(10);
                opt.PermitLimit = 5;
                opt.QueueLimit = 10;
                //LIFO approach : Last in First out
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.SegmentsPerWindow = 4;
            }).RejectionStatusCode = 429;

            options.AddConcurrencyLimiter("ConcurrencyPolicy", opt =>
            {
                opt.PermitLimit = 7;
                opt.QueueLimit = 3;
                //LIFO approach : Last in First out
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
            }).RejectionStatusCode = 429;

            options.AddTokenBucketLimiter("BucketPolicy", opt =>
            {
                opt.TokenLimit = 5;
                opt.QueueLimit = 2;
                //LIFO approach : Last in First out
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.ReplenishmentPeriod = TimeSpan.FromSeconds(5);
                opt.AutoReplenishment = true;
                opt.TokensPerPeriod = 3;
            }).RejectionStatusCode = 429;
        });
        return services;
    }
}

What AddCustomRateLimiter registers

RegistrationDetail
IConnectionMultiplexer (singleton)Redis connection via StackExchange.Redis
Health checksRedis probe named "Redis Server" with tags db, cache, redis
Health Checks UIDashboard polled every 5 seconds, backed by in-memory storage
RedisRateLimiter (singleton)Custom distributed rate limiter service
Controllers + SwaggerAddControllers, AddEndpointsApiExplorer, AddSwaggerGen
FixedWindowPolicy5 requests per 5-second window, queue 10, rejection → 429
SlidingWindowPolicy5 requests per 10-second window, 4 segments, queue 10, rejection → 429
ConcurrencyPolicy7 concurrent requests, queue 3, rejection → 429
BucketPolicyToken bucket: 5 tokens max, 3 tokens per 5-second period, auto-replenish, queue 2, rejection → 429

Middleware Pipeline (Program.cs)

The full Program.cs wires the middleware pipeline in a specific order that matters for correctness:
using Dotnet_RateLimiter.Extensions;
using Dotnet_RateLimiter.Middlewares;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddCustomRateLimiter(builder.Configuration);

var app = builder.Build();

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

app.UseRateLimiter();                              // 1. Built-in ASP.NET Core policies

app.UseHttpsRedirection();                         // 2. HTTPS redirect

app.UseAuthorization();                            // 3. Authorization

app.UseMiddleware<RedisRateLimitingMiddleware>();   // 4. Custom Redis rate limiting

app.MapControllers();                              // 5. Controller routes

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

app.UseHealthChecksUI(config => 
{
    config.UIPath = "/health-ui";
});                                                // 7. Health Checks UI dashboard

app.Run();

Pipeline order explained

StepMiddlewarePurpose
1UseRateLimiter()Applies in-memory [EnableRateLimiting("PolicyName")] attributes (FixedWindow, SlidingWindow, Concurrency, Bucket)
2UseHttpsRedirection()Redirects HTTP → HTTPS
3UseAuthorization()Resolves the authenticated user identity used by the Redis middleware
4UseMiddleware<RedisRateLimitingMiddleware>()Reads [RedisRateLimit] endpoint metadata and enforces distributed Redis-backed limits
5MapControllers()Registers controller action routes
6MapHealthChecks("/health", ...)Exposes the /health JSON endpoint consumed by the UI
7UseHealthChecksUI(...)Serves the live dashboard at /health-ui
Middleware order is not optional. UseRateLimiter() must be called before UseMiddleware<RedisRateLimitingMiddleware>(). The built-in rate limiter needs to run first so that requests blocked by in-memory policies never reach the Redis layer. Additionally, UseAuthorization() must come before UseMiddleware<RedisRateLimitingMiddleware>() so that context.User is populated when the Redis middleware determines whether the caller is an authenticated user or an anonymous guest.

Build docs developers (and LLMs) love