ASP.NET Routing

ASP.NET routing enables you to use URLs that do not have to map to specific files in a Web site. Because the URL does not have to map to a file, you can use URLs in a Web application that are descriptive of the user's action and therefore more easily understood by users.

In an ASP.NET application that does not use routing, an incoming request for a URL typically maps to a physical file on disk such as an .aspx file. For example, a request for https://server/application/Products.aspx?id=4 maps to a file that is named Products.aspx that contains code and markup for rendering a response to the browser. The Web page uses the query string value of id=4 to determine what type of content to display, but the value is likely to have little meaning to the user.

In ASP.NET routing, you define URL patterns that contain placeholders for values that are used when you handle URL requests. At run time, the pieces of the URL that follow the application name are parsed into discrete values, based on a URL pattern that you have defined. For example, in the request for https://server/application/Products/show/beverages, the routing parser can pass the values Products, show, and beverages to a handler for the request. In contrast, in a request that is not managed by URL routing, the /Products/show/beverages fragment would be interpreted as the path of a file in the application.

You can also use the URL patterns to programmatically create URLs that correspond to the routes. This enables you to centralize the logic for creating hyperlinks in your ASP.NET application.

ASP.NET Routing versus URL Rewriting

ASP.NET routing differs from other URL rewriting schemes. URL rewriting processes incoming requests by actually changing the URL before it sends the request to the Web page. For example, an application that uses URL rewriting might change a URL from /Products/Widgets/ to /Products.aspx?id=4. Also, URL rewriting typically does not have an API for creating URLs that are based on your patterns. In URL rewriting, if you change a URL pattern, you must manually update all hyperlinks that contain the original URL.

With ASP.NET routing, the URL is not changed when an incoming request is handled, because routing can extract values from the URL. When you have to create a URL, you pass parameter values into a method that generates the URL for you. To change the URL pattern, you change it in one location, and all the links that you create in the application that are based on that pattern will automatically use the new pattern.

Defining URL Routes

The URL patterns that you define are known as routes. In a route, you specify placeholders that are mapped to values that are parsed from the URL request. You can also specify constant values that are used for matching URL requests.

In a route, you define placeholders (referred to as URL parameters) by enclosing them in braces ( { and } ). The / character is interpreted as a delimiter when the URL is parsed. Information in the route definition that is not a delimiter and that is not in braces is treated as a constant value. Values that are extracted from between the delimiters are assigned to placeholders.

You can define more than one placeholder between delimiters, but they must be separated by a constant value. For example, {language}-{country}/{action} is a valid route pattern. However, {language}{country}/{action} is not a valid pattern, because there is no constant or delimiter between the placeholders. Therefore, routing cannot determine where to separate the value for the language placeholder from the value for the country placeholder.

The following table shows valid route patterns and examples of URL requests that match the patterns.

Route definition

Example of matching URL

{controller}/{action}/{id}

/Products/show/beverages

{table}/Details.aspx

/Products/Details.aspx

blog/{action}/{entry}

/blog/show/123

{reporttype}/{year}/{month}/{day}

/sales/2008/1/5

{locale}/{action}

/en-US/show

{language}-{country}/{action}

/en-US/show

Typically, you add routes in a method that is called from the handler for the Application_Start event in the Global.asax file. This approach makes sure that the routes are available when the application starts. It also enables you to call the method directly when you unit-test the application. If you want to call a method directly when you are unit-testing the application, the method that registers the routes must be static (Shared in Visual Basic) and must have a RouteCollection parameter.

You add routes by adding them to the static Routes property of the RouteTable class. The Routes property is aRouteCollection object that stores all the routes for the ASP.NET application. The following example shows code from a Global.asax file that adds a Route object that defines two URL parameters named action and categoryName. In the example, CategoryRouteHandler is a custom route handler. For information about how to create a custom route handler, see How to: Use Routing with Web Forms.

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    RegisterRoutes(RouteTable.Routes)
End Sub

Shared Sub RegisterRoutes(routes As RouteCollection)
    Dim urlPattern As String
    Dim categoryRoute As Route

    urlPattern = "Category/{action}/{categoryName}"
    categoryRoute = New Route(urlPattern, New CategoryRouteHandler)

    routes.Add(categoryRoute)
End Sub
protected void Application_Start(object sender, EventArgs e)
{
    RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Add(new Route
    (
         "Category/{action}/{categoryName}"
         , new CategoryRouteHandler()
    ));
}

Setting Default Values for Route Parameters

When you define a route, you can assign a default value for a parameter. The default value is used if a value for that parameter is not included in the URL. You set default values for a route by assigning a dictionary to the Defaults property of the Route class. The following example shows a route with default values.

Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    RegisterRoutes(RouteTable.Routes)
End Sub

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    Dim urlPattern As String
    Dim categoryRoute As Route

    urlPattern = "Category/{action}/{categoryName}"
    categoryRoute = New Route(urlPattern, New CategoryRouteHandler)
    categoryRoute.Defaults = New RouteValueDictionary(New With _
        {.categoryName = "food", _
         .action = "show"} )

    routes.Add(categoryRoute)
End Sub
void Application_Start(object sender, EventArgs e) 
{
    RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
  routes.Add(new Route
  (
     "Category/{action}/{categoryName}"
          new CategoryRouteHandler()
  )
    {
       Defaults = new RouteValueDictionary 
           {{"categoryName", "food"}, {"action", "show"}}
     }
  )
}

When ASP.NET routing handles a URL request, the route definition shown in the example (with default values of food for categoryName and show for action) produces the results that are listed in the following table.

URL

Parameter values

/Category

action = "show" (default value)

categoryName = "food" (default value)

/Category/add

action = "add"

categoryName = "food" (default value)

/Category/add/beverages

action = "add"

categoryName= "beverages"

Handling a Variable Number of Segments

Sometimes you have to handle URL requests that contain a variable number of URL segments. When you define a route, you can specify that the last parameter should match the rest of the URL by marking the parameter with an asterisk (*). This is then referred to as a catch-all parameter. A route with a catch-all parameter will also match URLs that do not contain any values for the last parameter. The following example shows a route pattern that matches an unknown number of segments.

query/{queryname}/{*queryvalues}

When ASP.NET routing handles a URL request, the route definition shown in the example produces the results that are listed in the following table.

URL

Parameter values

/query/select/bikes/onsale

queryname = "select"

queryvalues = "bikes/onsale"

/query/select/bikes

queryname = "select"

queryvalues = "bikes"

/query/select

queryname = "select"

queryvalues = Empty string

Adding Constraints to Routes

In addition to matching a URL request to a route definition by the number of parameters in the URL, you can specify that values in the parameters meet certain constraints. If a URL contains values that are outside the constraints for a route, that route is not used to handle the request. You add constraints to make sure that the URL parameters contain values that will work in your application.

Constraints are defined by using regular expressions or by using objects that implement the IRouteConstraint interface. When you add the route definition to the Routes collection, you add constraints by creating a RouteValueDictionary object that contains the verification test. You then assign this object to the Constraints property. The key in the dictionary identifies the parameter that the constraint applies to. The value in the dictionary can be either a string that represents a regular expression or an object that implements the IRouteConstraint interface.

If you provide a string, routing treats the string as a regular expression and checks whether the parameter value is valid by calling the IsMatch method of the Regex class. The regular expression is always treated as case-insensitive. For more information, see .NET Framework Regular Expressions.

If you provide an IRouteConstraint object, ASP.NET routing checks whether the parameter value is valid by calling the Match method of the IRouteConstraint object. The Match method returns a Boolean value that indicates whether the parameter value is valid.

The following example shows constraints that limit what values can be included in the locale and year parameters.

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    Dim urlPattern As String
    Dim reportRoute As Route

    urlPattern = "{locale}/{year}"
    reportRoute = New Route(urlPattern, New ReportRouteHandler)
    reportRoute.Constraints = New RouteValueDictionary(New With _
        {.locale = "[a-z]{2}-[a-z]{2}", .year = "\d{4}"})

    routes.Add(reportRoute) 
End Sub
void Application_Start(object sender, EventArgs e) 
{
    RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Add(new Route
    (
      "{locale}/{year}"
         , new ReportRouteHandler()
    )
       {
          Constraints = new RouteValueDictionary 
          {{"locale", "[a-z]{2}-[a-z]{2}"},{year, @"\d{4}"}}
       });
}

When routing handles a URL request, the route definition shown in the previous example produces the results that are listed in the following table.

URL

Result

/en-US

No match. Both locale and year are required.

/en-US/08

No match. The constraint on year requires 4 digits.

/en-US/2008

locale = "en-US"

year = "2008"

Scenarios When Routing Is Not Applied

By default, routing does not handle requests that map to an existing physical file on the Web server. For example, a request for https://server/application/Products/Beverages/Coffee.aspx is not handled by routing if a physical file exists at Products/Beverages/Coffee.aspx. Routing does not handle the request even if it matches a defined pattern, such as {controller}/{action}/{id}.

If you want routing to handle all requests, even requests that point to files, you can overwrite the default behavior by setting the RouteExistingFiles property of the RouteCollection object to true. When you set this value to true, all requests that match a defined pattern are handled by routing.

You can also specify that routing should not handle certain URL requests. You prevent routing from handling certain requests by defining a route and specifying that the StopRoutingHandler class should be used to handle that pattern. When a request is handled by a StopRoutingHandler object, the StopRoutingHandler object blocks any additional processing of the request as a route. Instead, the request is processed as an ASP.NET page, Web service, or other ASP.NET endpoint. For example, you can add the following route definition to prevent routing from handling requests for the WebResource.axd file.

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.Add(New Route("{resource}.axd/{*pathInfo}", New StopRoutingHandler()))
End Sub
public static void RegisterRoutes(RouteCollection routes)
{
  routes.Add(new Route("{resource}.axd/{*pathInfo}", new StopRoutingHandler()));
}

How URLs Are Matched to Routes

When routing handles URL requests, it tries to match the URL of the request to a route. Matching a URL request to a route depends on all the following conditions:

  • The route patterns that you have defined or the default route patterns, if any, that are included in your project type.

  • The order in which you added them to the Routes collection.

  • Any default values that you have provided for a route.

  • Any constraints that you have provided for a route.

  • Whether you have defined routing to handle requests that match a physical file.

To avoid having the wrong handler handle a request, you must consider all these conditions when you define routes. The order in which Route objects appear in the Routes collection is significant. Route matching is tried from the first route to the last route in the collection. When a match occurs, no more routes are evaluated. In general, add routes to the Routes property in order from the most specific route definitions to least specific ones.

For example, suppose that you add routes with the following patterns:

  • Route 1: {controller}/{action}/{id}

  • Route 2: products/show/{id}

Route 2 will never handle a request because Route 1 is evaluated first, and it will always match requests that could also work for Route 2. A request for https://server/application/products/show/bikes seems to match Route 2 more closely, but it is handled by Route 1 with the following values:

  • controller = products

  • action = show

  • id = bikes

Default values are used if a parameter is missing from the request. Therefore, they can cause a route to match a request that you did not expect. For example, suppose that you add routes with the following patterns:

  • Route 1: {report}/{year}/{month}, with default values for year and month.

  • Route 2: {report}/{year}, with default value for year.

Route 2 will never handle a request. Route 1 might be intended for a monthly report, and Route 2 might be intended for an annual report. However, the default values in Route 1 mean that it will match any request that could also work for Route 2.

You can avoid ambiguity in the patterns by including constants, such as annual/{report}/{year} and monthly/{report}/{year}/{month}.

If a URL does not match any Route object defined in the RouteTable collection, ASP.NET routing does not process the request. Instead, processing is passed to an ASP.NET page, Web service, or other ASP.NET endpoint.

Creating URLs from Routes

You can use routes to generate URLs when you want to centralize the logic for constructing URLs. You create a URL by passing parameter values as a dictionary to the GetVirtualPath method of the RouteCollection object. The GetVirtualPath method looks for the first route in the RouteCollection object that matches the parameters in the dictionary. The matching route is used to generate the URL. The following example shows a route definition. In the example, CategoryRouteHandler is a custom route handler. For information about how to create a custom route handler, see How to: Use Routing with Web Forms.

Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
    routes.Add(New Route( _
      "Category/{action}/{categoryName}", _
      New RouteValueDictionary(New With _
          {.categoryName = "food", _
           .action = "show"}), _
           New CategoryRouteHandler()) )
End Sub
public static void RegisterRoutes(RouteCollection routes)
{
  routes.Add(new Route
  (
     "Category/{action}/{categoryName}"
          new CategoryRouteHandler()
  )
    {
       Defaults = new RouteValueDictionary {{"categoryName", "food"}, 
           {"action", "show"}}
     }
  );
}

The following example shows a control that creates a URL based on the route.

Dim urlParameters As RouteValueDictionary

urlParameters = New RouteValueDictionary(New With {.categoryName = "beverages", _
        .action = "summarize"})
HyperLink1.href = RouteTable.Routes.GetVirtualPath _
    (context, urlParameters).VirtualPath
HyperLink1.href = RouteTable.Routes.GetVirtualPath
  (context,
  new RouteValueDictionary { 
    { "categoryName", "beverages" }, 
    {"action", "summarize" }}
  ).VirtualPath;

When this code runs, the HyperLink1 control will contain the value "Category/summarize/beverages" in the href property.

When you create a URL from a route, you can specify which route to use by including a name for the route. For more information, see How to: Construct a URL from a Route.

See Also

Tasks

How to: Use Routing with Web Forms

Concepts

Understanding the ASP.NET Infrastructure

Change History

Date

History

Reason

October 2009

Fixed syntax in a code example.

Customer feedback.