class

Analyzer::CSharp::AspNetCoreMvc

Inherits Analyzer::CSharp::Common < Analyzer < FileHelper < Reference < Object

Constants

ACCEPT_VERB_TOKENS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "QUERY"] of ::String

ASP.NET Core routing has always accepted an arbitrary method string through [AcceptVerbs(...)]; QUERY (RFC 10008) ships natively as HttpMethods.Query starting with .NET 10.

ACTION_PLACEHOLDER_RE = /\{action[=:?}]/
AREA_PLACEHOLDER_RE = /\{area[=:?}]/
AREA_TOKEN_RE = /\{area(?:[:=][^}]*)?\}/
ATTRIBUTE_CLASS_RE = /\bclass\s+\w+Attribute\b/
CONTROLLER_DECLARATION_RE = /\bclass\s+\w*Controller\b|[\[,]\s*(?:global::)?(?:\w+\.)*(?:Api)?Controller(?:Attribute)?\s*[\]\(,]|:\s*(?:global::)?(?:\w+\.)*Controller(?:Base)?\b/
CONTROLLER_MARKER = "Controller"

Discovery reads the whole solution, so the gate in front of CSharpLexer decides what the analyzer costs: bitwarden/server ships 5,512 .cs files and 277 controllers, and lexing all of them is a 5-6x scan.

Every intrinsic controller marker is visible in the declaring source — the *Controller name suffix, [Controller]/[ApiController], and the Controller/ControllerBase framework bases. Those files are lexed up front. A type can still inherit a marker from a local base (class Users : BaseApiEndpoint), so the files that name one of the marked types are pulled in afterwards, and those that never spell Controller at all only have to be checked against marked names that do not spell it either — in practice none, so the bulk of a solution is never read past the substring test.

CONTROLLER_MARKER_RE = /Controller/
CONTROLLER_PLACEHOLDER_RE = /\{controller[=:?}]/
CONTROLLER_SUFFIX_RE = /Controller$/i
CONVENTION_ADD_RE = /\bConventions\s*\.\s*(?:Add|Insert)\s*\(\s*(?:\d+\s*,\s*)?new\s+(\w+)\s*\(\s*(?:new\s+(?:\w+\.)*RouteAttribute\s*\(\s*)?(?:@?"([^"]*)")?/
CONVENTION_CLASS_RE = /\bclass\s+(\w+)[^{;]*\bIApplicationModelConvention\b/

An IApplicationModelConvention that combines a constant RouteAttribute with every controller selector, registered with Conventions.Add(new ApiRoutePrefixConvention("api")): its prefix leads every attribute route of the project. The template is the class's own new RouteAttribute("api") literal, else the registration's first argument ("api" or new RouteAttribute("api")).

DEFAULT_AREA_ROUTE = "{area:exists}/{controller=Home}/{action=Index}/{id?}"

The area route every MVC template ships: what serves an [Area] controller when the scan shows no route that reaches its area.

DEFAULT_PATTERNS = [ConventionalRoute.new(DEFAULT_ROUTE), ConventionalRoute.new(DEFAULT_AREA_ROUTE)]
DEFAULT_ROUTE = "{controller=Home}/{action=Index}/{id?}"
DERIVED_CLASS_RE = /\bclass\s+\w+[^{};:]*:/

Only a class with a base list can inherit a marker it does not declare.

FRAMEWORK_CONTROLLER_BASES = {"Controller", "ControllerBase"}
FROM_BINDINGS = {"FromQuery" => "query", "FromRoute" => "path", "FromBody" => "json", "FromHeader" => "header", "FromForm" => "form", "FromCookie" => "cookie", "FromServices" => "service", "FromKeyedServices" => "service"}

The request source each [FromX] attribute binds a parameter from.

HTTP_METHODS_ARG_RE = /\A(?:\w+\.)*HttpMethods\s*\.\s*([A-Za-z]+)\z/
IDENTIFIER_PATH_RE = /\A[A-Za-z_][\w.]*\z/
PLACEHOLDER_DEFAULT_RE = /\{([^{}=]+)=[^{}]*\}/
ROUTE_ATTRIBUTE_NEW_RE = /\bnew\s+(?:\w+\.)*RouteAttribute\s*\(\s*(?:@?"([^"]*)")?/
ROUTE_CALL_RE = /\bMap(Area)?ControllerRoute\s*\(|\bMapDefaultControllerRoute\s*\(/
TAGGER = "aspnet_core_mvc_analyzer"
UNREAD_ROUTE_ARG_RE = /\A[A-Za-z_]/

A route argument that is a name (ApiRoutes.Item, Const + "/x"): the route exists but cannot be read here, so the action is reported at the class route with an unresolved-route tag.

UNRESOLVED_ROUTE_DESCRIPTION = "Route template is a constant the analyzer cannot read; reported at the controller route"
USING_DIRECTIVE_RE = /^\s*using\s+([\w.]+)\s*;/m
VERB_ATTRIBUTES = {"HttpGet" => "GET", "HttpPost" => "POST", "HttpPut" => "PUT", "HttpDelete" => "DELETE", "HttpPatch" => "PATCH", "HttpHead" => "HEAD", "HttpOptions" => "OPTIONS"}

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source

Nested types