100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

Navigation in MVVM

How MVVM apps navigate between screens through an INavigationService abstraction, pass parameters and receive results, and support deep linking without ViewModels ever referencing Views.

ArchitectureIntermediate9 min readJul 10, 2026
Analogies

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.'

csharp
// .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

Was this page helpful?

Topics covered

#NET#MVVMDesignPatternStudyNotes#MicrosoftTechnologies#NavigationInMVVM#Navigation#MVVM#Passing#Parameters#StudyNotes#SkillVeris