ronnythedev/dotnet-clean-architecture-skills/24-dotnet-rate-limiting

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Implements ASP.NET Core rate limiting middleware for API protection. Covers fixed window, sliding window, token bucket, and concurrency limiters with custom policies.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/24-dotnet-rate-limiting

SKILL.md

Rate Limiting Middleware for ASP.NET Core

Overview

Built-in rate limiting middleware (ASP.NET Core 7+) protects APIs from abuse:

  • Fixed Window - Reset counter at fixed intervals
  • Sliding Window - Smoothed fixed window
  • Token Bucket - Allow bursts, refill over time
  • Concurrency - Limit simultaneous requests

Quick Reference

AlgorithmBest ForCharacteristics
Fixed WindowSimple rate limitsCounter resets at interval boundary
Sliding WindowSmoother limitingWeighted average across segments
Token BucketBurst toleranceAllows bursts up to bucket size
ConcurrencyResource protectionLimits parallel requests

Rate Limiting Structure

/API/RateLimiting/
├── RateLimitingConfiguration.cs
├── Policies/
│ ├── UserRateLimitPolicy.cs
│ └── IpRateLimitPolicy.cs
└── Middleware/
└── RateLimitExceededHandler.cs

Template: Basic Rate Limiting Configuration

csharp
// src/{name}.api/Program.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// ═══════════════════════════════════════════════════════════════
// RATE LIMITING CONFIGURATION
// ═══════════════════════════════════════════════════════════════
builder.Services.AddRateLimiter(options =>
{
// Global limiter settings
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// ═══════════════════════════════════════════════════════════════
// FIXED WINDOW POLICY
// Allows N requests per time window, resets at window boundary
// ═══════════════════════════════════════════════════════════════
options.AddFixedWindowLimiter("fixed", limiterOptions =>
{
limiterOptions.PermitLimit = 100; // Requests per window
limiterOptions.Window = TimeSpan.FromMinutes(1); // Window duration
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 10; // Queue when limit exceeded
});
// ═══════════════════════════════════════════════════════════════
// SLIDING WINDOW POLICY
// Smoother rate limiting with segments
// ═══════════════════════════════════════════════════════════════
options.AddSlidingWindowLimiter("sliding", limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.SegmentsPerWindow = 6; // 6 segments = 10s each
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 10;
});
// ═══════════════════════════════════════════════════════════════
// TOKEN BUCKET POLICY
// Allows bursts, then rate-limited
// ═══════════════════════════════════════════════════════════════
options.AddTokenBucketLimiter("token", limiterOptions =>
{
limiterOptions.TokenLimit = 100; // Maximum tokens (bucket size)
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
limiterOptions.TokensPerPeriod = 10; // Tokens added per period
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 10;
limiterOptions.AutoReplenishment = true;
});
// ═══════════════════════════════════════════════════════════════
// CONCURRENCY LIMITER
// Limits simultaneous requests (not rate)
// ═══════════════════════════════════════════════════════════════
options.AddConcurrencyLimiter("concurrency", limiterOptions =>
{
limiterOptions.PermitLimit = 50; // Max concurrent requests
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiterOptions.QueueLimit = 25;
});
// Custom response for rejected requests
options.OnRejected = async (context, cancellationToken) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString();
}
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.HttpContext.Response.WriteAsJsonAsync(new
{
error = "Too many requests",
message = "Rate limit exceeded. Please try again later.",
retryAfterSeconds = retryAfter?.TotalSeconds ?? 60
}, cancellationToken);
};
});
var app = builder.Build();
// ═══════════════════════════════════════════════════════════════
// MIDDLEWARE ORDER - Rate limiting before authentication
// ═══════════════════════════════════════════════════════════════
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

Template: Policy-Based Rate Limiting

csharp
// src/{name}.api/RateLimiting/RateLimitPolicies.cs
namespace {name}.api.ratelimiting;
public static class RateLimitPolicies
{
// Policy names
public const string Anonymous = "anonymous";
public const string Authenticated = "authenticated";
public const string Premium = "premium";
public const string Api = "api";
public const string Upload = "upload";
}
csharp
// src/{name}.api/RateLimiting/RateLimitingConfiguration.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
namespace {name}.api.ratelimiting;
public static class RateLimitingConfiguration
{
public static IServiceCollection AddRateLimitingPolicies(
this IServiceCollection services,
IConfiguration configuration)
{
var settings = configuration
.GetSection("RateLimiting")
.Get<RateLimitSettings>() ?? new RateLimitSettings();
services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
// ═══════════════════════════════════════════════════════════════
// ANONYMOUS USERS - Stricter limits
// ═══════════════════════════════════════════════════════════════
options.AddFixedWindowLimiter(RateLimitPolicies.Anonymous, limiterOptions =>
{
limiterOptions.PermitLimit = settings.AnonymousRequestsPerMinute;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 5;
});
// ═══════════════════════════════════════════════════════════════
// AUTHENTICATED USERS - More generous limits
// ═══════════════════════════════════════════════════════════════
options.AddTokenBucketLimiter(RateLimitPolicies.Authenticated, limiterOptions =>
{
limiterOptions.TokenLimit = settings.AuthenticatedBurstLimit;
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
limiterOptions.TokensPerPeriod = settings.AuthenticatedTokensPerSecond;
limiterOptions.QueueLimit = 20;
});
// ═══════════════════════════════════════════════════════════════
// PREMIUM USERS - Highest limits
// ═══════════════════════════════════════════════════════════════
options.AddTokenBucketLimiter(RateLimitPolicies.Premium, limiterOptions =>
{
limiterOptions.TokenLimit = settings.PremiumBurstLimit;
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(1);
limiterOptions.TokensPerPeriod = settings.PremiumTokensPerSecond;
limiterOptions.QueueLimit = 50;
});
// ═══════════════════════════════════════════════════════════════
// API ENDPOINTS - Per-endpoint limiting
// ═══════════════════════════════════════════════════════════════
options.AddSlidingWindowLimiter(RateLimitPolicies.Api, limiterOptions =>
{
limiterOptions.PermitLimit = settings.ApiRequestsPerMinute;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.SegmentsPerWindow = 6;
limiterOptions.QueueLimit = 10;
});
// ═══════════════════════════════════════════════════════════════
// FILE UPLOADS - Concurrency limited
// ═══════════════════════════════════════════════════════════════
options.AddConcurrencyLimiter(RateLimitPolicies.Upload, limiterOptions =>
{
limiterOptions.PermitLimit = settings.MaxConcurrentUploads;
limiterOptions.QueueLimit = settings.UploadQueueSize;
});
// Global rejection handler
options.OnRejected = HandleRejection;
});
return services;
}
private static async ValueTask HandleRejection(
OnRejectedContext context,
CancellationToken cancellationToken)
{
var response = context.HttpContext.Response;
// Set retry-after header if available
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
response.Headers.RetryAfter = ((int)retryAfter.TotalSeconds).ToString();
}
// Log the rejection
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<RateLimitingConfiguration>>();
var clientIp = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var path = context.HttpContext.Request.Path;
logger.LogWarning(
"Rate limit exceeded for client {ClientIp} on path {Path}",
clientIp,
path);
response.StatusCode = StatusCodes.Status429TooManyRequests;
response.ContentType = "application/json";
await response.WriteAsJsonAsync(new
{
type = "https://tools.ietf.org/html/rfc6585#section-4",
title = "Too Many Requests",
status = 429,
detail = "Rate limit exceeded. Please slow down your requests.",
retryAfter = retryAfter?.TotalSeconds ?? 60
}, cancellationToken);
}
}
Settings Class
csharp
// src/{name}.api/RateLimiting/RateLimitSettings.cs
namespace {name}.api.ratelimiting;
public sealed class RateLimitSettings
{
public int AnonymousRequestsPerMinute { get; set; } = 30;
public int AuthenticatedBurstLimit { get; set; } = 100;
public int AuthenticatedTokensPerSecond { get; set; } = 10;
public int PremiumBurstLimit { get; set; } = 500;
public int PremiumTokensPerSecond { get; set; } = 50;
public int ApiRequestsPerMinute { get; set; } = 60;
public int MaxConcurrentUploads { get; set; } = 5;
public int UploadQueueSize { get; set; } = 10;
}
appsettings.json
json
{
"RateLimiting": {
"AnonymousRequestsPerMinute": 30,
"AuthenticatedBurstLimit": 100,
"AuthenticatedTokensPerSecond": 10,
"PremiumBurstLimit": 500,
"PremiumTokensPerSecond": 50,
"ApiRequestsPerMinute": 60,
"MaxConcurrentUploads": 5,
"UploadQueueSize": 10
}
}

Template: User-Based Rate Limiting (Partitioned)

csharp
// src/{name}.api/Program.cs
builder.Services.AddRateLimiter(options =>
{
// ═══════════════════════════════════════════════════════════════
// PARTITIONED BY USER ID
// Each user has their own rate limit bucket
// ═══════════════════════════════════════════════════════════════
options.AddPolicy("per-user", httpContext =>
{
var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
64
Repository forks
11
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/24-dotnet-rate-limiting/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/24-dotnet-rate-limiting)

Something wrong with this skill or its page? Tell us.