Skip to main content

Page Routing

Overview

The page routing system in MepApps.Dash.Inv.Batch.MiniMrpOrderCreation implements a sophisticated service-based discovery and registration pattern that enables dynamic view instantiation, dependency injection, and lifecycle management. The system leverages the MepDash module's infrastructure to provide seamless page transitions while maintaining loose coupling between components.

Key Concepts

  • Service-Based Registration: Views and ViewModels are registered as services in the DI container
  • Type-Safe Navigation: Generic methods ensure compile-time type safety
  • View Factory Pattern: Centralized view creation through ViewFactoryService
  • Lifecycle Management: Automatic initialization and disposal of view resources

Implementation Details

RegisterServiceProvider Pattern

The core of the routing system lies in the RegisterServiceProvider class, which inherits from ARegisterService:

// Services/RegisterServiceProvider.cs
public class RegisterServiceProvider : ARegisterService
{
public override void RegisterServices(IServiceCollection services)
{
// View registrations
services.AddTransient<MainView>();
services.AddTransient<WarehouseOrderingView>();
services.AddTransient<InvOrderingCompletionView>();

// Dialog registrations
services.AddTransient<SupplierSelectionView>();
services.AddTransient<SalesOrderAllocationsView>();
services.AddTransient<WipAllocationsView>();
services.AddTransient<SettingsDialogView>();

// ViewModel registrations
services.AddTransient<MainViewModel>();
services.AddTransient<WarehouseOrderingViewModel>();
services.AddTransient<InvOrderingCompletionViewModel>();

// Navigation services
services.AddTransient<NavToOrderCompletion>();
}
}

This registration pattern ensures:

  • All views are available through dependency injection
  • ViewModels are automatically injected into views
  • Services maintain proper lifetimes (Transient, Scoped, or Singleton)

View Factory Service

The ViewFactoryService provides centralized view creation:

// Services/ViewFactoryService.cs
public class ViewFactoryService : IViewFactoryService
{
private readonly IServiceProvider _serviceProvider;

public T CreateView<T>() where T : UserControl
{
var view = _serviceProvider.GetService<T>();
InitializeView(view);
return view;
}

private void InitializeView(UserControl view)
{
// Set DataContext if ViewModel exists
var viewModelType = GetViewModelType(view.GetType());
if (viewModelType != null)
{
var viewModel = _serviceProvider.GetService(viewModelType);
view.DataContext = viewModel;

// Call InitializeAsync if available
if (viewModel is IAsyncInitializable asyncInit)
{
asyncInit.InitializeAsync().ConfigureAwait(false);
}
}
}
}

Route Configuration and Naming Conventions

The system follows strict naming conventions for automatic discovery:

View-ViewModel Pairing

View: WarehouseOrderingView
ViewModel: WarehouseOrderingViewModel

View: InvOrderingCompletionView
ViewModel: InvOrderingCompletionViewModel

Namespace Organization

Views: MepApps.Dash.Inv.Batch.MiniMrpOrderCreation.Views
ViewModels: MepApps.Dash.Inv.Batch.MiniMrpOrderCreation.ViewModels
Dialog Views: MepApps.Dash.Inv.Batch.MiniMrpOrderCreation.Views.Dialog
Dialog ViewModels: MepApps.Dash.Inv.Batch.MiniMrpOrderCreation.ViewModels.Dialog

Parameter Passing Between Pages

The routing system supports multiple parameter passing strategies:

1. Direct Property Setting

// NavToOrderCompletion.cs
public void NavToOrderCompletionView(CreateOrdersResult results, RelayCommandAsync navBackCommand)
{
var completionView = _serviceProvider.GetService<InvOrderingCompletionView>();
var completionViewModel = completionView.DataContext as InvOrderingCompletionViewModel;

// Direct parameter passing
completionViewModel.SetOrderResult(results);
completionViewModel.NavigateBackCmd = navBackCommand;

_navigationService.NavigateToExistingUserControl(completionView);
}

2. Constructor Injection

// WarehouseOrderingViewModel constructor
public WarehouseOrderingViewModel(
ILogger<WarehouseOrderingViewModel> logger,
IExcelExportService excelExportService,
InvOrderingService invOrderingService,
NavToOrderCompletion navToOrderCompletion,
IMepSettingsService mepSettingsService)
{
// Services injected through constructor
_logger = logger;
_excelExportService = excelExportService;
_invOrderingService = invOrderingService;
}

3. Initialization Methods

// SupplierSelectionViewModel.cs
public void Initialize(string searchString)
{
SearchString = searchString;
SearchCmd.ExecuteAsync().ConfigureAwait(true);
}

Route Discovery Mechanism

The system discovers routes through multiple mechanisms:

1. Explicit Registration

services.AddTransient<WarehouseOrderingView>();

2. Convention-Based Discovery

// Views ending with "View" are automatically discoverable
// ViewModels ending with "ViewModel" are automatically paired

3. Attribute-Based Routing

[NavigationRoute("warehouse-ordering")]
public partial class WarehouseOrderingView : UserControl
{
// View implementation
}

Deep Linking Support

While primarily designed for internal navigation, the system supports deep linking through:

// Navigate directly to a specific view with parameters
public void NavigateToWarehouseOrdering(string warehouse, string supplier)
{
var view = _serviceProvider.GetService<WarehouseOrderingView>();
var viewModel = view.DataContext as WarehouseOrderingViewModel;

viewModel.SearchFilter.SelectedWarehouse = warehouse;
viewModel.SearchFilter.SelectedSupplier = supplier;

_navigationService.NavigateToExistingUserControl(view);
}

Route Constraints and Rules

The routing system enforces several constraints:

1. Business Rule Constraints

// From WarehouseOrderingViewModel.cs
case "SelectedBuyer":
OrderingEnabled = _selectedBuyer != null; // Enable ordering only with buyer selected
break;

2. Data Validation Constraints

// Navigation only allowed after validation
if (await ValidateDataAsync())
{
_navigationService.NavigateToNewUserControl<NextView>();
}

3. Permission-Based Constraints

// Check user permissions before allowing navigation
if (_userService.HasPermission("CreateOrders"))
{
EnableOrderCreationNavigation();
}

Examples

Example 1: Basic Page Registration

// RegisterServiceProvider.cs
public override void RegisterServices(IServiceCollection services)
{
// Register view
services.AddTransient<WarehouseOrderingView>();

// Register corresponding ViewModel
services.AddTransient<WarehouseOrderingViewModel>();

// Register supporting services
services.AddTransient<InvOrderingService>();
}

Example 2: Navigation with Complex Parameters

// Creating a navigation service for order completion
public class NavToOrderCompletion
{
private readonly IServiceProvider _serviceProvider;
private readonly IBasicMepNavigationService _navigationService;

public void NavToOrderCompletionView(CreateOrdersResult results, RelayCommandAsync navBackCommand)
{
// Get view from service provider
var completionView = _serviceProvider.GetService<InvOrderingCompletionView>();
var completionViewModel = completionView.DataContext as InvOrderingCompletionViewModel;

// Pass complex parameters
completionViewModel.SetOrderResult(results);
completionViewModel.NavigateBackCmd = navBackCommand;

// Navigate to the view
_navigationService.NavigateToExistingUserControl(completionView);
}
}

Example 3: Dynamic Route Resolution

// ViewFactoryService.cs - Dynamic view creation
public UserControl CreateViewByName(string viewName)
{
var assembly = Assembly.GetExecutingAssembly();
var viewType = assembly.GetTypes()
.FirstOrDefault(t => t.Name == viewName &&
t.IsSubclassOf(typeof(UserControl)));

if (viewType != null)
{
return (UserControl)_serviceProvider.GetService(viewType);
}

throw new InvalidOperationException($"View {viewName} not found");
}

Best Practices

Service Registration Guidelines

  • Register views as Transient for fresh instances
  • Register shared services as Singleton for performance
  • Use Scoped for request-specific services

Naming Convention Adherence

  • Always follow View/ViewModel naming patterns
  • Maintain consistent namespace organization
  • Use descriptive names that reflect functionality

Parameter Passing Strategies

  • Use constructor injection for required dependencies
  • Use initialization methods for optional parameters
  • Implement property setters for runtime configuration

Route Organization

  • Group related routes in the same namespace
  • Separate dialog routes from main workflow routes
  • Document complex routing scenarios

Common Pitfalls

Missing Service Registration

// BAD: Forgetting to register a view
_navigationService.NavigateToNewUserControl<UnregisteredView>(); // Throws exception

// GOOD: Ensure all views are registered
services.AddTransient<UnregisteredView>();

Circular Dependencies

// BAD: ViewA depends on ViewB, ViewB depends on ViewA
// GOOD: Use navigation service to break circular dependencies

Synchronous Initialization in Constructors

// BAD: Performing async operations in constructor
public MyViewModel()
{
LoadDataAsync().Wait(); // Can cause deadlocks
}

// GOOD: Use InitializeAsync pattern
public async Task InitializeAsync()
{
await LoadDataAsync();
}

Summary

The page routing system in MiniMrpOrderCreation provides a robust, type-safe mechanism for view discovery and navigation. Through service registration, naming conventions, and the view factory pattern, the system maintains loose coupling while ensuring reliable navigation. The parameter passing mechanisms and route constraints ensure data integrity throughout the navigation flow, while the deep linking support enables direct access to specific application states when needed.