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 |
description | string | Optional description used by the source generator to produce XML doc comments, [Description] attributes, and GeneratedRouteInfo metadata. Enables AI tooling integration. |
// 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.
| Parameter | Type | Description |
|---|---|---|
description | string | Optional description used by the source generator to produce XML doc comments, [Description] attributes on parameters, and GeneratedRouteParameter metadata |
required | bool | Whether this parameter is required for navigation (default: true). Optional parameters get default values in the generated method signature |
[ShellMap<DetailPage>("Detail", description: "Navigate to the detail page")]public class DetailViewModel{ [ShellProperty("The item identifier")] public int Id { get; set; }
[ShellProperty("The display name")] public string Name { get; set; }}The generator creates a strongly-typed extension method with parameters matching each [ShellProperty], including XML docs and [Description] attributes when descriptions are provided:
// 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 five files (nav extensions 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. When description is set on [ShellMap] and [ShellProperty], the generated methods include XML doc comments and [Description] attributes for AI tooling integration.
public static class NavigationExtensions{ /// <summary> /// Navigate to the detail page /// </summary> /// <param name="id">The item identifier</param> /// <param name="name">The display name</param> /// <param name="relativeNavigation">If true, it will navigate/stack from where the application currently is otherwise, it will reset the stack to this new route</param> [Description("Navigate to the detail page")] public static Task NavigateToDetail(this INavigator navigator, [Description("The item identifier")] int id, [Description("The display name")] string name, [Description("...")] bool relativeNavigation = true) { return navigator.NavigateTo<DetailViewModel>(x => { x.Id = id; x.Name = name; }, relativeNavigation); }}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; }}NavigationBuilderNavExtensions.g.cs
Section titled “NavigationBuilderNavExtensions.g.cs”Strongly-typed Add extension methods on INavigationBuilder for use with the fluent navigation builder. Generated alongside NavigationExtensions.g.cs when nav extensions are enabled.
public static class NavigationBuilderNavExtensions{ public static INavigationBuilder AddDetail(this INavigationBuilder builder, int id, string name) { return builder.Add<DetailViewModel>(x => { x.Id = id; x.Name = name; }); }
public static INavigationBuilder AddModal(this INavigationBuilder builder) { return builder.Add<ModalViewModel>(); }}Usage:
await navigator .CreateBuilder() .AddDetail(id: 42, name: "Allan") .AddModal() .Navigate();AiExtensions.g.cs
Section titled “AiExtensions.g.cs”A GetGeneratedRouteInfo() extension method on INavigator that returns structured metadata about all registered routes and their parameters. Designed for AI agents and tooling to discover available navigation routes at runtime.
public static class AiExtensions{ [Description("This provides a list of routes throughout the application")] public static GeneratedRouteInfo[] GetGeneratedRouteInfo(this INavigator navigator) => [ new("Detail", "Navigate to the detail page", [new("Id", "The item identifier", "int", true), new("Name", "The display name", "string", true)]), new("MainPage", "", []), new("Modal", "", []) ];}The GeneratedRouteInfo record contains:
Route— the Shell route nameDescription— the description from[ShellMap](empty string if not set)Parameters— array ofGeneratedRouteParameterrecords for all[ShellProperty]properties
Each GeneratedRouteParameter contains:
ParameterName— the property nameDescription— from[ShellProperty("...")](empty string if not set)TypeName— the CLR type name (e.g.,"string","int")IsRequired— from[ShellProperty(required: ...)]
All [ShellProperty] properties are included in the parameters array, even those without descriptions.
When AI extensions are enabled (ShinyMauiShell_GenerateAiExtensions=true), the generator also produces GetAiToolApplicableGeneratedRoutes(), NavigateToRoute(), GetAiTools(), and AiRoutePrompt for AI integration. NavigateToRoute automatically converts string values from the AI to the target property type — int, bool, double, enums (case-insensitive), DateTime, Guid, and more. See AI Integration for details.
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, NavigationBuilderNavExtensions.g.cs, and AddGeneratedMaps) --> <ShinyMauiShell_GenerateNavExtensions>false</ShinyMauiShell_GenerateNavExtensions>
<!-- Enable AI extensions (disabled by default, requires Microsoft.Extensions.AI) --> <ShinyMauiShell_GenerateAiExtensions>true</ShinyMauiShell_GenerateAiExtensions>
<!-- Customize the generated class name (default: AiExtensions) --> <ShinyMauiShell_AiExtensionsClassName>MyAppRouteExtensions</ShinyMauiShell_AiExtensionsClassName>
<!-- Customize the AI navigate method name (default: NavigateToRoute) --> <ShinyMauiShell_AiNavigateMethodName>GoToPage</ShinyMauiShell_AiNavigateMethodName></PropertyGroup>| Property | Default | Controls |
|---|---|---|
ShinyMauiShell_GenerateRouteConstants | true | Routes.g.cs |
ShinyMauiShell_GenerateNavExtensions | true | NavigationExtensions.g.cs, NavigationBuilderNavExtensions.g.cs, AiExtensions.g.cs, and NavigationBuilderExtensions.g.cs (AddGeneratedMaps) |
ShinyMauiShell_GenerateAiExtensions | false | GetAiToolApplicableGeneratedRoutes, NavigateToRoute, GetAiTools(), and AiRoutePrompt. Requires Microsoft.Extensions.AI (SHINY003 error if missing) |
ShinyMauiShell_AiExtensionsClassName | AiExtensions | Class name for the route info/AI extensions class |
ShinyMauiShell_AiNavigateMethodName | NavigateToRoute | Method name for the AI-friendly navigate method |
NavigationBuilderExtensions.g.cs (AddGeneratedMaps()) and AiExtensions.g.cs are only generated when at least one [ShellMap] attribute is detected and ShinyMauiShell_GenerateNavExtensions is enabled. If maps are detected but nav extensions are disabled, the generator emits a SHINY002 warning.
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.