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 IRouter system (via UseRouter) and the modern endpoint routing system (via UseRouting/UseEndpoints).
  • Endpoint routing separates matching (handled by EndpointRoutingMiddleware in src/Http/Routing/src/EndpointRoutingMiddleware.cs) from execution (handled by EndpointMiddleware).
  • Key classes include IEndpointRouteBuilder for collecting endpoints, Matcher for high-performance request evaluation, and RouteEndpoint for representing compiled routing targets.
  • Performance benefits arise from the compiled Matcher data 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →