Skip to content

fix(routes): extract ASP.NET Core attribute routes (#1692) - #2688

Open
DeusData wants to merge 5 commits into
mainfrom
fix/issue-1692-aspnet-routes-v2
Open

DeusData wants to merge 5 commits into
mainfrom
fix/issue-1692-aspnet-routes-v2

Conversation

@DeusData

@DeusData DeusData commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

What

ASP.NET Core attribute routing produced no Route nodes, so controller actions had no HANDLES edge and cross-service HTTP calls could not reach them (#1692; the reporter's controller shape in #1041 too).

The C# extractor now reads the attribute routes:

  • verb attributes ([HttpGet("x")], [HttpPost], [HttpPut("x/{id}")], ...) and [Route("...")] on actions and controllers;
  • a relative template joined to the controller prefix;
  • the [controller] / [action] tokens;
  • ~/ overrides of the class prefix.

It is gated on C# (aspnet_action_route) and only fires on controller classes, with a non-controller control test.

A second commit keeps both resolvers in agreement. A GET form action and its POST submit share one method name, so both overloads collapse into one Method node. The parallel resolver emitted each definition's route when it created the node and kept both. The sequential one rebuilt routes afterwards from the merged node properties and kept only the last. Both now call cbm_pipeline_emit_def_route() per definition (the parallel block moved into it unchanged).

Evidence

Corpus run, 8 ASP.NET repos (jellyfin, bitwarden/server, dotnet/eShop, eShopOnWeb, nopCommerce, healthcare-ai-model-evaluator, CleanArchitecture, OrchardCore), against current main (354ccec):

venue HANDLES DATA_FLOWS other edge types
parallel 30 -> 2,483 (+2,453 / -0) +10 unchanged*
sequential 30 -> 2,483 (+2,453 / -0) +10 unchanged*
  • Parallel and sequential agree. Before the per-definition fix, sequential had 2,455.
  • The 10 DATA_FLOWS are healthcare-ai's frontend dataSetService.* calls, now linked to the matching DataSetsController actions.
  • CleanArchitecture is unchanged; it uses minimal APIs, not attribute routes.
  • Base run-to-run noise on this set is 0.
  • * SIMILAR_TO -34 / SEMANTICALLY_RELATED churn came from a separate, existing defect: the properties buffer dropped the MinHash fingerprint on long-docstring actions. That is fixed by fix(pipeline): size definition properties for every field #2687, which lands first.

Tests (edge_types_probe):

  • handles_aspnet_attribute_routes_issue1692: the reporter's shape.
  • handles_aspnet_attribute_routes_issue1041.
  • handles_aspnet_route_tokens_issue1692.
  • handles_aspnet_override_constraints_issue1692.
  • handles_aspnet_non_controller_control_issue1692.
  • handles_aspnet_overload_verbs_both_venues_issue1692: fails without the fix on the sequential path (1 of 2 HANDLES, the GET route missing) and passes with it on both venues.

Suites run locally: edge_types_probe, cross_repo, route_canon, parallel, pipeline, httpd: 661 passed (1 Windows-only platform skip). cppcheck 2.20 (the CI version) and clang-format are clean on the changed files.

Fixes #1692
Refs #1041 (attribute-route extraction for the reporter's shape; the case-insensitive matching follows separately)

A controller routed with ASP.NET Core attributes produced no Route node
and no HANDLES edge:

  [Route("api/[controller]")] class QuotesController
    [HttpGet("{symbol}")]                        -> nothing
    [HttpPost] + [Route("orders/{id}/confirm")]  -> nothing

Root cause: the route extractor only knew decorator calls (Flask, FastAPI,
Express-style) and Spring/JAX-RS annotation names. HttpGet/HttpPost/.../
Route matched none of them, the class-prefix join ran only for Java and
Kotlin, and ASP.NET templates are relative ("{symbol}", "api/[controller]"),
while the path filter and ensure_one_decorator_route accept only
'/'-prefixed paths. #1759 made every stacked C# attribute visible as
DECORATES but did not turn any of them into a route.

Fix: a C# path in push_method_def, next to the JVM prefix join, that
reads the attributes of the action and of its class:
- HttpGet/Post/Put/Delete/Patch/Head/Options with or without a template,
  [Route] on the class and on the action, [AcceptVerbs("GET", Route = "x")];
  qualified names and the "Attribute" suffix are accepted.
- [controller] is the class name minus "Controller", [action] the method
  name minus "Async" (the ASP.NET Core default) or its [ActionName], and
  [area] comes from [Area].
- A template is relative to the class prefix; "/" or "~/" overrides it.
- Constraints, defaults and optional markers reduce to the bare parameter
  ("{id:int}", "{code:regex(^\d{{3}}$)}", "{page=1}" -> "{id}", "{code}",
  "{page}"), so the path is '/'-led and canonicalizes like every other
  decorator route. This works with both the current cbm_route_canon_path
  and the depth-matched variant.
An action without a verb or [Route] attribute stays route-less, and other
C# attributes never become routes.

Known limits, unchanged in kind from the JVM path: one route per action
(a stacked [HttpGet] + [HttpHead] records the first), and a [Route]
inherited from a base controller in another file is not applied.

Proof on jellyfin/jellyfin (shallow clone, depth 1, 208c278): Route
nodes 0 -> 360, HANDLES 0 -> 388, e.g.
GET /Items/{itemId}/Images/{imageType} <- ImageController.GetItemImage.

Tests (edge_types_probe, exact Route-name sets): the #1692 controller,
the #1041 AuthController, [controller]/[action]/[ActionName]/[AcceptVerbs]
tokens, "/" and "~/" overrides with constraints plus a controller without
a class [Route], and a non-controller C# control.

Refs #1692
Refs #1041

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…-routes-v2

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…1692)

An ASP.NET MVC GET form action and its POST submit share one method name,
so both overloads collapse into one Method node whose merged properties
keep only one route. The parallel resolver emitted each definition's route
at node creation and kept both; the sequential one rebuilt decorator routes
from the merged properties afterwards and kept only the last. On six
ASP.NET corpora the sequential run lost 28 HANDLES edges this way.

Both resolvers now call cbm_pipeline_emit_def_route() per definition at
node creation (the parallel block moved into it unchanged), so the venues
agree. The later decorator-route pass still skips handlers it already has.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…-routes-v2

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…-routes-v2

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant