Learn how you manage routing in Blazor for .NET Core and VS Code

Chris Noring - Jun 24 '20 - - Dev Community

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

 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 are orderId and itemId. 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 following blog/{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 case slug.
  • 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.

  1. Create a new Blazor project by typing in the following command in the terminal:
   dotnet new blazorwasm -o blazor-routing
   cd blazor-routing
Enter fullscreen mode Exit fullscreen mode

Now you have a blazor project ready to go.

  1. 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;
      }
    }
   }
Enter fullscreen mode Exit fullscreen mode
  1. In the Pages directory create a file Products.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);
    }
   }
Enter fullscreen mode Exit fullscreen mode

Note 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>
   }
Enter fullscreen mode Exit fullscreen mode

By using NavLink it will create a link the user can click on. Note how href is pointing to product.Url. You don't have that property yet so let's fix that. Open up Data.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;
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode

-2- Creating a product detail page.

Next, you will create a product detail page.

  1. Create the file Product.razor under Pages 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; }
   }
Enter fullscreen mode Exit fullscreen mode

Above 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; }
Enter fullscreen mode Exit fullscreen mode

This 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 component NavLink:

   <NavLink href="products" target="_blank">Products</NavLink>
Enter fullscreen mode Exit fullscreen mode

The href is pointing to products which is your product list page. The NavLink is translated into an anchor tag during compilation.

  1. Try out the application at this point by running the following command in the terminal:
   dotnet build && dotnet run
Enter fullscreen mode Exit fullscreen mode

Yor app should now run at http://localhost:5000. Let's navigate to http://localhost:5000/products. You should see the following:

Product list view

-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.

  1. Open up Data.cs and add the following method to the Products class:
   public static Product GetProduct(int id) 
   {
     return products.SingleOrDefault(p => p.Id == id);
   }
Enter fullscreen mode Exit fullscreen mode
  1. Open 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");
     }
   }
Enter fullscreen mode Exit fullscreen mode

Now above the declaration of the lifecycle method OnParametersSet(). This is a point at which Id has got its value and you can safely write code that asks for a specific Product with this code:

   this.product = Data.Products.GetProduct(this.Id);
Enter fullscreen mode Exit fullscreen mode

Note also how programmatic navigation was added. In the template there is this section:

   <button class="btn btn-primary" @onclick="Navigate">
     Navigate to products
   </button>
Enter fullscreen mode Exit fullscreen mode

In the code section there is a method Navigate(), like so:

   private void Navigate() 
   {
     NavigationManager.NavigateTo("products");
   }
Enter fullscreen mode Exit fullscreen mode

What did it get NavigationManager from? It's added at the of the component like so:

   @inject NavigationManager NavigationManager
Enter fullscreen mode Exit fullscreen mode

NavigationManager 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.

  1. In the terminal run the following command:
   dotnet add package Microsoft.AspNetCore.WebUtilities
Enter fullscreen mode Exit fullscreen mode

This will install the WebUtilities package from NuGet.

  1. 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);
     }
   }
Enter fullscreen mode Exit fullscreen mode

The 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);   
    
  • Parse out the parameters. For this you use the QueryHelper class with uri.Query as input parameter. Then you call TryGetValue() with the query parameter as an argument. You do this twice, once for page and once for pageSize.

      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); 
      }
    
    • Call 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 the GetProducts() method a little. Ensure the GetProducts() 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;
    }
Enter fullscreen mode Exit fullscreen mode
  1. Let's test out the solution. Type the following command in the terminal:
   dotnet build && dotnet run
Enter fullscreen mode Exit fullscreen mode
  1. Navigate to http://localhost:5000/products?page=1&pageSize=2. You should now see the 2 first rows displayed like so:

Filtered product list

  1. Change page by changing the page to 2 so the URL should now look like so http://localhost:5000/products?page=2&pageSize=2. Your UI should now look like so:

Filterd product list page 2

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .