Skip to main content

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 type
  • ArReversePaymentAddPaymentView - AR module, Reverse Payment feature, Add Payment action, View type
  • CustomerSelectionDialogView - Customer entity, Selection action, Dialog type, View type

ViewModel Naming Pattern

[Feature][Action]ViewModel

Examples:

  • ArReversePaymentQueueViewModel
  • ArReversePaymentAddPaymentViewModel
  • CustomerSelectionDialogViewModel

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;
}
}
// 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:

  1. 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;
}
  1. Service Provider Configuration
// All views and services registered
var services = new ServiceCollection();
services.AddTransient<IArReversePaymentQueueView>();
// ... more registrations
  1. 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;
}
}

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

  1. Always use service registration for views and ViewModels
  2. Follow naming conventions consistently
  3. Validate parameters before navigation
  4. Handle navigation failures gracefully
  5. Log all navigation events for debugging
  6. Cache routes where appropriate for performance
  7. Clean up resources when navigating away

Common Pitfalls

  1. Forgetting to register views in ServiceProvider
  2. Circular dependencies in navigation
  3. Memory leaks from event handlers
  4. Missing parameter validation
  5. Synchronous operations blocking navigation

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.