ASP.NET Core (2) - Routing and Model Binding

19 May 2021 , 3003 words

You can easily tell from the last post that routing and executing endpoints are at the core of ASP.NET Core. The routing is not so difficult to grasp, you just need to remember the syntaxes.

To start, if you are building a Razor Page website, which has a page to show product detail, you may want to create a page: Pages/Products/Detail.cshtml. The page may have the following directive on its first line:

@page "products/detail/{category}/{id}/{color}"

With this information, the framework’s routing component will route the request to www.example.com/products/detail/shoes/5/red to this Razor Page’s handler.

Note that it’s using the location of the file plus the @page directive to determine the destination. By default, the router starts from the Pages folder, and treat every nested folder as a segment in the URL. So the products/detail segments in the URL will be mapped to Pages/Products/Detail.cshtml. But that’s not the end of story yet. If you try to visit the URL www.example.com/Products/Detail, it will not be mapped to the Detail.cshtml, because the {category}/{id}/{color} segments in the directive is still missing. These segments in the {} is the required dynamic data.

Before we dive into what the dynamic data are for, it’s worth mentioning that we always need the @page directive on the first line for normal Razor Pages that you want to be the target of the routing system. This also means pages like layout and partial views should not starts with the @page directive, as they are not the destinations for any routing.

So, what are the {category}/{id}/{color}? In short, they are part of the URL carrying dynamic data that will be used for model binding.

When the URL www.example.com/products/detail/shoes/5/red is requested, the Detail page is selected as the destination. Then the framework’s model binder parses the rest of the URL as values for the pages’ binding model, in our case:

  • category = shoes
  • id = 5
  • color = red

To get the binding working, there are two approaches:

  1. Add parameters to the handler method:
public class DetailPageModel : PageModel {
    public string Category {get; set;}
    public int Id {get; set;}
    public string Color {get; set;}
    
    public void OnGet(string category, int id, string color){
    	Category = category;
        ...
	}
}
  1. Use the [BindProperty] attribute, which only works for POST request unless you specify GET support in the attribute:
public class DetailPageModel : PageModel {
    [BindProperty(SupportsGet = true)]
    public string Category {get; set;}
    
    [BindProperty(SupportsGet = true)]
    public int Id {get; set;}
    
    [BindProperty(SupportsGet = true)]
	public string Color {get; set;}
    ...
}

Once the model binding is done, the Razor page can access the model with the directive:

@model DetailPageModel

For example, with Razor engine, the page can display the category of the product like: <h1> Look at my @Model.Category </h1>, which will be transformed to <h1> Look at my shoes </h1> in the response.