Navigation Best Practices
Overview
This document outlines the best practices, patterns, and guidelines for implementing and maintaining navigation in the MepApps.Dash.Inv.Batch.MiniMrpOrderCreation dashboard. Following these practices ensures consistent user experience, maintainable code, and optimal performance across the application's navigation infrastructure.
Key Concepts
- Navigation Anti-Patterns: Common mistakes to avoid
- Memory Management: Preventing leaks during navigation
- Testing Strategies: Ensuring reliable navigation flows
- Performance Optimization: Keeping navigation responsive
- Accessibility: Making navigation usable for all users
Implementation Details
Navigation Anti-Patterns to Avoid
1. Direct View Instantiation
// BAD: Creating views directly
var view = new WarehouseOrderingView();
MainContent = view;
// GOOD: Using navigation service
var navigationService = _serviceProvider.GetService<IBasicMepNavigationService>();
navigationService.NavigateToNewUserControl<WarehouseOrderingView>();
2. Circular Navigation Dependencies
// BAD: ViewA depends on ViewB, ViewB depends on ViewA
public class ViewAViewModel
{
private ViewBViewModel _viewB; // Direct dependency
}
public class ViewBViewModel
{
private ViewAViewModel _viewA; // Circular dependency
}
// GOOD: Use navigation service to break dependencies
public class ViewAViewModel
{
private IBasicMepNavigationService _navigationService;
private void NavigateToViewB()
{
_navigationService.NavigateToNewUserControl<ViewB>();
}
}
3. Synchronous Navigation in Async Context
// BAD: Blocking async operations
public async Task LoadDataAndNavigate()
{
var data = await LoadDataAsync();
NavigateToNextView().Wait(); // Potential deadlock
}
// GOOD: Proper async flow
public async Task LoadDataAndNavigate()
{
var data = await LoadDataAsync();
await NavigateToNextViewAsync();
}
Memory Management During Navigation
1. Event Handler Cleanup
// From WarehouseOrderingViewModel.cs - Proper event cleanup
public void Dispose()
{
if (!_disposed)
{
// Unsubscribe from events
SelectedItems.ListChanged -= SelectedItems_ListChanged;
PropertyChanged -= WarehouseOrderingViewModel_PropertyChanged;
// Clear collections
SelectedItems?.Clear();
Data = null;
// Dispose child objects
PagedData?.Dispose();
_disposed = true;
}
}
2. Weak Reference Pattern
// Use weak references for cross-view model communication
public class NavigationCoordinator
{
private readonly List<WeakReference> _viewModels = new();
public void RegisterViewModel(BaseViewModel viewModel)
{
_viewModels.Add(new WeakReference(viewModel));
}
public void NotifyAll(string message)
{
// Clean up dead references
_viewModels.RemoveAll(wr => !wr.IsAlive);
// Notify live view models
foreach (var wr in _viewModels)
{
if (wr.Target is BaseViewModel vm)
{
vm.HandleNotification(message);
}
}
}
}
3. Collection Management
// Clear large collections when navigating away
public override void OnNavigatingFrom(NavigationContext context)
{
// Clear data that won't be needed
if (_largeDataCollection?.Count > 1000)
{
_largeDataCollection.Clear();
_largeDataCollection = null;
}
// Keep essential state for back navigation
context.Parameters["SelectedFilter"] = CurrentFilter;
base.OnNavigatingFrom(context);
}
Navigation Testing Guidelines
1. Unit Testing Navigation Logic
[TestClass]
public class NavigationTests
{
[TestMethod]
public async Task NavigateToCompletion_WithValidResults_NavigatesToCompletionView()
{
// Arrange
var navigationService = new Mock<IBasicMepNavigationService>();
var navToCompletion = new NavToOrderCompletion(null, navigationService.Object);
var results = new CreateOrdersResult { IsSuccess = true };
// Act
navToCompletion.NavToOrderCompletionView(results, null);
// Assert
navigationService.Verify(n => n.NavigateToExistingUserControl(
It.IsAny<InvOrderingCompletionView>()), Times.Once);
}
}
2. Integration Testing Navigation Flows
[TestMethod]
public async Task CompleteOrderWorkflow_NavigatesThroughAllSteps()
{
// Arrange
var app = new TestApplication();
await app.StartAsync();
// Act & Assert - Step 1: Main View
Assert.IsInstanceOfType(app.CurrentView, typeof(MainView));
// Step 2: Navigate to Warehouse Ordering
await app.NavigateAsync<WarehouseOrderingView>();
Assert.IsInstanceOfType(app.CurrentView, typeof(WarehouseOrderingView));
// Step 3: Select items and create orders
await app.SelectItemsAndCreateOrders();
// Step 4: Verify navigation to completion
Assert.IsInstanceOfType(app.CurrentView, typeof(InvOrderingCompletionView));
}
3. Navigation State Testing
[TestMethod]
public void NavigationState_PreservedDuringNavigation()
{
// Arrange
var viewModel = new WarehouseOrderingViewModel();
viewModel.SelectedWarehouse = "WH01";
viewModel.SelectedSupplier = "SUP001";
// Act
var state = viewModel.SaveNavigationState();
viewModel.RestoreNavigationState(state);
// Assert
Assert.AreEqual("WH01", viewModel.SelectedWarehouse);
Assert.AreEqual("SUP001", viewModel.SelectedSupplier);
}
Accessibility Considerations
1. Keyboard Navigation Support
// Implement keyboard shortcuts for navigation
public class KeyboardNavigationHandler
{
public void RegisterKeyboardShortcuts()
{
// Alt+W: Navigate to Warehouse Ordering
RegisterShortcut(Key.W, ModifierKeys.Alt, () =>
NavigateToView<WarehouseOrderingView>());
// Alt+C: Navigate to Completion
RegisterShortcut(Key.C, ModifierKeys.Alt, () =>
NavigateToView<InvOrderingCompletionView>());
// Escape: Navigate back
RegisterShortcut(Key.Escape, ModifierKeys.None, () =>
NavigateBack());
}
}
2. Focus Management
// Properly manage focus during navigation
public override void OnNavigatedTo(NavigationContext context)
{
base.OnNavigatedTo(context);
// Set focus to the first interactive element
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
{
var firstInput = FindFirstFocusableElement();
firstInput?.Focus();
}), DispatcherPriority.Input);
}
3. Screen Reader Support
<!-- Provide proper automation properties -->
<Button x:Name="NavigateButton"
Content="Create Orders"
AutomationProperties.Name="Navigate to create orders"
AutomationProperties.HelpText="Opens the order creation screen with selected items"
AutomationProperties.AcceleratorKey="Alt+O"/>
Performance Optimization Tips
1. Lazy Loading Views
// Load views only when needed
public class LazyNavigationService : IBasicMepNavigationService
{
private readonly Dictionary<Type, Lazy<UserControl>> _viewCache = new();
public void NavigateToNewUserControl<T>() where T : UserControl
{
if (!_viewCache.ContainsKey(typeof(T)))
{
_viewCache[typeof(T)] = new Lazy<UserControl>(() =>
_serviceProvider.GetService<T>());
}
var view = _viewCache[typeof(T)].Value;
NavigateToView(view);
}
}
2. View Caching Strategy
// Cache frequently accessed views
public class ViewCacheManager
{
private readonly Dictionary<Type, object> _cache = new();
private readonly int _maxCacheSize = 5;
public T GetOrCreateView<T>() where T : UserControl
{
if (_cache.ContainsKey(typeof(T)))
{
return (T)_cache[typeof(T)];
}
var view = _serviceProvider.GetService<T>();
// Implement LRU cache
if (_cache.Count >= _maxCacheSize)
{
RemoveLeastRecentlyUsed();
}
_cache[typeof(T)] = view;
return view;
}
}
3. Async Data Loading
// From MainViewModel.cs (lines 40-72) - Async initialization pattern
public async Task InitializeAsync()
{
try
{
// Initialize services asynchronously
await _mepSettingsService.InitializeDefaultAsync().ConfigureAwait(true);
// Perform database operations asynchronously
await _databaseValidationService.InitializeAsync().ConfigureAwait(true);
await _databaseValidationService.CreateMissingDatabaseViewsAsync().ConfigureAwait(true);
await _databaseValidationService.ValidateAndUpdateExistingViewsAsync().ConfigureAwait(true);
// Navigate after initialization complete
var navigationService = MainView.MepPluginServiceProvider.GetService<IBasicMepNavigationService>();
navigationService.NavigateToNewUserControl<WarehouseOrderingView>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error initializing MainViewModel");
throw;
}
}
Error Handling During Navigation
1. Graceful Degradation
// Handle navigation failures gracefully
public async Task SafeNavigateAsync<T>() where T : UserControl
{
try
{
await NavigateAsync<T>();
}
catch (NavigationException ex)
{
_logger.LogError(ex, "Navigation to {ViewType} failed", typeof(T).Name);
// Show user-friendly error
await ShowErrorDialogAsync(
"Navigation Error",
"Unable to open the requested screen. Please try again.");
// Attempt fallback navigation
await NavigateToFallbackViewAsync();
}
}
2. Navigation Validation
// Validate before allowing navigation
public bool CanNavigateToOrderCompletion()
{
// Check prerequisites
if (SelectedItems?.Any() != true)
{
ShowMessage("Please select items before proceeding");
return false;
}
if (SelectedBuyer == null)
{
ShowMessage("Please select a buyer before creating orders");
return false;
}
// Validate business rules
if (!ValidateOrderCreationRules())
{
return false;
}
return true;
}
3. State Recovery
// Recover from navigation state corruption
public class NavigationStateManager
{
public void SaveState()
{
try
{
var state = SerializeCurrentState();
File.WriteAllText(StateFilePath, state);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save navigation state");
}
}
public void RestoreState()
{
try
{
if (File.Exists(StateFilePath))
{
var state = File.ReadAllText(StateFilePath);
DeserializeAndApplyState(state);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to restore navigation state");
// Start with default state
NavigateToDefaultView();
}
}
}
Examples
Example 1: Complete Navigation Setup
// Comprehensive navigation implementation
public class NavigationManager
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<NavigationManager> _logger;
private readonly Stack<Type> _navigationHistory = new();
public async Task NavigateToAsync<T>() where T : UserControl
{
try
{
// Validate navigation
if (!CanNavigate<T>())
return;
// Save current state
SaveCurrentViewState();
// Add to history
_navigationHistory.Push(typeof(T));
// Perform navigation
var navigationService = _serviceProvider.GetService<IBasicMepNavigationService>();
await Task.Run(() => navigationService.NavigateToNewUserControl<T>());
// Log navigation
_logger.LogInformation("Navigated to {ViewType}", typeof(T).Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Navigation failed");
await HandleNavigationError(ex);
}
}
public async Task NavigateBackAsync()
{
if (_navigationHistory.Count > 1)
{
_navigationHistory.Pop(); // Remove current
var previousType = _navigationHistory.Peek();
await NavigateToTypeAsync(previousType);
}
}
}
Example 2: Navigation with Loading States
// Show loading during navigation transitions
public async Task NavigateWithLoadingAsync<T>() where T : UserControl
{
ShowLoadingOverlay("Loading...");
try
{
// Prepare data for next view
var data = await PrepareDataForViewAsync<T>();
// Navigate
var view = _serviceProvider.GetService<T>();
var viewModel = view.DataContext as IInitializableViewModel;
if (viewModel != null)
{
await viewModel.InitializeAsync(data);
}
_navigationService.NavigateToExistingUserControl(view);
}
finally
{
HideLoadingOverlay();
}
}
Example 3: Navigation Guards Implementation
// Implement navigation guards
public class NavigationGuard
{
public async Task<bool> CanDeactivateAsync(BaseViewModel viewModel)
{
if (viewModel.HasUnsavedChanges)
{
var result = await ShowConfirmationAsync(
"Unsaved Changes",
"You have unsaved changes. Do you want to save before leaving?");
switch (result)
{
case MessageBoxResult.Yes:
return await viewModel.SaveChangesAsync();
case MessageBoxResult.No:
return true;
case MessageBoxResult.Cancel:
return false;
}
}
return true;
}
}
Common Pitfalls
Navigation During Construction
// BAD: Navigation in constructor
public MyViewModel()
{
if (SomeCondition)
NavigateToOtherView(); // Can cause issues
}
// GOOD: Navigation in initialization
public async Task InitializeAsync()
{
if (SomeCondition)
await NavigateToOtherViewAsync();
}
Ignoring Navigation Context
// BAD: Not preserving context
public void NavigateAway()
{
_navigationService.NavigateToNewUserControl<NextView>();
// Lost all state!
}
// GOOD: Preserve necessary context
public void NavigateAway()
{
var context = new NavigationContext
{
["SelectedItems"] = SelectedItems.ToList(),
["CurrentFilter"] = CurrentFilter
};
_navigationService.NavigateToNewUserControl<NextView>(context);
}
Excessive View Caching
// BAD: Caching everything
private readonly Dictionary<Type, UserControl> _allViews = new();
// GOOD: Selective caching with limits
private readonly LRUCache<Type, UserControl> _viewCache = new(maxSize: 5);
Related Documentation
Summary
Following these navigation best practices ensures a robust, maintainable, and user-friendly navigation system in the MiniMrpOrderCreation dashboard. By avoiding anti-patterns, properly managing memory, implementing comprehensive testing, and optimizing performance, the navigation infrastructure remains reliable and efficient. The focus on accessibility and error handling ensures that all users can successfully navigate the application, while the structured approach to state management and view lifecycle maintains application stability throughout complex navigation scenarios.