How ASP.NET Core Handles Routing: From IRouter to Endpoint Routing
ASP.NET Core routing matches HTTP requests to endpoints through a middleware pipeline that evolved from the legacy IRouter model to the modern endpoint routing system introduced in version 3.0.
The routing system in the dotnet/aspnetcore repository directs incoming HTTP requests to appropriate handlers using a sophisticated middleware architecture. Understanding how ASP.NET Core routing works internally reveals why the framework maintains two distinct models and how the current endpoint routing system achieves higher performance.
Classic Routing vs. Endpoint Routing
The framework supports two routing architectures that coexist for backward compatibility. The modern endpoint routing system is recommended for new applications, while the legacy IRouter-based system remains available for older codebases.
Legacy IRouter-Based Routing
The original routing model relies on the IRouter interface and the RouterMiddleware. When you call UseRouter in src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs (lines 20-35), the middleware pipeline adds RouterMiddleware to the request chain.
RouterMiddleware.Invoke (found in src/Http/Routing/src/RouterMiddleware.cs lines 40-53) creates a RouteContext, sets the provided IRouter, and invokes IRouter.RouteAsync. If the router finds no matching handler, the request flows to the next middleware in the pipeline.
Modern Endpoint Routing (ASP.NET Core 3.0+)
The current architecture uses UseRouting() and UseEndpoints() extension methods defined in src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs. This model separates route matching from endpoint execution, enabling middleware to inspect the selected endpoint before the final handler runs.
The Endpoint Routing Pipeline
Modern ASP.NET Core routing operates in two distinct phases: matching and execution.
Matching Requests with EndpointRoutingMiddleware
The EndpointRoutingMiddleware (implemented in src/Http/Routing/src/EndpointRoutingMiddleware.cs) handles the matching phase. When Invoke executes (lines 99-124), it checks whether an endpoint has already been set on the HttpContext. If not, it asynchronously matches the request against a composite EndpointDataSource containing all registered endpoints.
The middleware uses a Matcher created by MatcherFactory to evaluate the request against the endpoint collection. When a match is found, the selected endpoint is stored via HttpContext.SetEndpoint, and the request proceeds to subsequent middleware.
The SetRoutingAndContinue method (lines 140-176) handles the transition between matching and execution, including short-circuiting logic for scenarios like request size limits or diagnostic endpoints.
Executing Endpoints with EndpointMiddleware
The UseEndpoints method adds EndpointMiddleware to the pipeline. This middleware executes the delegate attached to the matched endpoint stored in HttpContext. Because the matching already occurred in EndpointRoutingMiddleware, this phase focuses solely on running the endpoint logic with any associated metadata (authorization, CORS, etc.).
Core Routing Components
Several key abstractions power the routing system:
IEndpointRouteBuilder – collects Endpoint objects through methods like MapGet, MapPost, and MapControllerRoute (defined in src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs). These methods create RouteEndpoint instances that combine URL patterns with request delegates.
Endpoint – represents a compiled routing target containing the request delegate, routing metadata, and additional metadata such as authorization policies or CORS requirements. The RouteEndpoint class in src/Http/Routing/src/RouteEndpoint.cs provides the concrete implementation.
Matcher – a compiled, high-performance data structure that evaluates HTTP requests against the endpoint collection. The framework builds this matcher from the composite EndpointDataSource (defined in src/Http/Routing/src/EndpointDataSource.cs) during application startup.
RouteOptions – configures routing behavior including URL case sensitivity, trailing slash handling, and fallback routes via the options pattern.
Implementation Examples
The following examples demonstrate both routing approaches:
// Classic routing (IRouter) – rarely needed today
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouter(new RouteBuilder(app)
{
// Configure routes manually
routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}
});
// Modern endpoint routing (recommended)
var app = WebApplication.CreateBuilder(args).Build();
app.UseRouting(); // Adds EndpointRoutingMiddleware
app.UseEndpoints(endpoints =>
{
// Minimal API endpoint
endpoints.MapGet("/", () => "Hello, world!");
// MVC controller endpoints
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.Run();
Summary
- ASP.NET Core routing uses a middleware pipeline that matches requests to endpoints before executing them.
- Two models coexist: the legacy
IRoutersystem (viaUseRouter) and the modern endpoint routing system (viaUseRouting/UseEndpoints). - Endpoint routing separates matching (handled by
EndpointRoutingMiddlewareinsrc/Http/Routing/src/EndpointRoutingMiddleware.cs) from execution (handled byEndpointMiddleware). - Key classes include
IEndpointRouteBuilderfor collecting endpoints,Matcherfor high-performance request evaluation, andRouteEndpointfor representing compiled routing targets. - Performance benefits arise from the compiled
Matcherdata structure and the ability to inspect endpoints earlier in the middleware pipeline.
Frequently Asked Questions
What is the difference between UseRouter and UseRouting?
UseRouter (from RoutingBuilderExtensions.cs) enables the classic IRouter-based routing where a single router object handles the entire matching process. UseRouting (from EndpointRoutingApplicationBuilderExtensions.cs) adds the modern EndpointRoutingMiddleware that uses a compiled Matcher against a composite data source, supporting endpoint inspection by earlier middleware.
How does endpoint routing improve performance?
Endpoint routing builds a compiled Matcher data structure from the EndpointDataSource during startup rather than evaluating routes sequentially at runtime. This structure enables O(1) or O(log n) lookup times for most route patterns, and it allows middleware to access endpoint metadata (like authorization requirements) before the endpoint executes.
Can I mix classic and endpoint routing in the same application?
While technically possible, mixing both models is not recommended. The framework maintains the IRouter API primarily for backward compatibility with legacy MVC and Web API code. New applications should use endpoint routing exclusively to avoid confusion and potential routing conflicts.
Where are routes defined in ASP.NET Core?
Routes are defined through IEndpointRouteBuilder extension methods in EndpointRouteBuilderExtensions.cs. Common methods include MapGet, MapPost, MapControllerRoute, and MapRazorPages. These methods create Endpoint objects that get aggregated into the EndpointDataSource used by the Matcher when EndpointRoutingMiddleware processes requests.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →