# Routing with Razor Pages in ASP.NET Core

Routing is the process of mapping an incoming request to a handler (Razor Page, or an action on some MVC controller).

Routing uses URL's path to decide on the handler. It doesn't use query parameters. These are bound in the handler to parameters/properties.

RoutingMiddleware is added to the app with:

app.UseRouing();

TIP

Routing is not case-sensitive

This middleware is responsible for selecting the endpoint for a requests. Endpoints can be either MVC or Razor Pages (or some middleware). All of them need to be registered with the EndpointMiddleware. That middleware has a dictionary of all registered endpoints, and it is shared with the RoutingMiddleware. Routing middleware looks through the dictionary for every requests and makes a "note" about the selection in the HttpContext object. EndpointMiddleware looks at that "note" and executes the selected endpoint handler.

Separation of routing and execution

The process of selecting an endpoint and executing it are separated into different middleware components. Thanks to it, we can add some other middleware between those. That middleware has access to the information which endpoint handler was selected by the routing middleware. Example of that is the AuthorizationMiddleware, which can do some authorization based on the selected endpoint.

# Configuration

The routing middleware comes with a set of conventions, which may be changed. It can be done as follows:

services.Configure<RouteOptions>(options => 
{
  options.AppendTrailingSlash = true,
  options.LowercaseUrls = true //by default, URLs are capitalized
  options.LowercaseQueryStrings = true
});

# Route Templates

When defining routes, the template syntax is used making it possible to have some parts of the URLs dynamic.

Binding

Placeholder values from a URL can be used during model binding.

Here are the options:

  • static URL, e.g. /users
  • placeholders, e.g. /products/{productId}
  • placeholder with defualt value, e.g. /products/{productId=123} - when navigating to "/products", a "/products/123" route will be taken
  • optional placegolder, e.g. /products/{productId}/{color?} - optional parameters only make sense at the end of the route template.
  • catch-all parameter, e.g. /products/{*restOfUrl} - restOfUrl will contain the rest of the URL, even if there are slashes. (there can be either 1 or 2 asterisks (*) in the template).

# Constraints

To avoid weird binding issues we can constraint dynamic parts of the template:

  • /products/{id:int} - id has to be convertible to an integer
  • /products/{id:min(5)} - id has to be integer and min 5.
  • /products/{id:length(3)} - id has to have 3 characters.
  • /products/{id:int?} - id is optional, but if provided it has to be convertible to an integer
  • /products/int:max(10)?

Binding Exceptions

If we have an unconstrained template /products/{id} and our handler is OnGet(int id), an exception would be thrown if we called "/products/test". The framework would try to bind "test" string to the id parameter that is an integer.

With proper constraints, that endpoint would be just skipped and not matched. The user will get 404, which might not make sense. Maybe a 400 would be more appropriate. That's why sometimes it makes more sense to rely on validation than on route constraints.

# Convention vs Attributes

Routes can be defined either in a signle place making all endpoints follow some convention, or we can use attributes on every action to define a route for that endpoint specifically. The latter apporach gives more freedom and makes it much easier to introduce changes for a specific endpoint.

# Razor Pages

Razor Pages uses a combination of convention and attribute-based routing. The routes are created by the MapRazorPages() extension method. All files in the Pages direcotry are analyzed and routes are created for them based on their placement.

Index

The Index.cshtml files are an exception from that rule. They are mapped to both xyz/ and xyz/Index (we could have multiple Index.cshtml files in different directories).

To customize the route of a given Razor Page, we need to modify the @page directive of a given page. Some examples for a page at Pages/Items.cshtml:

  • @page "Something" - the URL will be "/items/something" (appending)
  • @page "{category}/{productId}" - the URL will be "items/{category}/{productId}" (appending)
  • @page "/Something" - the URL will be "/something" (replacing)
  • @page "/{category}/{productId}" - the URL will be "/{category}/{productId}" (replacing)

With such modifications, the default file location-based route is no longer valid.

# Generating URLs

# Razor Pages

The framework has a helper for generating URLs to other parts of our app. An example:

var url = Url.Page("Products/Winter", new { id = "273" });

The Url object is a property on PageModel base class. It has various methods for building URLs. We can provide some parameters, and the helper will fit this into the template of the taget page (either as path, or as query).

Relative or Absolute

We can proivde relative or absolute links. The example above was relative. Absolute link (starts from the Pages directory) should start from a /.

Tag Helpers

To genrerate links in HTML, the Anchor Tag Helper is probably the best option.

# MVC

It's very similar to Razor Pages. We also have the Url object. However, we'd use the Action method on it, together with the inputs: action name, controller name, and optional parameters.

var url = Url.Action(nameof(Winter), nameof(Products), new { id = "273" });

TIP

The Page and Action methods are availble from both the Razor Pages and MVC controllers. We can redirect from a Page to a controller and the other way around as well.

ActionResult

Often it's more practical to use the RedirectToPage or RedirectToAction methods. They act similarly as the described methods, but they don't return the URLs in code.

# LinkGenerator

There is also a LinkGenerator class, which may be used in a code outside of Razor Pages or MVC controllers (such as middleware, or any other code).

var url = _linkGenerator.GetPathByPage("/Products/Winter", values: new { id: "273" });
Last Updated: 10/5/2022, 6:26:29 PM