Skip to content

Shell | Source Generation

Shiny Shell includes source generators that eliminate route registration boilerplate and create strongly-typed navigation extensions. Decorate your ViewModels with attributes and the generators handle the rest.

Apply [ShellMap<TPage>] to a ViewModel to register it with a page and route.

[ShellMap<DetailPage>("Detail")]
public class DetailViewModel
{
}
ParameterTypeDescription
routestringThe Shell route name for this page. Also used as the generated constant and method name. Must be a valid C# identifier.
registerRouteboolWhether to register the route with Shell (default: true). Set to false for pages already defined in AppShell.xaml
// Root page defined in AppShell.xaml — don't re-register the route
[ShellMap<MainPage>(registerRoute: false)]
public class MainViewModel
{
}

When you specify a route, it is used as:

  • The constant name in Routes (e.g., Routes.Detail)
  • The method suffix in navigation extensions (e.g., NavigateToDetail)
  • The string value passed to Shell routing

When no route is specified, the page type name without the Page suffix is used as the generated name, and the full page type name is used as the route value:

[ShellMap<DetailPage>("Detail")]
// → Routes.Detail = "Detail"
// → NavigateToDetail(...)
[ShellMap<HomePage>]
// → Routes.Home = "HomePage"
// → NavigateToHome(...)

Mark ViewModel properties with [ShellProperty] to include them in the generated navigation extension methods.

[ShellMap<DetailPage>("Detail")]
public class DetailViewModel
{
[ShellProperty]
public int Id { get; set; }
[ShellProperty]
public string Name { get; set; }
}

The generator creates a strongly-typed extension method with parameters matching each [ShellProperty]:

// Generated — you call this instead of manual NavigateTo
await navigator.NavigateToDetail(id: 42, name: "Allan");

The source generators produce up to three files (each can be individually disabled):

A static class with const string fields for every route.

public static class Routes
{
public const string Detail = "Detail";
public const string Main = "MainPage";
public const string Modal = "Modal";
}

Strongly-typed extension methods on INavigator for each ViewModel with [ShellProperty] attributes.

public static class NavigationExtensions
{
public static Task NavigateToDetail(this INavigator navigator, int id, string name)
{
return navigator.NavigateTo<DetailViewModel>(x =>
{
x.Id = id;
x.Name = name;
});
}
}

A DI registration method that replaces all manual .Add<TPage, TViewModel>() calls. Uses inline string literals so it works regardless of whether route constants are enabled.

public static class NavigationBuilderExtensions
{
public static ShinyAppBuilder AddGeneratedMaps(this ShinyAppBuilder builder)
{
builder.Add<DetailPage, DetailViewModel>("Detail");
builder.Add<MainPage, MainViewModel>("MainPage", registerRoute: false);
builder.Add<ModalPage, ModalViewModel>("Modal");
return builder;
}
}

You can disable individual generated files via MSBuild properties in your .csproj:

<PropertyGroup>
<!-- Disable route constants (Routes.g.cs) -->
<ShinyMauiShell_GenerateRouteConstants>false</ShinyMauiShell_GenerateRouteConstants>
<!-- Disable navigation extensions (NavigationExtensions.g.cs) -->
<ShinyMauiShell_GenerateNavExtensions>false</ShinyMauiShell_GenerateNavExtensions>
</PropertyGroup>
PropertyDefaultControls
ShinyMauiShell_GenerateRouteConstantstrueRoutes.g.cs
ShinyMauiShell_GenerateNavExtensionstrueNavigationExtensions.g.cs

NavigationBuilderExtensions.g.cs is always generated — even when no [ShellMap] attributes exist yet — so you can wire up MauiProgram.cs immediately.

// MauiProgram.cs — manual registration
builder
.UseShinyShell(x => x
.Add<MainPage, MainViewModel>(registerRoute: false)
.Add<DetailPage, DetailViewModel>("Detail")
.Add<ModalPage, ModalViewModel>("Modal")
);
// Navigation — string-based, error-prone
await navigator.NavigateTo("Detail", ("Id", 42), ("Name", "Allan"));
// MauiProgram.cs — one line
builder.UseShinyShell(x => x.AddGeneratedMaps());
// Navigation — strongly typed, compile-time checked
await navigator.NavigateToDetail(id: 42, name: "Allan");

Replace your manual route registrations with the generated method in MauiProgram.cs:

public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseShinyShell(x => x.AddGeneratedMaps())
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}

All [ShellMap] decorated ViewModels are automatically registered — no manual .Add() calls needed.