Navigation in MVVM
Navigation — moving from one screen to another — is inherently a View concern in most UI frameworks (a Frame.Navigate call, a NavigationStack push), yet MVVM insists that ViewModels contain no reference to Views. The resolution is an INavigationService abstraction: the ViewModel calls something like '_navigationService.NavigateToAsync("OrderDetails", parameters)' against an interface, and a framework-specific implementation behind that interface performs the actual Frame/NavigationPage manipulation. This keeps the ViewModel portable and testable — a unit test can assert that NavigateToAsync was called with the correct route and parameters without ever instantiating a real page — while the concrete navigation mechanics (WPF's NavigationService, .NET MAUI Shell, Prism's region navigation) stay isolated in the View layer.
Cricket analogy: It's like a captain calling for a specific field change through the walkie-talkie to the twelfth man rather than personally walking out to reposition every fielder — the captain (ViewModel) issues the instruction through an abstraction, and the actual physical repositioning (View mechanics) happens elsewhere.
Passing Parameters and Handling Results
Real navigation needs to carry data — navigating to an OrderDetailsViewModel requires an OrderId, and returning from a modal picker needs to hand a selected value back to the caller. Frameworks solve this differently: .NET MAUI Shell supports query-property attributes and a Dictionary<string,object> parameter bag; Prism passes an INavigationParameters object and exposes IConfirmNavigationRequest/OnNavigatedTo for the destination ViewModel to read incoming values; WPF applications often roll a custom parameter object passed through a NavigationService wrapper. A well-designed navigation layer also supports a 'result' channel — an async Task<T> that resolves once the destination page is dismissed, letting a caller await NavigateToAsync<Address>("AddressPicker") and receive the chosen Address directly, rather than wiring up a one-off message just for that single interaction.
Cricket analogy: It's like a DRS review carrying specific data — which decision, which over, which ball — to the third umpire, and the review resolves with a result (out/not out) sent back to the on-field umpire, rather than a vague 'please review something.'
// .NET MAUI Shell: passing parameters and awaiting a result
public partial class OrderListViewModel : ObservableObject
{
private readonly INavigationService _navigation;
[RelayCommand]
private async Task OpenOrderAsync(int orderId)
{
var parameters = new Dictionary<string, object> { ["OrderId"] = orderId };
await _navigation.NavigateToAsync("OrderDetails", parameters);
}
[RelayCommand]
private async Task PickAddressAsync()
{
// Awaits a modal push and receives the picked Address back
Address? selected = await _navigation.NavigateForResultAsync<Address>("AddressPicker");
if (selected is not null)
ShippingAddress = selected;
}
}
[QueryProperty(nameof(OrderId), "OrderId")]
public partial class OrderDetailsViewModel : ObservableObject
{
[ObservableProperty] private int orderId;
partial void OnOrderIdChanged(int value) => _ = LoadOrderAsync(value);
}Deep Linking and Back-Stack Management
Deep linking lets navigation be driven from outside the app — a push notification, an email link, or an app-launch URI — by resolving a route string like 'app://orders/482' directly to the OrderDetailsViewModel with orderId=482 pre-populated, without the user manually navigating through the app's normal screen flow. This requires the navigation service to expose a route-registration table mapping string routes to ViewModel/View pairs, checked both at explicit in-app NavigateToAsync calls and at cold-start URI handling. Back-stack management is the related concern of what happens on the hardware back button or a swipe-back gesture: should the stack simply pop one entry, or does a multi-step wizard need to intercept back navigation to show a confirmation dialog first (commonly exposed as an IConfirmNavigation or OnNavigatingFrom hook that can cancel the pop)?
Cricket analogy: It's like a broadcaster's app letting a viewer tap a push notification 'Kohli just hit a century' and land directly on that specific ball's replay, skipping the normal menu-by-menu navigation through match, innings, and over.
In Prism and .NET MAUI Shell, route registration is typically centralized in one place (App.xaml.cs or AppShell.xaml.cs) so every valid deep-link target is auditable in a single file rather than scattered across the codebase.
- An INavigationService abstraction lets ViewModels trigger navigation without referencing Views directly.
- Navigation parameters carry data to the destination (e.g., an OrderId), typically via a dictionary or a framework-specific parameters object.
- A result channel (async Task<T> from a navigation call) supports modal picker-style interactions cleanly.
- Deep linking resolves an external route string directly to a ViewModel/View pair, bypassing normal in-app navigation flow.
- Route tables should be centralized so all valid navigation targets are auditable in one place.
- Back-stack management includes hooks like OnNavigatingFrom/IConfirmNavigation to intercept or cancel back navigation.
Practice what you learned
1. Why do MVVM applications use an INavigationService abstraction instead of calling Frame.Navigate directly from a ViewModel?
2. What does a navigation 'result channel' (e.g., an async Task<T> returned from a navigation call) typically support?
3. What is deep linking in the context of MVVM navigation?
4. What is a common hook used to intercept or cancel back navigation, such as to warn about unsaved changes?
5. Why is centralizing route registration in one file recommended?
Was this page helpful?
You May Also Like
Dependency Injection in MVVM
How MVVM ViewModels receive their collaborators — data services, navigation, dialogs — via constructor injection instead of creating them directly, and why that makes apps testable and swappable.
Messaging and EventAggregator
How MVVM ViewModels communicate through a decoupled publish/subscribe message bus instead of direct references, and how to avoid the memory leaks that naive implementations cause.
MVVM in Blazor
How to apply MVVM in Blazor components despite the absence of a WPF-style binding engine, bridging ViewModel notifications into Blazor's render pipeline and handling Server vs WebAssembly differences.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics