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();
}
Related Documentation
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.