Seamless Navigation in .NET MAUI Hybrid Apps – My .NET MAUI Hybrid Journey (part 1)

I’ve always wanted to build a native application with all the insight and expertise that I’ve accumulated in building web applications, it’s felt like a big jump for me to get into native mobile or desktop applications. A long time ago I used to build windows forms applications for my employer, and I haven’t worked a desktop application since.

With the advent of .NET MAUI and the ability to build Blazor applications for a website and share their pages and components for use in a native application felt like a real possibility for me. I haven’t really stepped into building and working working with this application model yet… until now.

This is the first of what I expect will be a series of blog posts describing things that I’ve discovered, challenges that I’ve overcome, and features as I migrate the TagzApp application to run as a native desktop application.

Menus – Not as Easy as They Look

The first feature that I wanted to bring over was going to be the layout of the application. In TagzApp I have a very simple layout with a header menu bar with a couple of options to allow you to navigate to other areas of the application. It’s not too complex but it felt like an easy piece to migrate over to the hybrid application.

In doing some research and thinking more about how I want to represent a menubar inside of a native application it felt like it made more sense to turn this into a native menubar and not an HTML menubar that lived inside of my Blazor application. I started doing some research about how to create a menubar in .NET MAUI and found a few examples that showed how to use a tab bar to create and use multiple BlazorWebView components to represent different sections of the application. This felt clumsy to me because it meant that I would be spinning up multiple browsers to run inside of my application just to access and work with other parts of the application. I knew that that would mean more resources used by the computer when this application is running, and that felt a little irresponsible for me as a developer.

I wanted to actually have a menu bar with items that you would click and it would navigate inside of my Blazor application. Looking at the documentation for the BlazorWebView, there is no direct access to the NavigationManager or an ability to reset the location of the browser component. I set about to make the NavigationManager inside of Blazor accessible to the .NET MAUI application.

In the demos on this post, I’ll start with the default Blazor Hybrid template application and turn the vertical NavBar element into a native menu. Completed source code for this sample is available on my GitHub. I also have recorded a video where I talk through this demo:

The default experience inside a Blazor Hybrid application with .NET 8

Configuring the Shell and Menu Items

To start with, I configured the App.xaml file to have a Shell embedded directly and contain the BlazorWebView for my application. This would allow me to add a Menubar to the Shell.

<Application.MainPage>
  <Shell>
    <ShellContent>
      <ShellContent.ContentTemplate>
        <DataTemplate>
          <ContentPage>
            <BlazorWebView x:Name="blazorWebView1" HostPage="wwwroot/index.html">
              <BlazorWebView.RootComponents>
                <RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
              </BlazorWebView.RootComponents>
            </BlazorWebView>
          </ContentPage>
        </DataTemplate>
      </ShellContent.ContentTemplate>
    </ShellContent>
  </Shell>
</Application.MainPage>

I also removed the call inside App.xaml.cs to set MainPage = new MainPage(); Since we’re specifying our own MainPage inside the XAML markup, there’s no need to instantiate another page. I could run the application now, and I’d get the same user experience as the previous image.

Ok.. next steps…

Adding a MenuBar component

In .NET MAUI, the MenuBar component is added when you introduce MenubarItems. No problem, I added a MenuBarItem and 3 MenuFlyoutItems for the 3 base pages inside the default application. This code was added just inside the ContentPage element in App.xaml

<ContentPage.MenuBarItems>
  <MenuBarItem Text="Content">
    <MenuFlyoutItem Text="Home" Clicked="MenuItem_Clicked"></MenuFlyoutItem>
    <MenuFlyoutItem Text="Counter" Clicked="MenuItem_Clicked"></MenuFlyoutItem>
    <MenuFlyoutItem Text="Weather" Clicked="MenuItem_Clicked"></MenuFlyoutItem>
  </MenuBarItem>
</ContentPage.MenuBarItems>

Notice that I set each of the menu items to trigger the same event, MenuItem_Clicked All of these menu items do the same thing, but vary in the location they target. We’ll write this method in a little bit, because we need to first make the NavigationManager available

Enabling the NavigationManager in .NET MAUI

The Blazor NavigationManager isn’t directly accessible in .NET MAUI. You can’t inject it or reach into the BlazorWebView and interact with it. Instead, we need to create a service that will allow us to capture the NavigationManager and interact with it. The curious part of this is that both parts of the application model, .NET MAUI and Blazor use the same dependency injection services. So…. we can exploit this to allow our service to be injected into both Blazor AND .NET MAUI.

No problem, I can whip up a little bit of code that allows both application models to work with the Blazor NavigationManager:

public class NavigatorService
{

  internal NavigationManager NavigationManager { get; set; }

}

I can then register this NavigatorService with the service locator in .NET MAUI with this line in the MauiProgram.cs file:

builder.Services.AddSingleton<NavigatorService>();

I want this Navigator service on every page in my Blazor application, so I’ll inject it and configure the NavigationManager we’ll use inside the MainLayout.razor file:

@inherits LayoutComponentBase
@inject MauiApp1.NavigatorService NavigatorService
@inject NavigationManager NavigationManager

<div class="page">
...
</div>

@code {

  protected override void OnInitialized()
  {

    NavigatorService.NavigationManager = NavigationManager;

  }

}

Finally, I’ll add the NavigatorService to my App.xaml.cs code so that it is injected and stored as a property for use later:

public partial class App : Application
{
  public App(NavigatorService navigatorService)
  {
    InitializeComponent();
    NavigatorService = navigatorService;
  }

  internal NavigatorService NavigatorService { get; }

  private void MenuItem_Clicked(object sender, EventArgs e)
  {
  }
}

Connecting and Navigating from the MenuBar

Now, we can use a switch statement to configure the navigation of the BlazorWebView. Let’s add that switch inside the Menuitem_Clicked method:

private void MenuItem_Clicked(object sender, EventArgs e)
{

  var menuItem = (MenuItem)sender;
  var url = menuItem.Text switch
  {
    "Counter" => "/counter",
    "Weather" => "/weather",
    _ => "/"
  };
  NavigatorService.NavigationManager.NavigateTo(url);

}
Application with the new MenuBar

Now, when we click the various items in the native MenuBar, the browser navigates appropriately.

For completeness, I removed the side navigation from the MainLayout.razor file so that the application felt more native and didn’t have 2 MenuBars.

Summary

This is just one creative way to connect our Blazor application to .NET MAUI and reuse the code we’ve already built in Blazor. The complete source code for this sample is available on my GitHub. I’m working through an entire application for TagzApp, and will share more of my findings in the weeks ahead.

Have you tried using Blazor content in .NET MAUI, WPF, or Windows Forms? What was your experience? Let me know in the comments below