Routing in Blazor Apps

Premier Developer

Developer

In this post, App Dev Manager Billy Sun compares routing of popular web frameworks and Blazor.


Blazing Routing

In today’s web application development, routing is one of the preconditions in every project that developer must incorporate and maintain. The usage of routing is simple, and methodology is consistence across many development frameworks. However, each framework implements routing differently and some are tedious to work with. With the recent release of Microsoft .NET Core Blazor, I found configuring routing has never been easier.

Without delving deep into every use cases of routing, the simplest use case is used to compares how’s routing is being incorporated in web application with various predominated frameworks versus Blazor.

ASP.NET MVC

Perhaps it wasn’t the first, the concept of routing was first introduced in ASP.NET MVC CTP back in early 2007. Instead of the traditional ASP.NET postback model which sends all user interactions back to the same ASP.NET page for processing, the newly introduced routing mechanism routes user interaction to a designated controller based on the structure of the requested URL. A routing map is predefined so that the request will landed on a predetermined controller. For example:

routes.MapRoute(
                "Default",                                              
                "{controller}/{action}/{id}",                                           
                new { controller = "Home", action = "Index", id = "" } 
);

In the example above, the request URL …/Home/Index is routed to the controller Home, action method Index for processing. Typical ASP.NET MVC application would have more than one controller, hence, the routing configuration needs to be expanded to incorporate additional definitions for request to route to the corresponding controller. By convention, if the request is to update (POST) instead of fetch (GET), the overloaded action method needs to be decorated with an attribute such as [AcceptVerbs(HttpVerbs.Post)] or [HttpPost]. Note: ASP.NET MVC follows this convention by decorating action method using attribute. Other Web API frameworks implementation might vary.

Angular

Although Angular is a client-side UI framework, routing between Angular components requires similar scaffolding as ASP.NET MVC. Each route needs to be defined in the app.module.ts file and maintained as well. For all application that requires routing, a <base> element first needs to be added to the index.html <head> tag as the first child in the hierarchy. The router service requires this information to compose all navigation in the application.

<head>
     …
     <base href="/">
     …
</head>

Since routing isn’t a core service in Angular, each application that requires routing needs to include the routing service by importing the router library defined in the app.module.ts file.

…
import { RouterModule, Routes } from '@angular/router'
…

Once the <base> element is set and the routing library is imported, next is to construct the route definition. Every path that is associated with a component needs to be added to the appRoutes array defined in the app.module.ts file. And a catch all routes for non-existing page.

const appRoutes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: 'help',      component: HelpComponent },
  …
  { path: '**', component: PageNotFoundComponent }
];

The appRoutes array is then passed to the router for configuration.

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
  ]
})

There are more routing constructs in Angular that can be defined such as where in the page the component should display <route-output/>, how to handle direct navigation if the route is linked with an anchor tag <a routeLink /> or using routing module, etc. The routing service in Angular is very flexible, but cumbersome to use at time. Without getting deep into every aspects of using Angular Router, detail can be found here.

React

With React, routing isn’t part of the core library also. React Router needs to be downloaded and installed using NPM before the application can use and define routing. Like frameworks described above, React Router requires minimal scaffolding defined in the src/App.js file:

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

Using the same routing requirements as before, each route to component needs to be defined and maintained.

export default function App() {
  return (
    <Router>
      …
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
       …
       <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
    …
    </Router>
  );
}
 
function Home() {
  return <h2>Home</h2>;
}
 
function About() {
  return <h2>About</h2>;

And if a component process parameter on the path such as :id value, React has a notion of nested route. Using an example from reacttraining.com, the router import structure needs to include additional parameters to accommodate nested route.

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  useRouteMatch, // <--
  useParams      // <--
} from "react-router-dom";

Then, the nested route definition for the component is added to an overloaded function corresponds to the route path.

function Topics() {
  let match = useRouteMatch();
 
  return (
    <div>
…        
<li>
          <Link to={`${match.url}/components`}>Components</Link>
        </li>
        <li>
          <Link to={`${match.url}/props-v-state`}>
            Props v. State
          …
       <Switch>
        <Route path={`${match.path}/:topicId`}>
          <Topic />
        </Route>
        <Route path={match.path}>
          <h3>Please select a topic.</h3>
        </Route>
      </Switch>
    …
  );
}

For complex React application, routing can become tedious to maintain as number of route definition increases. Similar construct is also required for React Native.

ASP.NET Core Blazor

Microsoft has released a new framework recently; ASP.NET Core Blazor. The new framework empowers .NET C# developer to leverage the existing .NET ecosystem of .NET libraries to build rich interactive Web UI without needing much knowledge of JavaScript. And a soon to be released Blazor WebAssembly framework which primarily for SPA development takes advantage of WASM, hence, no plugins or code transpilation once the application is deployed and loaded into the browser. More importantly, it works in all modern web browsers, including mobile browsers. To learn more about Blazor, start here.

Back to routing. No web application that needs routing can get away from some scaffolding, and no exception working with Blazor. Blazor adds the bare minimum routing scaffolding in the App.razor file when the application is first created.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

In a simplest term, the default routing configuration does the following: when a user clicked a link in the application, Blazor Router first checks if the destination URL is within the WASM. If it is found, the component corresponded to the URL gets rendered in the <RouteView /> tag. Otherwise, the router would do a full-page load to see if the page can be found on the server. If no avail on the server, the text or component defined in the <NotFound /> tag gets rendered.

The question is how does the Blazor Router know which component corresponds to which URL?

In Blazor, every razor page is represented as a single component. To indicate a page is routable, a directive @page is added to the top of the page. Using the same routing use case as before, the Home page is decorated with the directive as below:

@page "/"

Or

@page "/home" (if the page isn't the root of the application.)

And for the About page:

@page "/about"

Very simple.

What if a page like Topic that takes a value? Following the similar convention as other frameworks, the page is decorated with the directive as below:

@page "/Topic/{topicID:int}"

During page load, the value of topicID gets passed to a .NET C# property decorated with the [Parameter] attribute in the @code section of the page.

@code {
[Parameter] public int TopicId { get; set; }
}

It is the construct within the @code section process the request when the page is requested and supplies the variable to the razor page to render the page.

Since all pages in Blazor are consider components, non-routable components are referenced like a typical HTML element using tag convention. For example, a component that pops up to request user to login.

… logic to determine if the user has logged in…
…
<LoginDisplay parameter=”value” />
…
…

There are two modes in Blazor: WebAssembly and Server Hosted. Developer can choose one over the other based on the application requirement. A drawback with WebAssembly is as the application increases complexity, the payload of the WebAssembly would grow which might have a significant impact to the initial download time. A hybrid mode is in research at the time of this writing. If it became fruition, developer could balance and choose where the page should be served from. Since the Blazor Router is already capable to look up pages between WebAssembly and Server, I assume routing construct would stay intact without much added complexity when the hybrid mode become available.

Based on the current implementation, Blazor Router makes routing much easier to implement and developer hardly need to maintain routing as the application evolves. Bugs due to misconfiguration of routing might become a thing of the past.

0 comments

Leave a comment