Routing and Areas

This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2, and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the System.Web-hosted MVC framework, its routing, Razor views, HTML helpers, model binding, filters, and the OWIN-based authentication/Identity stack — as described by the official documentation at Microsoft Learn (plus Web API, Web Pages, SignalR, and Identity), which are the reference these pages are written and verified against.

This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, System.Web-hosted MVC framework; it is functionally frozen and receives only security fixes. For the current, cross-platform MVC framework see MVC Controllers and Views under ASP.NET Core (Blazor).

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page documents ASP.NET MVC 5.3.x routing on .NET Framework 4.8.1 — not ASP.NET Core’s endpoint routing, which replaces RouteCollection with IEndpointRouteBuilder (see Routing under ASP.NET Core).

RouteCollection and RouteTable

RouteTable.Routes is the single, process-wide RouteCollection that UrlRoutingModule matches every request against (see The MVC Pattern and Request Life Cycle). Convention routes are registered with MapRoute, in order, from RouteConfig.RegisterRoutes:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "ProductDetails",
            url: "products/{id}",
            defaults: new { controller = "Products", action = "Details" },
            constraints: new { id = @"\d+" });                       // regex constraint

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
    }
}
  • Defaults (UrlParameter.Optional) make a segment optional; a URL missing that segment still matches.

  • Constraints accept either a regex string per segment or a custom IRouteConstraint.

  • IgnoreRoute marks URLs (e.g. *.axd handler paths) that routing should never intercept.

  • Order matters: RouteCollection tries routes in registration order and stops at the first match — a more specific route (like ProductDetails above) must be registered before a catch-all Default route or it is never reached.

Route matching walks the route table in order and stops at the first match

A custom constraint implements IRouteConstraint.Match for logic a regex cannot express (e.g. checking a database):

public class ActiveProductConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName,
        RouteValueDictionary values, RouteDirection routeDirection)
        => int.TryParse(values[parameterName]?.ToString(), out var id) && ProductStore.IsActive(id);
}

Attribute routing

MapMvcAttributeRoutes() (called once, typically before the convention routes) enables [Route]/[RoutePrefix] on controllers and actions, matched before convention routes in the pipeline regardless of registration position:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapMvcAttributeRoutes();
    routes.MapRoute("Default", "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}

[RoutePrefix("products")]
public class ProductsController : Controller
{
    [Route("")]
    [Route("~/catalog")]                          // "~/" overrides the prefix entirely
    public ActionResult Index() => View();

    [Route("{id:int:min(1)}", Name = "ProductDetails")]   // inline constraint + a named route
    public ActionResult Details(int id) => View();

    [Route("{category}/{page:int=1}")]             // optional segment with a default value
    public ActionResult Browse(string category, int page) => View();
}

Inline constraints (:int, :min(1), :regex(…​), :alpha, …​) live directly in the route template; a Name lets Url.RouteUrl/Html.RouteLink generate URLs from that specific route regardless of which one would otherwise match first. See Attribute Routing in ASP.NET MVC 5.

Generating URLs from route values (rather than hardcoding paths) keeps links in sync with routing changes:

string url = Url.Action("Details", "Products", new { id = 42 });     // "/products/42" or "/products/details/42"
@Html.ActionLink("View product", "Details", "Products", new { id = 42 }, null)
<a href="@Url.Action("Details", "Products", new { id = 42 })">View product</a>

Both resolve through the same RouteCollection.GetVirtualPath, so a route change is reflected everywhere links are generated this way instead of only where URLs happen to be typed literally.

Areas

An area is a self-contained slice of an application — its own Controllers, Views, and (usually) a distinct route namespace — registered through an AreaRegistration:

// Areas/Admin/AdminAreaRegistration.cs
public class AdminAreaRegistration : AreaRegistration
{
    public override string AreaName => "Admin";

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "Admin_default",
            "Admin/{controller}/{action}/{id}",
            new { action = "Index", id = UrlParameter.Optional });
    }
}
/Areas/Admin/Controllers/UsersController.cs
/Areas/Admin/Views/Users/Index.cshtml
/Areas/Admin/Views/web.config

AreaRegistration.RegisterAllAreas(), called from Application_Start before RouteConfig.RegisterRoutes, reflects over the assembly and invokes every AreaRegistration’s `RegisterArea. Ambiguous-controller pitfall: if both /Controllers/UsersController.cs and /Areas/Admin/Controllers/UsersController.cs exist, DefaultControllerFactory can throw "multiple types were found that match the controller named 'Users'" unless the area route’s namespaces constraint (or DataTokens["Namespaces"]) disambiguates which UsersController it means:

context.MapRoute("Admin_default", "Admin/{controller}/{action}/{id}",
    new { action = "Index", id = UrlParameter.Optional },
    namespaces: new[] { "MyApp.Areas.Admin.Controllers" });

Next: Razor Syntax covers how a view actually turns into HTML.