Browse Source

Update operation rate limiting docs for named partition resolver API

pull/25092/head
maliming 5 months ago
parent
commit
c6170ba804
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 2
      docs/en/Community-Articles/2026-03-10-Operation-Rate-Limiting-in-ABP-Framework/POST.md
  2. 76
      docs/en/modules/operation-rate-limiting.md

2
docs/en/Community-Articles/2026-03-10-Operation-Rate-Limiting-in-ABP-Framework/POST.md

@ -149,7 +149,7 @@ Getting this wrong can make your rate limiting completely ineffective. Using `Pa
- **`PartitionByCurrentUser`** — uses the authenticated user's ID, with no value to pass. Perfect for "each user gets N per day" scenarios where user identity is all you need.
- **`PartitionByClientIp`** — uses the client's IP address. Don't rely on this alone — it's too easy to rotate. Use it as a secondary layer alongside another partition type, as in the login example below.
- **`PartitionByEmail`** and **`PartitionByPhoneNumber`** — designed for pre-authentication flows where the user isn't logged in yet. They prefer the `Parameter` value you explicitly pass, and fall back to the current user's email or phone number if none is provided.
- **`PartitionBy`** — a custom async delegate that can produce any partition key you need. When the built-in options don't fit, you're free to implement whatever logic makes sense: look up a resource's owner in the database, derive a key from the user's subscription tier, partition by tenant — anything that returns a string.
- **`PartitionBy`** — a named custom resolver that can produce any partition key you need. Register a resolver function under a unique name via `options.AddPartitionKeyResolver("MyResolver", ctx => ...)`, then reference it by name: `.PartitionBy("MyResolver")`. You can also register and reference in one step: `.PartitionBy("MyResolver", ctx => ...)`. When the built-in options don't fit, you're free to implement whatever logic makes sense: look up a resource's owner in the database, derive a key from the user's subscription tier, partition by tenant — anything that returns a string. Because the resolver is stored by name (not as an anonymous delegate), it can be serialized and managed from a UI or database.
> The rule of thumb: partition by the identity of whoever's behavior you're trying to limit.

76
docs/en/modules/operation-rate-limiting.md

@ -374,14 +374,59 @@ Works the same way as `PartitionByEmail`: resolves from `context.Parameter` firs
### Custom Partition (PartitionBy)
You can provide a custom async function to generate the partition key. The async signature allows you to perform database queries or other I/O operations:
You can register a named custom resolver to generate the partition key. The resolver is an async function, so you can perform database queries or other I/O operations. Because the resolver is stored by name (not as an anonymous delegate), it can be serialized and managed from a UI or database.
**Step 1 — Register the resolver by name:**
````csharp
Configure<AbpOperationRateLimitingOptions>(options =>
{
options.AddPartitionKeyResolver("ByDevice", ctx =>
Task.FromResult($"{ctx.Parameter}:{ctx.ExtraProperties["DeviceId"]}"));
});
````
**Step 2 — Reference it in a policy:**
````csharp
policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 100)
.PartitionBy("ByDevice");
````
You can also register and reference in one step (inline):
````csharp
policy.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 100)
.PartitionBy(ctx => Task.FromResult(
$"{ctx.Parameter}:{ctx.ExtraProperties["DeviceId"]}"));
.PartitionBy("ByDevice", ctx =>
Task.FromResult($"{ctx.Parameter}:{ctx.ExtraProperties["DeviceId"]}"));
````
> If you call `PartitionBy("name")` with a resolver name that hasn't been registered, an exception is thrown at configuration time (not at runtime), so typos are caught early.
To replace an existing resolver (e.g., in a downstream module), use `ReplacePartitionKeyResolver`:
````csharp
options.ReplacePartitionKeyResolver("ByDevice", ctx =>
Task.FromResult($"v2:{ctx.Parameter}:{ctx.ExtraProperties["DeviceId"]}"));
````
### Named Rules (WithName)
By default, a rule's store key is derived from its `Duration`, `MaxCount`, and `PartitionType`. This means that if you change a rule's parameters (e.g., increase `maxCount` from 5 to 10), the counter resets because the key changes.
To keep a stable key across parameter changes, give the rule a name:
````csharp
policy.AddRule(rule => rule
.WithName("HourlyLimit")
.WithFixedWindow(TimeSpan.FromHours(1), maxCount: 100)
.PartitionByCurrentUser());
````
When a name is set, it is used as the store key instead of the content-based descriptor. This is particularly useful when rules are managed from a database or UI — changing the `maxCount` or `duration` will not reset existing counters.
> Rule names must be unique within a policy. Duplicate names cause an exception at build time.
## Multi-Tenancy
By default, partition keys do not include tenant information — for partition types like `PartitionByParameter`, `PartitionByCurrentUser`, `PartitionByClientIp`, etc., counters are shared across tenants unless you call `WithMultiTenancy()`. Note that `PartitionByCurrentTenant()` is inherently per-tenant since the partition key is the tenant ID itself, and `PartitionByClientIp()` is typically kept global since the same IP should share a counter regardless of tenant.
@ -664,6 +709,31 @@ Replace `IOperationRateLimitingFormatter` to customize how time durations are di
Replace `IOperationRateLimitingPolicyProvider` to load policies from a database or external configuration source instead of the in-memory options.
When loading pre-built policies from an external source, use the `AddPolicy` overload that accepts an `OperationRateLimitingPolicy` object directly (bypassing the builder):
````csharp
options.AddPolicy(new OperationRateLimitingPolicy
{
Name = "DynamicPolicy",
Rules =
[
new OperationRateLimitingRuleDefinition
{
Name = "HourlyLimit",
Duration = TimeSpan.FromHours(1),
MaxCount = 100,
PartitionType = OperationRateLimitingPartitionType.CurrentUser
}
]
});
````
To remove a policy (e.g., when it is deleted from the database), use `RemovePolicy`:
````csharp
options.RemovePolicy("DynamicPolicy");
````
## See Also
* [ASP.NET Core Rate Limiting Middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit)

Loading…
Cancel
Save