惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
MyScale Blog
MyScale Blog
博客园_首页
G
Google Developers Blog
博客园 - 【当耐特】
美团技术团队
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
Vercel News
Vercel News
小众软件
小众软件
博客园 - 司徒正美
雷峰网
雷峰网
T
Tailwind CSS Blog
V
V2EX
博客园 - 三生石上(FineUI控件)
F
Fortinet All Blogs
罗磊的独立博客
量子位
P
Proofpoint News Feed
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Rate Limiting in C# — Don't Let Your API Get Hammered
Printo Tom · 2026-05-27 · via DEV Community

Printo Tom

If you run a public API without rate limiting, it's only a matter of time before a runaway client, a misconfigured retry loop, or a well-intentioned load test brings your service to its knees. .NET 7 shipped a first-class rate-limiting API — no third-party middleware required. This post walks through every knob you can turn.

Prerequisite: the built-in rate limiter lives in System.Threading.RateLimiting and the ASP.NET Core middleware in Microsoft.AspNetCore.RateLimiting. Both ship in the box from .NET 7 onwards.


Why rate limiting matters

Rate limiting protects three things simultaneously: your infrastructure from overload, your downstream dependencies from fan-out abuse, and your legitimate users from a noisy neighbour hogging capacity. It also plugs a class of denial-of-service vectors that auth alone can't stop.


The four built-in algorithms

1. Fixed window

Permits N requests per fixed time window (e.g. 100 requests per minute, window resets on the clock boundary). Simple, low memory, but can allow 2× burst at window boundaries.

using System.Threading.RateLimiting;

var limiter = new FixedWindowRateLimiter(
    new FixedWindowRateLimiterOptions
    {
        PermitLimit          = 100,
        Window               = TimeSpan.FromMinutes(1),
        QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
        QueueLimit           = 0   // reject immediately when full
    });

2. Sliding window

Divides the window into segments and tracks usage per segment. Smoother than fixed window — eliminates the boundary burst at the cost of slightly more memory.

var limiter = new SlidingWindowRateLimiter(
    new SlidingWindowRateLimiterOptions
    {
        PermitLimit          = 100,
        Window               = TimeSpan.FromMinutes(1),
        SegmentsPerWindow    = 6,     // 10-second granularity
        QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
        QueueLimit           = 0
    });

3. Token bucket

A bucket fills with tokens at a steady rate up to a maximum. Each request consumes one token. Allows short bursts up to the bucket capacity while enforcing a long-run average. Ideal for APIs where short spikes are acceptable.

var limiter = new TokenBucketRateLimiter(
    new TokenBucketRateLimiterOptions
    {
        TokenLimit               = 50,   // max burst
        ReplenishmentPeriod      = TimeSpan.FromSeconds(10),
        TokensPerPeriod          = 10,   // ~1/s average
        AutoReplenishment        = true,
        QueueProcessingOrder     = QueueProcessingOrder.OldestFirst,
        QueueLimit               = 0
    });

4. Concurrency limiter

Limits simultaneous in-flight requests rather than request rate. Useful for protecting expensive operations like report generation or ML inference where time-in-system matters more than throughput.

var limiter = new ConcurrencyLimiter(
    new ConcurrencyLimiterOptions
    {
        PermitLimit          = 20,
        QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
        QueueLimit           = 5
    });


Wiring it up in ASP.NET Core

Register policies in Program.cs, then apply them with the [EnableRateLimiting] attribute or inline via RequireRateLimiting().

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter(policyName: "fixed", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window      = TimeSpan.FromMinutes(1);
        opt.QueueLimit  = 0;
    });

    options.AddTokenBucketLimiter(policyName: "burst", opt =>
    {
        opt.TokenLimit          = 50;
        opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        opt.TokensPerPeriod     = 10;
        opt.AutoReplenishment   = true;
    });
});

var app = builder.Build();
app.UseRateLimiter();   // must come before MapControllers

Apply to a minimal API endpoint or controller action:

// Minimal API
app.MapGet("/products", GetProducts)
   .RequireRateLimiting("fixed");

// Controller
[EnableRateLimiting("burst")]
[HttpGet("search")]
public IActionResult Search(string query) { ... }


Per-user and per-endpoint policies

A single global policy rarely fits real-world needs. Use AddPolicy with a partition key derived from the request context:

options.AddPolicy("per-user", httpContext =>
    RateLimitPartition.GetTokenBucketLimiter(
        partitionKey: httpContext.User.Identity?.Name
                      ?? httpContext.Connection.RemoteIpAddress?.ToString()
                      ?? "anonymous",
        factory: _ => new TokenBucketRateLimiterOptions
        {
            TokenLimit          = 200,
            ReplenishmentPeriod = TimeSpan.FromMinutes(1),
            TokensPerPeriod     = 200,
            AutoReplenishment   = true
        }));

Tip: prefer authenticated user ID over IP address as the partition key — NAT and proxies can share a single IP across hundreds of users, leading to false positives at scale.


Custom rejection responses

By default, the middleware returns 503 Service Unavailable. The RFC-correct status for rate limiting is 429 Too Many Requests with a Retry-After header:

options.OnRejected = async (context, token) =>
{
    context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;

    if (context.Lease.TryGetMetadata(
            MetadataName.RetryAfter, out var retryAfter))
    {
        context.HttpContext.Response.Headers.Append(
            "Retry-After",
            ((int)retryAfter.TotalSeconds).ToString(
                System.Globalization.CultureInfo.InvariantCulture));
    }

    await context.HttpContext.Response.WriteAsync(
        "Rate limit exceeded. Please slow down.", token);
};


Distributed scenarios & Redis

The built-in limiters are in-process only — each pod maintains its own counters. In a horizontally scaled deployment, use a Redis-backed limiter via the RedisRateLimiting community library, which wraps the same RateLimiter abstraction:

dotnet add package RedisRateLimiting

builder.Services.AddStackExchangeRedisCache(o =>
    o.Configuration = builder.Configuration["Redis:Connection"]);

options.AddPolicy("distributed", httpContext =>
    RedisRateLimitPartition.GetSlidingWindowRateLimiter(
        partitionKey: httpContext.User.Identity?.Name ?? "anon",
        factory: _ => new RedisSlidingWindowRateLimiterOptions
        {
            ConnectionMultiplexerFactory =
                httpContext.RequestServices
                    .GetRequiredService<IConnectionMultiplexer>,
            PermitLimit = 500,
            Window      = TimeSpan.FromMinutes(1)
        }));


Client-side resilience with Polly

If your code consumes a rate-limited API, use Polly's RateLimiter strategy combined with Retry to handle 429s gracefully:

dotnet add package Polly.Extensions.Http

services.AddHttpClient<IProductsClient, ProductsClient>()
        .AddResilienceHandler("products-pipeline", builder =>
        {
            builder.AddRateLimiter(new SlidingWindowRateLimiter(
                new SlidingWindowRateLimiterOptions
                {
                    PermitLimit       = 50,
                    Window            = TimeSpan.FromSeconds(10),
                    SegmentsPerWindow = 5
                }));

            builder.AddRetry(new HttpRetryStrategyOptions
            {
                MaxRetryAttempts = 3,
                Delay            = TimeSpan.FromSeconds(2),
                BackoffType      = DelayBackoffType.Exponential,
                ShouldHandle     = args => ValueTask.FromResult(
                    args.Outcome.Result?.StatusCode ==
                        HttpStatusCode.TooManyRequests)
            });
        });


Choosing the right algorithm

Algorithm Best for Watch out for Memory cost
Fixed window Simple quotas, billing tiers Boundary burst (2× spike) Very low
Sliding window Smooth public APIs Segment count × partitions Low–medium
Token bucket Burst-tolerant consumer APIs Tuning burst vs average Low
Concurrency Expensive ops (ML, reports) Doesn't bound throughput Very low

Distributed gotcha: in-process limiters per pod means a cluster of 4 replicas effectively multiplies your limit by 4. Always use a Redis-backed partitioned limiter for multi-replica deployments where correctness matters.


Wrapping up

.NET 7+ gives you production-grade rate limiting with zero external dependencies for single-node scenarios. The four algorithms cover the full spectrum from simple quotas to burst-tolerant consumer clients. Add Redis for distributed enforcement, Polly for client-side resilience, and always return 429 with a Retry-After header — your API consumers will thank you.

Questions or patterns I missed? Drop them in the comments.