.NET MAUI Blazor windows
The .NET MAUI documentation details the creation of windows and states that .NET MAUI apps support having multiple windows, but what it omits is explaining how to access this functionality in the context of a .NET MAUI Blazor app.
Fundamentally, a .NET MAUI Blazor app is just a normal MAUI app with a single page (MainPage.xaml
) that has a BlazorWebView which then actually hosts the Razor content. That means that any newly created window will still have the same MainPage.xaml
as its content and it is up to the Blazor-side to present the correct content.
.NET MAUI app startup
First, a little background about how a .NET MAUI Blazor application starts (in the boilerplate project):
-
Program.cs
The entry point is
CreateMauiApp()
which referencesApp
by using it as a type argument in the generic methodUseMauiApp()
. -
App.xaml(.cs)
(derives fromMicrosoft.Maui.Controls.Application
)Sets its
MainPage
property (inherited from its base class) to a new instance ofMainPage
. According to the .NET MAUI documentation on windows, setting theMainPage
property of anApplication
will cause aWindow
to be created. -
MainPage.xaml(.cs)
Contains a
BlazorWebView
that references the staticwwwroot/index.html
as the HTML boilerplate/skeleton and adds theMain
type as the onlyRootComponent
of theBlazorWebView
. -
Main.razor(.cs)
Contains a
Router
component that, according to the Blazor routing and navigation documentation, scans the specifiedAppAssembly
to "gather route information for the app's components that have a RouteAttribute".
So we need to provide a way for Razor components to create a new MAUI window with the possibility to specify an URI that references a specific page within the AppAssembly
of the Router
.
Implementation
The following details the implementation from the bottom up in terms of the startup described above:
Main.razor.cs
(create)
Firstly, we need to provide a way to specify a page other that the index (i.e. the page that has the /
route) in the Main
razor component. So, we add a parameter to specify the URI of the page we want to view and navigate to that URI right after initializing the component:
+public partial class Main : ComponentBase
+{
+ [Parameter]
+ public string PageUri { get; set; } = string.Empty;
+
+ protected override void OnInitialized()
+ {
+ if (!string.IsNullOrEmpty(PageUri))
+ Nav.NavigateTo(PageUri);
+ }
+}
MainPage.xaml
Remove the <BlazorWebView.RootComponents>
element from the XAML completly. We will populate this collection in the code-behind instead:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:RecEx.App"
x:Class="RecEx.App.MainPage"
BackgroundColor="{DynamicResource PageBackgroundColor}">
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
- <BlazorWebView.RootComponents>
- <RootComponent Selector="#app" ComponentType="{x:Type local:Main}" Parameters="{Binding Path=RootComponentParameters}" />
- </BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>
MainPage.xaml.cs
(create)
The items in the RootComponents
collection provide a way to pass parameters to the specified component in the form of a IDictionary<string, object>
. So we add an optional argument to the constructor to expose this to callers:
+public partial class MainPage : ContentPage
+{
+ public MainPage(IDictionary<string, object> rootComponentParameters = null)
+ {
+ InitializeComponent();
+
+ blazorWebView.RootComponents.Add(new()
+ {
+ ComponentType = typeof(Main),
+ Parameters = rootComponentParameters,
+ Selector = "#app"
+ });
+ }
+}
App.xaml.cs
Finally, we add a static class that provides extension methods to open the window:
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
}
+public static class ApplicationExtensions
+{
+ public static Window OpenWindowFromPage(this Application app, Page page)
+ {
+ var windowObj = new Window(page);
+ windowObj.Parent = app.Windows[0];
+ app.OpenWindow(windowObj);
+ return windowObj;
+ }
+ public static void OpenInNewWindow(this Application app, string pageUri)
+ {
+ var windowPage = new MainPage(new Dictionary<string, object>()
+ {
+ {nameof(Main.PageUri), pageUri }
+ });
+ app.OpenWindowFromPage(windowPage);
+ }
+
+ public static void OpenInNewWindow(this ComponentBase component, string pageUri) =>
+ Application.Current.OpenInNewWindow(pageUri);
+}
Usage
To open a Razor page in a new MAUI window, simply call the OpenInNewWindow
extension method from any component:
@page "/"
<h1>.NET MAUI Blazor window demo</h1>
<button type="button" @onclick="() => OpenCounterWindow()">Open counter window</button>
@code {
protected void OpenCounterWindow()
{
this.OpenInNewWindow("/counter");
}
}
Limitations/caveats
- There does not seem to be any support for modal windows (yet?). I've experimented with the
Parent
property onWindow
, but to no avail. - In my tests, I could always briefly see the content of the
Index.razor
page because theRouter
loads it by default. In addition to being a cosmetic issue, it also means that any code on theIndex
page is executed before the specified page is loaded.