Skip to content

RateLimiting: rename folder, target net10.0, own the rejection handling - #2164

Open
vladimir-pecanac-main wants to merge 1 commit into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/116349-rate-limiting-b8
Open

RateLimiting: rename folder, target net10.0, own the rejection handling#2164
vladimir-pecanac-main wants to merge 1 commit into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/116349-rate-limiting-b8

Conversation

@vladimir-pecanac-main

Copy link
Copy Markdown
Collaborator

Modernises the rate limiting sample and makes the article's central claim demonstrable in code.

Folder rename. aspnetcore-webapi/RateLimitingDotNET8 becomes aspnetcore-webapi/RateLimiting: the solution, both projects and the namespace go with it. The name baked in a version the sample no longer targets, and a version-free name means this never recurs. Exactly one article links this folder, so the rename is a one-article change.

Retarget and packages. Both projects net8.0 to net10.0.

Package Was Now
Swashbuckle.AspNetCore 6.5.0 10.2.3
Microsoft.AspNetCore.Mvc.Testing 8.0.2 10.0.11
Microsoft.NET.Test.Sdk 17.6.0 18.9.0
xunit 2.4.2 2.9.3
xunit.runner.visualstudio 2.4.5 4.0.0
coverlet.collector 6.0.0 10.0.1

Swashbuckle is bumped rather than swapped for Microsoft.AspNetCore.OpenApi: the Swagger UI is how a reader fires requests at the rate-limited endpoints by hand, and MapOpenApi() ships no UI to do that with. The xunit.runner.visualstudio 2.4.5 to 4.0.0 jump does not require moving to xunit.v3; 2.9.3 plus runner 4.0.0 builds and runs clean, which the test output below shows.

Rejection handling gets its own registration. RejectionStatusCode = 429 used to be set inside AuthorizationRateLimiter(), and every other policy in the sample inherited it by side effect, because repeated AddRateLimiter() calls configure the same options instance. The four documented limiters therefore appeared to return 429 while the code the article prints returns the framework default, 503. A new RateLimiters.RejectionHandling() now owns that setting and adds an OnRejected callback that writes Retry-After from MetadataName.RetryAfter.

The class-level attribute is live. [EnableRateLimiting(Policies.Fixed)] on CustomerController was commented out, so the sample could not demonstrate the article's action-overrides-controller claim. It is uncommented, with tests for both halves.

Two smaller fixes. GetOptionValues<T> built a throwaway service provider once per limiter, five times at startup; it now reads configuration directly, the way AuthorizationRateLimiter already did. (Note: ASP0000 is not actually raised on this shape, the build is warning-free either way; the fix stands on the five discarded providers, not on an analyzer.) And the sample's own RateLimiterOptions class is renamed LimiterSettings so it no longer collides by name with Microsoft.AspNetCore.RateLimiting.RateLimiterOptions, which is the type this sample exists to teach.

Deliberately unchanged. The httpContext.GetTokenAsync("access_token").Result call in the authorization partitioner stays: GetTokenAsync does scheme resolution a raw Authorization header read would skip, so the two are not unconditionally equivalent and the swap is not made on reasoning alone. ChainedRateLimiter also stays unregistered; registering it would apply a 4-requests-per-2-seconds global limit partitioned by user agent, and the test client sends a fixed user agent, so every multi-request test would fail. The stub is deliberate.

New tests (5). Controller policy applies to an action without its own attribute; an action attribute overrides the controller's; Retry-After is written on a token bucket rejection; the global limiter is enforced alongside the endpoint limiter rather than instead of it; [DisableRateLimiting] beats the global limiter.

$ dotnet build RateLimiting.sln -c Release
Build succeeded.
    0 Warning(s)
    0 Error(s)

$ dotnet test RateLimiting.sln -c Release --no-build
Passed!  - Failed:     0, Passed:    19, Skipped:     0, Total:    19, Duration: 856 ms - RateLimiting.Tests.dll (net10.0)

SDK 10.0.302, runtime 10.0.10.

…he rejection handling

- aspnetcore-webapi/RateLimitingDotNET8 renamed to aspnetcore-webapi/RateLimiting
  (solution, both projects and the namespace with it) so the folder name stops
  contradicting its target framework.
- Both projects retargeted net8.0 -> net10.0. Swashbuckle.AspNetCore 6.5.0 -> 10.2.3
  (kept, not swapped: the Swagger UI is how a reader fires requests at these endpoints),
  Microsoft.AspNetCore.Mvc.Testing 8.0.2 -> 10.0.11, Microsoft.NET.Test.Sdk 17.6.0 -> 18.9.0,
  xunit 2.4.2 -> 2.9.3, xunit.runner.visualstudio 2.4.5 -> 4.0.0, coverlet.collector 6.0.0 -> 10.0.1.
- RejectionHandling: one AddRateLimiter call now owns RejectionStatusCode = 429 and an
  OnRejected callback that writes Retry-After from MetadataName.RetryAfter. The 429 used to
  be set inside AuthorizationRateLimiter, where every other policy inherited it by side effect.
- The class-level [EnableRateLimiting(Policies.Fixed)] on CustomerController is no longer
  commented out, with tests covering both it and the action-level override.
- GetOptionValues reads configuration directly instead of building a throwaway service
  provider per limiter.
- The sample's own RateLimiterOptions class renamed to LimiterSettings so it stops colliding
  with Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.
- New tests: controller policy applies, action policy overrides it, Retry-After is written,
  the global limiter runs alongside the endpoint limiter, and [DisableRateLimiting] beats it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant