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.
ShellMap Attribute
Section titled “ShellMap Attribute”Apply [ShellMap<TPage>] to a ViewModel to register it with a page and route.
[ShellMap<DetailPage>("detail")]public class DetailViewModel{}| Parameter | Type | Description |
|---|---|---|
route | string | The Shell route name for this page |
registerRoute | bool | Whether 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>("main", registerRoute: false)]public class MainViewModel{}ShellProperty Attribute
Section titled “ShellProperty Attribute”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 NavigateToawait navigator.NavigateToDetail(id: 42, name: "Allan");What Gets Generated
Section titled “What Gets Generated”The source generators produce three files:
Routes.g.cs
Section titled “Routes.g.cs”A static class with const string fields for every route.
public static class Routes{ public const string Detail = "detail"; public const string Main = "main"; public const string Modal = "modal";}NavigationExtensions.g.cs
Section titled “NavigationExtensions.g.cs”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; }); }}NavigationBuilderExtensions.g.cs
Section titled “NavigationBuilderExtensions.g.cs”A DI registration method that replaces all manual .Add<TPage, TViewModel>() calls.
internal static class __ShinyMauiNavigationRegistry{ [global::System.Runtime.CompilerServices.ModuleInitializerAttribute] public static void Initialize() { global::Shiny.Infrastructure.ShinyMauiShellRegistry.RegisterCallback(builder => { builder.Add<DetailPage, DetailViewModel>(Routes.Detail); builder.Add<MainPage, MainViewModel>(Routes.Main, registerRoute: false); builder.Add<ModalPage, ModalViewModel>(Routes.Modal); }); }}Before & After
Section titled “Before & After”Without source generation
Section titled “Without source generation”// MauiProgram.cs — manual registrationbuilder .UseShinyShell(x => x .Add<MainPage, MainViewModel>(registerRoute: false) .Add<DetailPage, DetailViewModel>("detail") .Add<ModalPage, ModalViewModel>("modal") );
// Navigation — string-based, error-proneawait navigator.NavigateTo("detail", ("Id", 42), ("Name", "Allan"));With source generation
Section titled “With source generation”// MauiProgram.cs — one linebuilder.UseShinyShell(x => x.AddGeneratedMaps());
// Navigation — strongly typed, compile-time checkedawait navigator.NavigateToDetail(id: 42, name: "Allan");Using AddGeneratedMaps
Section titled “Using AddGeneratedMaps”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.