I’ve been working on a project using ZhonTai’s Admin.Core framework recently. While going through the configuration files, I noticed that IP rate limiting is built-in, implemented via the AspNetCoreRateLimit component combined with Redis. After studying it, I’m documenting how to use it and the underlying principles.


Enabling Rate Limiting

Rate limiting is disabled by default in Admin.Core and needs to be manually enabled in two places.

In appconfig.json, turn on the rate limiting switch:

{
  "rateLimit": true
}

Then, configure the rate limiting storage method as Redis in cacheconfig.json:

{
  "typeRateLimit": "Redis"
}

If Redis is not configured, the rate-limiting counter will be stored in memory. This works fine for a single instance, but if multiple instances are deployed, each will calculate its own rate limit independently, rendering the rate limiting ineffective.

Configuring Rate Limiting Rules

Rate limiting rules are defined under the IpRateLimiting node in appsettings.json:

{
  "IpRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "HttpStatusCode": 429,
    "GeneralRules": [
      {
        "Endpoint": "*",
        "Period": "1s",
        "Limit": 10
      },
      {
        "Endpoint": "post:/api/admin/auth/login",
        "Period": "1m",
        "Limit": 5
      }
    ]
  }
}

A few key configurations to note:

EnableEndpointRateLimiting should be set to true; otherwise, all rules apply globally, making it impossible to rate-limit specific endpoints individually.

It is recommended to keep StackBlockedRequests as false. If set to true, repeated requests after being rate-limited will still be counted towards the access count, potentially causing them to remain blocked indefinitely.

RealIpHeader: If your service sits behind a reverse proxy like Nginx or Caddy, you need to configure this to the header passed by the reverse proxy containing the real client IP. Otherwise, rate limiting will apply to the reverse proxy's IP address, meaning all users would share the same counter.

The format for Endpoint is HTTP Method:Path, where * represents all endpoints. The configuration above means: a global limit of 10 requests per second, and a limit of 5 requests per minute specifically for the login endpoint.

How It Works

The principle is straightforward. When each request arrives, the AspNetCoreRateLimit middleware performs the following steps:

  1. Retrieves the client's IP address.
  2. Generates a Redis key based on the IP + request path.
  3. Performs an increment operation on this key while setting an expiration time (corresponding to your configured Period).
  4. If the count exceeds the Limit, it immediately returns a 429 status code, and the request does not reach the Controller.

Since the counter is stored in Redis, multiple instances share the same data. Therefore, regardless of which instance handles the request via load balancing, the rate limiting applies uniformly.

Behavior After Rate Limiting Is Triggered

When rate-limited, the endpoint returns an HTTP 429 status code. The Admin.Core frontend also handles this by displaying a notification to inform the user that requests are too frequent.

If you want to customize the response content returned after rate limiting is triggered, you can add a QuotaExceededResponse configuration:

{
  "IpRateLimiting": {
    "QuotaExceededResponse": {
      "Content": "{{ \"message\": \"请求太频繁,请稍后再试\", \"details\": \"限流规则: 每 {1} 最多 {0} 次,请 {2} 秒后重试\" }}",
      "ContentType": "application/json",
      "StatusCode": 429
    }
  }
}

A Common Pitfall to Avoid

The order of middleware registration is crucial. UseIpRateLimiting() must be placed after UseStaticFiles(). Otherwise, requests for static files (JS, CSS, images) will also count toward the request limit, causing rate limiting to kick in almost immediately.

How to Integrate Without Admin.Core

If you're not using Admin.Core and want to integrate this rate-limiting solution into your own .NET Web API project, it boils down to three core steps:

Install dependencies:

dotnet add package AspNetCoreRateLimit
dotnet add package AspNetCoreRateLimit.Redis
dotnet add package StackExchange.Redis

Register service:

builder.Services.AddOptions();
builder.Services.Configure<IpRateLimitOptions>(
    builder.Configuration.GetSection("IpRateLimiting"));

var redisOptions = ConfigurationOptions.Parse(
    builder.Configuration.GetConnectionString("Redis"));
builder.Services.AddSingleton<IConnectionMultiplexer>(
    _ => ConnectionMultiplexer.Connect(redisOptions));

builder.Services.AddRedisRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

Using middleware:

app.UseStaticFiles();
app.UseIpRateLimiting();  // 放在 UseStaticFiles 后面

The rule format in the configuration file is the same as above.

Another Option

Starting with .NET 7, Microsoft has included built-in rate limiting middleware Microsoft.AspNetCore.RateLimiting, which supports four algorithms: fixed window, sliding window, token bucket, and concurrent limit. For distributed rate limiting combined with Redis, you can use the community-maintained NuGet package RedisRateLimiting.

However, Admin.Core uses AspNetCoreRateLimit. This component was released earlier and offers more flexible configuration, with nearly 3,000 stars on GitHub. If you are already using Admin.Core, simply use its built-in rate limiting without any additional effort.

This content is automatically translated to English. View Original