Sumapap.Navigations
2.0.0
dotnet add package Sumapap.Navigations --version 2.0.0
NuGet\Install-Package Sumapap.Navigations -Version 2.0.0
<PackageReference Include="Sumapap.Navigations" Version="2.0.0" />
<PackageVersion Include="Sumapap.Navigations" Version="2.0.0" />
<PackageReference Include="Sumapap.Navigations" />
paket add Sumapap.Navigations --version 2.0.0
#r "nuget: Sumapap.Navigations, 2.0.0"
#:package Sumapap.Navigations@2.0.0
#addin nuget:?package=Sumapap.Navigations&version=2.0.0
#tool nuget:?package=Sumapap.Navigations&version=2.0.0
Sumapap.Navigations
💡 Overview
Sumapap.Navigations provides a clean abstraction layer for navigation functionality in your applications. This lightweight library defines interfaces and contracts for navigating between pages or views, passing parameters, and managing navigation stacks, enabling you to build navigation solutions that are framework-agnostic and testable.
The package includes:
- INavigationService — Core interface for navigation operations
- INavigationAdapter — Adapter pattern interface for platform-specific implementations
- INavigationParams — Marker interface for type-safe parameter passing
📦 Ready-to-use implementation: For .NET MAUI applications, use Sumapap.Navigations.Maui which provides complete Page-based and Shell-based navigation adapters with zero boilerplate.
✨ Why use Sumapap.Navigations?
- Framework-Agnostic — Abstractions that work across different UI frameworks (MAUI, WPF, Avalonia, etc.)
- Testable — Mock navigation behavior easily in unit tests
- Type-Safe — Strongly-typed parameter objects prevent runtime errors
- Clean Architecture — Decouple navigation logic from view implementation
- Async-First — All navigation operations support async/await with cancellation tokens
- Adapter Pattern — Extensible design supporting multiple navigation strategies
🚀 Quick start
💡 For MAUI developers: Skip the manual implementation and use Sumapap.Navigations.Maui for instant Page and Shell navigation support.
For custom implementations:
- Add the package to your project:
dotnet add package Sumapap.Navigations
- Define a parameter object for navigation:
using Sumapap.Navigations.Abstractions;
public record UserDetailParams(int UserId) : INavigationParams;
- Inject and use the navigation service:
using Sumapap.Navigations.Abstractions;
public class MainViewModel
{
private readonly INavigationService _navigationService;
public MainViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
public async Task NavigateToUserDetailAsync(int userId)
{
var parameters = new UserDetailParams(userId);
await _navigationService.NavigateToAsync<UserDetailView>(parameters);
}
}
- Implement the navigation service or adapter in your UI layer (see examples below).
// Example implementation for MAUI
public class MauiNavigationService : INavigationService
{
private readonly IServiceProvider _serviceProvider;
public MauiNavigationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
{
var view = _serviceProvider.GetRequiredService<TView>();
await Shell.Current.Navigation.PushAsync((Page)(object)view!);
}
public async Task NavigateToAsync<TView>(INavigationParams param, CancellationToken cancellationToken = default)
{
var view = _serviceProvider.GetRequiredService<TView>();
// Pass parameters to the view or view model
if (view is Page page && page.BindingContext is INavigationAware aware)
{
aware.OnNavigatedTo(param);
}
await Shell.Current.Navigation.PushAsync(page);
}
public async Task BackAsync(CancellationToken cancellationToken = default)
{
await Shell.Current.Navigation.PopAsync();
}
public async Task BackAsync(INavigationParams param, CancellationToken cancellationToken = default)
{
await Shell.Current.Navigation.PopAsync();
}
public async Task NavigateToRootAsync<TView>(CancellationToken cancellationToken = default)
{
await Shell.Current.Navigation.PopToRootAsync();
await NavigateToAsync<TView>(cancellationToken);
}
public async Task NavigateToRootAsync<TView>(INavigationParams param, CancellationToken cancellationToken = default)
{
await Shell.Current.Navigation.PopToRootAsync();
await NavigateToAsync<TView>(param, cancellationToken);
}
}
- Register the service in your DI container:
services.AddSingleton<INavigationService, MauiNavigationService>();
🛠 Features and usage
INavigationService
The core navigation interface with comprehensive navigation operations:
public interface INavigationService
{
// Navigate forward without parameters
Task NavigateToAsync<TView>(CancellationToken cancellationToken = default);
// Navigate forward with parameters
Task NavigateToAsync<TView>(INavigationParams param, CancellationToken cancellationToken = default);
// Navigate backward without parameters
Task BackAsync(CancellationToken cancellationToken = default);
// Navigate backward with parameters
Task BackAsync(INavigationParams param, CancellationToken cancellationToken = default);
// Navigate to root and then to a specific view
Task NavigateToRootAsync<TView>(CancellationToken cancellationToken = default);
// Navigate to root and then to a specific view with parameters
Task NavigateToRootAsync<TView>(INavigationParams param, CancellationToken cancellationToken = default);
}
Forward navigation:
// Simple navigation
await _navigationService.NavigateToAsync<SettingsView>();
// Navigation with parameters
var params = new ProductDetailParams(productId: 123);
await _navigationService.NavigateToAsync<ProductDetailView>(params);
// With cancellation support
await _navigationService.NavigateToAsync<OrderView>(cancellationToken);
Backward navigation:
// Go back to previous view
await _navigationService.BackAsync();
// Go back with result parameters
var result = new OrderCompletedParams(orderId: 456, success: true);
await _navigationService.BackAsync(result);
Root navigation:
// Clear stack and navigate to home
await _navigationService.NavigateToRootAsync<HomeView>();
// Clear stack and navigate with parameters
var params = new ResetParams(reason: "Logout");
await _navigationService.NavigateToRootAsync<LoginView>(params);
INavigationParams
Marker interface for creating type-safe parameter objects:
// Simple parameter object
public record UserDetailParams(int UserId, string? Tab = null) : INavigationParams;
// Complex parameter object
public record EditProductParams : INavigationParams
{
public required int ProductId { get; init; }
public required string Mode { get; init; } // "edit" or "view"
public bool ShowComments { get; init; }
public Dictionary<string, object>? AdditionalData { get; init; }
}
// Usage
var editParams = new EditProductParams
{
ProductId = 789,
Mode = "edit",
ShowComments = true,
AdditionalData = new Dictionary<string, object>
{
["source"] = "search",
["highlight"] = true
}
};
await _navigationService.NavigateToAsync<ProductEditView>(editParams);
INavigationAdapter
The adapter pattern interface extends INavigationService with a capability check method. This allows multiple navigation strategies to coexist and be selected at runtime:
public interface INavigationAdapter : INavigationService
{
bool CanHandle(); // Returns true if this adapter can handle navigation in current context
}
Usage in implementations:
// Example: Shell-specific adapter
public class ShellNavigationAdapter : INavigationAdapter
{
public bool CanHandle() => Shell.Current is not null;
public async Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
{
await Shell.Current.GoToAsync(typeof(TView).Name);
}
// Implement other methods...
}
// Example: Page-specific adapter
public class PageNavigationAdapter : INavigationAdapter
{
public bool CanHandle() => Application.Current?.MainPage is not null;
public async Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
{
var page = ServiceProvider.GetRequiredService<TView>() as Page;
await Application.Current.MainPage.Navigation.PushAsync(page);
}
// Implement other methods...
}
// Service can choose adapter based on context
public class AdaptiveNavigationService : INavigationService
{
private readonly IEnumerable<INavigationAdapter> _adapters;
public AdaptiveNavigationService(IEnumerable<INavigationAdapter> adapters)
{
_adapters = adapters;
}
public Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
{
var adapter = _adapters.FirstOrDefault(a => a.CanHandle())
?? throw new InvalidOperationException("No suitable navigation adapter found");
return adapter.NavigateToAsync<TView>(cancellationToken);
}
// Implement other methods similarly...
}
💡 See it in action: Sumapap.Navigations.Maui includes
PageNavigationAdapterandShellNavigationAdapterimplementations you can use directly.
Navigation lifecycle integration
Create an interface for views that need navigation lifecycle notifications:
public interface INavigationAware
{
void OnNavigatedTo(INavigationParams? parameters);
void OnNavigatedFrom();
}
// ViewModel implementation
public class ProductDetailViewModel : INavigationAware
{
public void OnNavigatedTo(INavigationParams? parameters)
{
if (parameters is ProductDetailParams productParams)
{
LoadProduct(productParams.ProductId);
}
}
public void OnNavigatedFrom()
{
// Cleanup resources
}
}
Testing navigation
Mock the navigation service for unit tests:
using Moq;
using Xunit;
public class MainViewModelTests
{
[Fact]
public async Task NavigateToUserDetail_CallsNavigationService()
{
// Arrange
var mockNavigation = new Mock<INavigationService>();
var viewModel = new MainViewModel(mockNavigation.Object);
// Act
await viewModel.NavigateToUserDetailAsync(userId: 42);
// Assert
mockNavigation.Verify(
x => x.NavigateToAsync<UserDetailView>(
It.Is<UserDetailParams>(p => p.UserId == 42),
It.IsAny<CancellationToken>()),
Times.Once);
}
[Fact]
public async Task GoBack_CallsBackAsync()
{
// Arrange
var mockNavigation = new Mock<INavigationService>();
var viewModel = new DetailViewModel(mockNavigation.Object);
// Act
await viewModel.GoBackAsync();
// Assert
mockNavigation.Verify(
x => x.BackAsync(It.IsAny<CancellationToken>()),
Times.Once);
}
}
Dependency injection patterns
Register views for navigation:
// Register all views as transient (new instance per navigation)
services.AddTransient<HomeView>();
services.AddTransient<UserDetailView>();
services.AddTransient<ProductListView>();
services.AddTransient<SettingsView>();
// Register navigation service
services.AddSingleton<INavigationService, MauiNavigationService>();
Advanced registration with factory:
services.AddSingleton<INavigationService>(sp =>
{
var logger = sp.GetRequiredService<ILogger<MauiNavigationService>>();
return new MauiNavigationService(sp, logger);
});
⚠️ Notes & best practices
Implementation requirements
- Implementations must handle thread safety for navigation operations
- Use
ConfigureAwait(false)in library code to avoid deadlocks - Ensure UI operations are executed on the main thread (use framework-specific dispatchers)
Parameter design
- Use records for immutable parameter objects
- Keep parameter objects focused and cohesive
- Consider using optional parameters with default values for flexibility
- Avoid putting business logic in parameter objects
Navigation patterns
- Prefer parameterless overloads when no data needs to be passed
- Use
NavigateToRootAsyncfor clearing navigation history (logout, reset scenarios) - Return navigation results using
BackAsync(INavigationParams)for callback scenarios - Consider implementing a navigation history service for complex navigation flows
Cancellation support
- Always pass cancellation tokens to navigation methods in long-running operations
- Implementations should respect cancellation tokens and cancel animations/transitions
- Test cancellation scenarios in your implementations
Cross-platform considerations
- Different UI frameworks handle navigation differently (stack, modal, tabs)
- Implementations should adapt to platform-specific navigation patterns
- Consider creating framework-specific implementations:
Sumapap.Navigations.MauiSumapap.Navigations.WpfSumapap.Navigations.Avalonia
Security
- Validate navigation parameters in receiving views/view models
- Don't pass sensitive data (passwords, tokens) through navigation parameters
- Consider encrypting sensitive navigation state if needed
Performance
- Register views with appropriate lifetime (typically Transient)
- Cache navigation routes/paths in implementations
- Avoid heavy initialization in view constructors (use
OnNavigatedToinstead)
Example: Complete MAUI implementation
public class MauiNavigationService : INavigationService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<MauiNavigationService> _logger;
public MauiNavigationService(
IServiceProvider serviceProvider,
ILogger<MauiNavigationService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public async Task NavigateToAsync<TView>(CancellationToken cancellationToken = default)
{
await NavigateToAsync<TView>(null!, cancellationToken);
}
public async Task NavigateToAsync<TView>(
INavigationParams param,
CancellationToken cancellationToken = default)
{
try
{
var view = _serviceProvider.GetRequiredService<TView>() as Page;
if (view is null)
{
throw new InvalidOperationException($"{typeof(TView).Name} must inherit from Page");
}
NotifyNavigationLifecycle(view, param);
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.Navigation.PushAsync(view, animated: true);
});
_logger.LogInformation("Navigated to {ViewName}", typeof(TView).Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Navigation to {ViewName} failed", typeof(TView).Name);
throw;
}
}
public async Task BackAsync(CancellationToken cancellationToken = default)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.Navigation.PopAsync(animated: true);
});
}
public async Task BackAsync(INavigationParams param, CancellationToken cancellationToken = default)
{
var previousView = Shell.Current.Navigation.NavigationStack[^2];
NotifyNavigationLifecycle(previousView, param);
await BackAsync(cancellationToken);
}
public async Task NavigateToRootAsync<TView>(CancellationToken cancellationToken = default)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.Navigation.PopToRootAsync(animated: true);
});
await NavigateToAsync<TView>(cancellationToken);
}
public async Task NavigateToRootAsync<TView>(
INavigationParams param,
CancellationToken cancellationToken = default)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await Shell.Current.Navigation.PopToRootAsync(animated: true);
});
await NavigateToAsync<TView>(param, cancellationToken);
}
private void NotifyNavigationLifecycle(Page page, INavigationParams? parameters)
{
if (page.BindingContext is INavigationAware aware)
{
aware.OnNavigatedTo(parameters);
}
}
}
// Optional: Navigation aware interface
public interface INavigationAware
{
void OnNavigatedTo(INavigationParams? parameters);
void OnNavigatedFrom();
}
📦 Framework implementations
.NET MAUI
Package: Sumapap.Navigations.Maui
Complete implementation for .NET MAUI with two built-in adapters:
- PageNavigationAdapter — Traditional page-based navigation with modal support
- ShellNavigationAdapter — Modern Shell-based navigation with route patterns
dotnet add package Sumapap.Navigations.Maui
// Register in MauiProgram.cs
builder.Services.AddSumapap()
.WithNavigations(nav => nav.UsePageNavigation());
// or nav.UseShellNavigation()
// Use in ViewModels
await _navigationService.NavigateToAsync<DetailPage>();
See the full documentation for detailed usage, examples, and advanced scenarios.
Other frameworks
Implementations for WPF, Avalonia, Blazor, and other frameworks are planned. Contributions welcome!
⭐ License
This project is licensed under the MIT License. See the LICENSE file for details.
🚩 Contact
- GitHub: muhammadirwanto-dev
- Project URL: https://github.com/muhammadirwanto-dev/sumapap
☕ Support
If you find this project helpful, consider supporting the developer:
<a href="https://www.buymeacoffee.com/muhammadirwanto" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Sumapap.DependencyInjection (>= 2.0.0)
-
net8.0
- Sumapap.DependencyInjection (>= 2.0.0)
-
net9.0
- Sumapap.DependencyInjection (>= 2.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Sumapap.Navigations:
| Package | Downloads |
|---|---|
|
Sumapap.Navigations.Maui
The `Sumapap.Navigations.Maui` package provides a set of tools and abstractions for implementing navigation functionality in your MAUI applications. It includes features such as page routing, navigation stacks, and integration with various UI frameworks. With Sumapap.Navigations.Maui, you can easily manage your application's navigation flow while maintaining a clean and modular architecture. |
GitHub repositories
This package is not used by any popular GitHub repositories.