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. Also used as the generated constant and method name. Must be a valid C# identifier. |
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>(registerRoute: false)]public class MainViewModel{}Route Naming
Section titled “Route Naming”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(...)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 up to three files (each can be individually disabled):
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 = "MainPage"; 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. 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; }}Configuring Source Generation
Section titled “Configuring Source Generation”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>| Property | Default | Controls |
|---|---|---|
ShinyMauiShell_GenerateRouteConstants | true | Routes.g.cs |
ShinyMauiShell_GenerateNavExtensions | true | NavigationExtensions.g.cs |
NavigationBuilderExtensions.g.cs is always generated — even when no [ShellMap] attributes exist yet — so you can wire up MauiProgram.cs immediately.
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.