Learn how you manage routing in Blazor for .NET Core and VS Code
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Here's a link to previous works I've written on Blazor. Blazor is a framework that enables you to build full-stack apps in C#. You don't have to mix a backend language with a frontend language like JavaScript. Not having this context switch is a thing that's truly powerful.
References
- Install .NET Core, latest version Ensure you have the latest version of .NET core installed so you have all the Blazor templates installed with it.
- Get started with blazor This is good starter page for Blazor.
- Blazor routing This page shows all you need to know about routing
- Community blog post on Query parameters
- My first article on Blazor
- My second article on Blazor + JavaScript
Routing
Routing, what do we mean by the term? It simply means how we set up navigation and how we deal with the URL of the app. More specifically this usually means the following:
- Setting up simple routes, this involves defining a route like
/products
and determine what component should respond when this route is entered in a Browser. - Dealing with complex routes and route parameters, a complex route usually looks a bit more advanced like so
/orders/{orderId}/items/{itemId}
. This route has something called route parameters. Route parameters are parts of the route that instructs the app to get a specific piece of data. With this example, the route parameters areorderId
anditemId
. What the parameters convey in this case is that they want a specific order and a specific order item on that order. An example of the mentioned route pattern can look like so/orders/111/item/1
. Other examples of complex routes can be the followingblog/{slug}
. This is a common pattern when creating a blog. It is the same idea as the order example and also uses route parameters, in this caseslug
. - Query parameters, query parameters aren't part of the route technically but is the end of URL. Given a URL
/products?page=1&pageSize=10
the query parameters is everything that happens after the?
. Parameters are separated by&
and are key-value pairs. - Navigation, there are two types of navigation that is interesting to look at:
- Clicking a link, usually there's a specific component helping you produce a link that the user is able to click on.
- Programmatic navigation, this involves navigating with code. This is usually the case if you need to perform some asynchronous work before navigating.
Routing in Blazor
Let's work through a scenario to learn how to handle the most common route aspects. Our scenario is that of master-detail. A product list page and product detail page. We'll gradually add more capabilities to it as we go along.
-1- Adding a product list page
This is a simple page that needs to show a list of products. For data, we will use a simple static data class.
Create a new Blazor project by typing in the following command in the terminal:
dotnet new blazorwasm -o blazor-routing cd blazor-routing
1
2Now you have a blazor project ready to go.
Create a file
Data.cs
in the root of the project and give it the following content:using System.Collections.Generic; namespace Data { public class Product { public int Id { get; set; } public string Name { get; set; } } public static class Products { private static List<Data.Product> products = new List<Data.Product>(){ new Data.Product(){ Id = 1, Name = "Test" }, new Data.Product(){ Id = 2, Name = "Test2" }, new Data.Product(){ Id = 3, Name = "Test3" }, new Data.Product(){ Id = 4, Name = "Test4" } }; public static List<Product> GetProducts() { return products; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24In the
Pages
directory create a fileProducts.razor
and give it the following content:@page "/products" @inject NavigationManager NavigationManager <h1>Products</h1> @foreach (var product in products) { <div> <NavLink href="@product.Url" target="_blank">@product.Name</NavLink> </div> } @code { List<Data.Product> products; protected override void OnInitialized() { this.products = Data.Products.GetProducts(pageParam, pageSizeParam); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Note how the
@page
directive at the top is used to instruct that Blazor that this component should listen to the route/products
. You've also set up the navigation between the product list and product detail page. Let's have a closer look at this part:@foreach (var product in products) { <div> <NavLink href="@product.Url" target="_blank">@product.Name</NavLink> </div> }
1
2
3
4
5
6By using
NavLink
it will create a link the user can click on. Note howhref
is pointing toproduct.Url
. You don't have that property yet so let's fix that. Open upData.cs
and add it like so:public class Product { public int Id { get; set; } public string Name { get; set; } public string Url { get { return "products/" + this.Id; } } }
1
2
3
4
5
6
7
8
9
10
11
12
-2- Creating a product detail page.
Next, you will create a product detail page.
Create the file
Product.razor
underPages
directory. Give the file the following content:@page "/products/{Id:int}" @inject NavigationManager NavigationManager <h1>A specific product</h1> This is for Id @Id <div> Here is the product: @product.Name </div> <div> <NavLink href="products" target="_blank">Products</NavLink> </div> @code { [Parameter] public int Id { get; set; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Above you set up a route
@page "/products/{Id:int}"
. This means you are handling routes like so/products/1
but now/products/abc
. Note how you are coercing the route parameter to be an integer{Id:Int}
. There's also another section of interest where you get hold of the router parameter:[Parameter] public int Id { get; set; }
1
2This is great if we in a later stage want to do a data lookup based on the route parameter value. Additionally, note how you add navigation back to the
/products
route using the built-in componentNavLink
:<NavLink href="products" target="_blank">Products</NavLink>
1
The href
is pointing to products
which is your product list page. The NavLink
is translated into an anchor tag during compilation.
Try out the application at this point by running the following command in the terminal:
dotnet build && dotnet run
1Yor app should now run at
http://localhost:5000
. Let's navigate tohttp://localhost:5000/products
. You should see the following:
-3- Detail component data lookup
Next, you will implement looking up specific data on the detail component. To do so you need to ensure you have a specific method in Data.cs
that lets you select a specific item by id.
Open up
Data.cs
and add the following method to theProducts
class:public static Product GetProduct(int id) { return products.SingleOrDefault(p => p.Id == id); }
1
2
3
4Open up
Product.razor
and update the code to look like the following:@page "/products/{Id:int}" @inject NavigationManager NavigationManager <h1>A specific product</h1> This is for Id @Id <div> Here is the product: @product.Name </div> <div> <NavLink href="products" target="_blank">Products</NavLink> </div> <button class="btn btn-primary" @onclick="Navigate"> Navigate to products </button> @code { [Parameter] public int Id { get; set; } Data.Product product = null; protected override void OnParametersSet() { this.product = Data.Products.GetProduct(this.Id); } private void Navigate() { NavigationManager.NavigateTo("products"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35Now above the declaration of the lifecycle method
OnParametersSet()
. This is a point at whichId
has got its value and you can safely write code that asks for a specificProduct
with this code:this.product = Data.Products.GetProduct(this.Id);
1Note also how programmatic navigation was added. In the template there is this section:
<button class="btn btn-primary" @onclick="Navigate"> Navigate to products </button>
1
2
3In the code section there is a method
Navigate()
, like so:private void Navigate() { NavigationManager.NavigateTo("products"); }
1
2
3
4What did it get
NavigationManager
from? It's added at the of the component like so:@inject NavigationManager NavigationManager
1NavigationManager
is built into Blazor and helps us navigate among other things.
-4- Adding query parameters
This is great we have our master-detail working. However, it would be great if we learn to use query parameters. So why is that? Query parameters help filter the response. Let's look at a route like so /products?page=1&page=10
. What does that mean? The query parameters page
and pageSize
signals that we want to limit the response. Imagine that /products
possibly might return millions of records. We don't want that. So what we do is to think of our data in terms of pages. Having page=1
and pageSize=10
means we want the first 10 records whereas page=2
would mean we want records 11-20.
To start using query parameters we need to both install an assembly and write some code to parse the query parameters from the URL.
In the terminal run the following command:
dotnet add package Microsoft.AspNetCore.WebUtilities
1This will install the
WebUtilities
package from NuGet.Open up
Products.razor
file. Give it the following content:@page "/products" @inject NavigationManager NavigationManager @using System.Linq <h1>Products</h1> Page: @pageParam PageSize: @pageSizeParam @foreach (var product in products) { <div> <NavLink href="@product.Url" target="_blank">@product.Name</NavLink> </div> } @code { List<Data.Product> products; int? pageParam = null; int? pageSizeParam = null; protected override void OnInitialized() { var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri); if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("page", out var param)) { this.pageParam = Int32.Parse(param); } if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("pageSize", out var param2)) { this.pageSizeParam = Int32.Parse(param2); } this.products = Data.Products.GetProducts(pageParam, pageSizeParam); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34The big addition here was the method
OnInitialized()
. There are a few things going on so let's take it line by line.Get the URI, we will need the Uri so it's easier to parse out the query parameters.
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
1Parse out the parameters. For this you use the
QueryHelper
class withuri.Query
as input parameter. Then you callTryGetValue()
with the query parameter as an argument. You do this twice, once forpage
and once forpageSize
.if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("page", out var param)) { this.pageParam = Int32.Parse(param); } if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("pageSize", out var param2)) { this.pageSizeParam = Int32.Parse(param2); }
1
2
3
4
5
6
7
8Call
GetProducts()
with the query parameters to get a filtered response.this.products = Data.Products.GetProducts(pageParam, pageSizeParam);
1
Open up
Data.cs
as you will need to update theGetProducts()
method a little. Ensure theGetProducts()
method now looks like so:public static List<Product> GetProducts(int? page, int? pageSize) { if (page.HasValue && pageSize.HasValue) { var filtered = products.Skip((page.Value-1) * pageSize.Value).Take(pageSize.Value); return filtered.ToList(); } return products; }
1
2
3
4
5
6
7
8Let's test out the solution. Type the following command in the terminal:
dotnet build && dotnet run
1Navigate to
http://localhost:5000/products?page=1&pageSize=2
. You should now see the 2 first rows displayed like so:Change page by changing the
page
to2
so the URL should now look like sohttp://localhost:5000/products?page=2&pageSize=2
. Your UI should now look like so:
Summary
We've covered quite a few things here related to routing. Basic routing looking like so /products
but also more complicated routing like so /products/{id: Int}
. Additionally, we even showed how to use query parameters to further filter the response. I hope it was helpful.