Page Routing
Overview
Page routing in the AR Payment Reversal dashboard implements a sophisticated service-based discovery and registration pattern. This document details how pages are registered, discovered, and instantiated through the dependency injection container, along with the parameter passing mechanisms and view factory patterns that enable dynamic page creation.
Key Concepts
- Service Provider Pattern: Centralized registration of all navigable views
- View Factory Service: Dynamic view instantiation with dependency resolution
- Type-Safe Navigation: Compile-time safety for navigation targets
- Parameter Passing: Structured data transfer between pages
- Deep Linking Support: Direct navigation to specific views with context
Implementation Details
Service Registration Architecture
RegisterServiceProvider Pattern
The dashboard uses a custom RegisterServiceProvider to configure all services and views:
// MepApps.Dash.Ar.Maint.PaymentReversal/Services/RegisterServiceProvider.cs
public static class RegisterServiceProvider
{
public static IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
// Register Views
services.AddTransient<MainView>();
services.AddTransient<ArReversePaymentQueueView>();
services.AddTransient<ArReversePaymentAddPaymentView>();
services.AddTransient<ArReversePaymentCompletionView>();
services.AddTransient<CustomerSelectionDialogView>();
services.AddTransient<SettingsDialogView>();
// Register ViewModels
services.AddTransient<IMainViewModel, MainViewModel>();
services.AddTransient<IArReversePaymentQueueViewModel, ArReversePaymentQueueViewModel>();
services.AddTransient<IArReversePaymentAddPaymentViewModel, ArReversePaymentAddPaymentViewModel>();
services.AddTransient<IArReversePaymentCompletionViewModel, ArReversePaymentCompletionViewModel>();
// Register Services
services.AddSingleton<IArReversePaymentService, ArReversePaymentService>();
services.AddSingleton<ISysproPostService, SysproPostService>();
services.AddSingleton<ICustomFormService, CustomFormService>();
services.AddSingleton<IDatabaseValidationService, DatabaseValidationService>();
services.AddSingleton<IMepSettingsService, MepSettingsService>();
return services.BuildServiceProvider();
}
}
This pattern provides:
- Centralized configuration of all dependencies
- Clear service lifetimes (Transient, Singleton, Scoped)
- Type-safe registration with interfaces
- Easy testing through dependency injection
View Discovery and Instantiation
ViewFactoryService Implementation
// MepApps.Dash.Ar.Maint.PaymentReversal/Services/ViewFactoryService.cs
public class ViewFactoryService : IViewFactoryService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ViewFactoryService> _logger;
public ViewFactoryService(IServiceProvider serviceProvider, ILogger<ViewFactoryService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public T CreateView<T>() where T : class
{
try
{
_logger.LogTrace("Creating view of type {ViewType}", typeof(T).Name);
var view = _serviceProvider.GetService<T>();
if (view == null)
{
throw new InvalidOperationException($"View type {typeof(T).Name} is not registered");
}
// Auto-wire ViewModel if view implements IViewWithViewModel
if (view is IViewWithViewModel viewWithViewModel)
{
AutoWireViewModel(viewWithViewModel);
}
return view;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create view of type {ViewType}", typeof(T).Name);
throw;
}
}
private void AutoWireViewModel(IViewWithViewModel view)
{
var viewModelType = GetViewModelType(view.GetType());
if (viewModelType != null)
{
var viewModel = _serviceProvider.GetService(viewModelType);
view.DataContext = viewModel;
// Initialize ViewModel if it implements IInitializable
if (viewModel is IInitializable initializable)
{
_ = initializable.InitializeAsync();
}
}
}
}
Route Configuration and Naming Conventions
The dashboard follows strict naming conventions for routing:
View Naming Pattern
[Feature][Action][ViewType]
Examples:
ArReversePaymentQueueView- AR module, Reverse Payment feature, Queue action, View typeArReversePaymentAddPaymentView- AR module, Reverse Payment feature, Add Payment action, View typeCustomerSelectionDialogView- Customer entity, Selection action, Dialog type, View type
ViewModel Naming Pattern
[Feature][Action]ViewModel
Examples:
ArReversePaymentQueueViewModelArReversePaymentAddPaymentViewModelCustomerSelectionDialogViewModel
Parameter Passing Between Pages
Using Event Arguments
// MepApps.Dash.Ar.Maint.PaymentReversal/Models/EventArgModels/CustomerSelectedEventArgs.cs
public class CustomerSelectedEventArgs : EventArgs
{
public string CustomerId { get; set; }
public string CustomerName { get; set; }
public DateTime SelectionDate { get; set; }
public CustomerSelectedEventArgs(string customerId, string customerName)
{
CustomerId = customerId;
CustomerName = customerName;
SelectionDate = DateTime.Now;
}
}
Navigation with Parameters
// MepApps.Dash.Ar.Maint.PaymentReversal/ViewModels/ArReversePaymentQueueViewModel.cs
public event EventHandler<ReversePaymentPostEventArgs> PostCompletedEvent;
private async Task ReversePaymentsAsync()
{
var completionObject = await _service.ReversePaymentsAsync(...);
PostCompletedEvent?.Invoke(this, new ReversePaymentPostEventArgs(completionObject));
}
// In the view handler
private void OnPostCompleted(object sender, ReversePaymentPostEventArgs e)
{
var completionView = _viewFactory.CreateView<ArReversePaymentCompletionView>();
var completionViewModel = completionView.DataContext as IArReversePaymentCompletionViewModel;
completionViewModel.SetCompletionObject(e.CompletionObject);
_navigationService.NavigateTo(completionView);
}
Deep Linking Implementation
The architecture supports deep linking through URL-style routing:
// Deep link to specific customer payment
public void NavigateToCustomerPayment(string customerId, string checkNumber)
{
var parameters = new Dictionary<string, object>
{
["CustomerId"] = customerId,
["CheckNumber"] = checkNumber
};
_navigationService.NavigateTo<ArReversePaymentAddPaymentView>(parameters);
}
Route Registration Process
The route registration follows this lifecycle:
- Application Startup
// MepApps.Dash.Ar.Maint.PaymentReversal/Views/MainView.xaml.cs (Lines 41-50)
public MainView(ISharedShellInterface sharedShellInterface, ...)
{
System.Data.Entity.Database.SetInitializer<Db.PluginSysproDataContext>(null);
// Build service provider
_mepPluginServiceProvider = RegisterServiceProvider.BuildServiceProvider();
_mepPluginServiceProviderStatic = _mepPluginServiceProvider;
}
- Service Provider Configuration
// All views and services registered
var services = new ServiceCollection();
services.AddTransient<IArReversePaymentQueueView>();
// ... more registrations
- Navigation Service Initialization
// Navigation service becomes available
var navigationService = _serviceProvider.GetService<IBasicMepNavigationService>();
Route Constraints and Rules
The dashboard implements several routing constraints:
1. Authentication Requirements
public bool CanNavigate(Type viewType)
{
// Check if user has permission for this view
if (RequiresAuthentication(viewType))
{
return _authService.IsAuthenticated;
}
return true;
}
2. Data Dependencies
// Cannot navigate to completion view without completion data
if (completionObject == null)
{
_logger.LogWarning("Cannot navigate to completion view without data");
return;
}
3. State Validation
// Ensure queue has items before allowing reversal
if (!ReversePaymentQueueHeaders?.Any() ?? true)
{
_logger.LogWarning("No payments in queue for reversal");
return;
}
Dynamic Route Generation
The system supports dynamic route generation based on configuration:
// MepApps.Dash.Ar.Maint.PaymentReversal/Services/MepSettingsService.cs
public class MepSettingsService : IMepSettingsService
{
public IEnumerable<NavigationRoute> GetDynamicRoutes()
{
var settings = LoadSettings();
var routes = new List<NavigationRoute>();
if (settings.EnableAdvancedFeatures)
{
routes.Add(new NavigationRoute
{
Name = "AdvancedSettings",
ViewType = typeof(AdvancedSettingsView),
RequiresPermission = "Admin"
});
}
return routes;
}
}
Route Caching and Performance
The architecture implements route caching for performance:
public class RouteCache
{
private readonly Dictionary<string, Type> _routeCache = new();
public Type GetViewType(string routeName)
{
if (_routeCache.TryGetValue(routeName, out var viewType))
{
return viewType;
}
// Discover and cache route
viewType = DiscoverRoute(routeName);
_routeCache[routeName] = viewType;
return viewType;
}
}
Navigation History and Back Stack
The system maintains navigation history for back navigation:
public class NavigationHistory
{
private readonly Stack<NavigationEntry> _backStack = new();
public void Push(NavigationEntry entry)
{
_backStack.Push(entry);
}
public NavigationEntry Pop()
{
return _backStack.Count > 0 ? _backStack.Pop() : null;
}
public bool CanGoBack => _backStack.Count > 0;
}
Route Testing Strategies
Unit Testing Routes
[TestMethod]
public void AllViewsShouldBeRegistered()
{
var serviceProvider = RegisterServiceProvider.BuildServiceProvider();
var viewTypes = new[]
{
typeof(ArReversePaymentQueueView),
typeof(ArReversePaymentAddPaymentView),
typeof(ArReversePaymentCompletionView)
};
foreach (var viewType in viewTypes)
{
var view = serviceProvider.GetService(viewType);
Assert.IsNotNull(view, $"View {viewType.Name} not registered");
}
}
Best Practices
- Always use service registration for views and ViewModels
- Follow naming conventions consistently
- Validate parameters before navigation
- Handle navigation failures gracefully
- Log all navigation events for debugging
- Cache routes where appropriate for performance
- Clean up resources when navigating away
Common Pitfalls
- Forgetting to register views in ServiceProvider
- Circular dependencies in navigation
- Memory leaks from event handlers
- Missing parameter validation
- Synchronous operations blocking navigation
Related Documentation
- Navigation Architecture - Overall navigation strategy
- Navigation Events - Event-driven navigation
- Dialog Navigation - Modal dialog patterns
- Navigation Best Practices - Guidelines and tips
Summary
The page routing system in the AR Payment Reversal dashboard provides a robust, type-safe, and performant mechanism for managing view navigation. Through the combination of dependency injection, naming conventions, and the ViewFactory pattern, the system ensures maintainable and testable navigation code while supporting advanced features like deep linking and dynamic route generation.