diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Controllers/RateLimitDemoController.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Controllers/RateLimitDemoController.cs
index 271b5b6af7..60f7b8c01a 100644
--- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Controllers/RateLimitDemoController.cs
+++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Controllers/RateLimitDemoController.cs
@@ -198,6 +198,52 @@ public class RateLimitDemoController : AbpController
return Ok(new { success = true, message = $"Tenant-isolated operation completed for key: {input.Key}" });
}
+ ///
+ /// Demo 11: Authenticated - PartitionByCurrentTenant (all users in a tenant share one counter)
+ ///
+ [HttpPost("tenant-wide")]
+ [Authorize]
+ public async Task TenantWide()
+ {
+ await _checker.CheckAsync("Demo_TenantWide");
+
+ return Ok(new { success = true, message = "Tenant-wide operation completed" });
+ }
+
+ ///
+ /// Demo 12: Ban policy (maxCount: 0) - always blocked
+ ///
+ [HttpPost("ban-policy")]
+ [AllowAnonymous]
+ public async Task BanPolicy([FromBody] BanPolicyInput input)
+ {
+ await _checker.CheckAsync("Demo_BanPolicy", new OperationRateLimitingContext
+ {
+ Parameter = input.Key
+ });
+
+ return Ok(new { success = true, message = $"Operation completed for key: {input.Key}" });
+ }
+
+ ///
+ /// Demo 13: IsAllowedAsync - read-only pre-check (does NOT increment counter)
+ ///
+ [HttpPost("pre-check")]
+ [AllowAnonymous]
+ public async Task PreCheck([FromBody] PreCheckInput input)
+ {
+ var context = new OperationRateLimitingContext { Parameter = input.Key };
+
+ if (input.ReadOnly)
+ {
+ var isAllowed = await _checker.IsAllowedAsync("Demo_PreCheck", context);
+ return Ok(new { success = true, message = $"IsAllowedAsync (read-only): {isAllowed}. Counter was NOT incremented." });
+ }
+
+ await _checker.CheckAsync("Demo_PreCheck", context);
+ return Ok(new { success = true, message = "CheckAsync: Counter incremented." });
+ }
+
///
/// Get status without consuming quota
///
@@ -243,7 +289,9 @@ public class RateLimitDemoController : AbpController
[FromQuery] string? compositeParamKey = null,
[FromQuery] string? compositeTripleKey = null,
[FromQuery] string? customMultiKeyResourceId = null,
- [FromQuery] string? tenantIsolatedKey = null)
+ [FromQuery] string? tenantIsolatedKey = null,
+ [FromQuery] string? banPolicyKey = null,
+ [FromQuery] string? preCheckKey = null)
{
var policies = new[]
{
@@ -259,6 +307,9 @@ public class RateLimitDemoController : AbpController
("Demo_LongDays", (string?)null),
("Demo_CustomMultiKey", customMultiKeyResourceId),
("Demo_TenantIsolated", tenantIsolatedKey),
+ ("Demo_TenantWide", (string?)null),
+ ("Demo_BanPolicy", banPolicyKey),
+ ("Demo_PreCheck", preCheckKey),
};
foreach (var (policyName, parameter) in policies)
@@ -317,3 +368,14 @@ public class DemoTenantIsolatedInput
{
public string Key { get; set; } = default!;
}
+
+public class BanPolicyInput
+{
+ public string Key { get; set; } = default!;
+}
+
+public class PreCheckInput
+{
+ public string Key { get; set; } = default!;
+ public bool ReadOnly { get; set; }
+}
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
index ea5ebf3e10..17becbed7e 100644
--- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
+++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
@@ -226,7 +227,7 @@ public class MyProjectNameWebModule : AbpModule
.PartitionBy(ctx =>
{
var userId = ctx.GetRequiredService().Id?.ToString() ?? "anonymous";
- return $"{ctx.Parameter}:{userId}";
+ return Task.FromResult($"{ctx.Parameter}:{userId}");
});
});
@@ -238,6 +239,28 @@ public class MyProjectNameWebModule : AbpModule
.WithMultiTenancy()
.PartitionByParameter();
});
+
+ // Demo 11: Authenticated - PartitionByCurrentTenant
+ // All users within the same tenant share one counter.
+ options.AddPolicy("Demo_TenantWide", policy =>
+ {
+ policy.WithFixedWindow(TimeSpan.FromSeconds(20), maxCount: 3)
+ .PartitionByCurrentTenant();
+ });
+
+ // Demo 12: Ban policy (maxCount: 0) - blocks all requests for the duration
+ options.AddPolicy("Demo_BanPolicy", policy =>
+ {
+ policy.WithFixedWindow(TimeSpan.FromSeconds(30), maxCount: 0)
+ .PartitionByParameter();
+ });
+
+ // Demo 13: IsAllowedAsync - read-only check without incrementing counter
+ options.AddPolicy("Demo_PreCheck", policy =>
+ {
+ policy.WithFixedWindow(TimeSpan.FromSeconds(20), maxCount: 3)
+ .PartitionByParameter();
+ });
});
}
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/RateLimitDemo/Index.cshtml b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/RateLimitDemo/Index.cshtml
index c86eff0f4f..ee82fe4c2a 100644
--- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/RateLimitDemo/Index.cshtml
+++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/Pages/RateLimitDemo/Index.cshtml
@@ -405,6 +405,90 @@
+
+
+
+
+ 11. Tenant-Wide Limit
+ Auth
+
+
+
+ Partition:PartitionByCurrentTenant
+ Rule: 3 requests per tenant within 20 seconds
+ Note: All users within the same tenant share a single counter. Different tenants have independent counters. Uses "host" as the key on the host side.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12. Ban Policy (maxCount: 0)
+ Public
+
+
+
+ Partition:PartitionByParameter
+ Rule:maxCount: 0 within 30 seconds
+ Note: Setting maxCount to 0 creates a ban policy — every request is blocked for the entire window duration. Useful for temporarily banning a user or resource.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 13. IsAllowedAsync (Pre-Check)
+ Public
+
+
+
+ Partition:PartitionByParameter
+ Rule: 3 requests per key within 20 seconds
+ Note: Demonstrates the difference between IsAllowedAsync (read-only, does not increment counter) and CheckAsync (increments counter). Click "Pre-Check" repeatedly — it never exhausts the quota. Click "Consume" to actually increment.
+ How to test: Click "Pre-Check" many times (always OK) → Click "Consume" 3 times → Click "Pre-Check" again (now returns false).
+