SimpleRouter is a lightweight and well-architected routing library for .NET applications, particularly designed for MVVM desktop applications using Avalonia UI. The library provides core navigation functionality with a clean API and solid foundation. However, to be considered production-ready, several improvements and enhancements are needed across code quality, features, documentation, and testing.
Current State: ✅ Functional core library with good test coverage
Production Ready:
Recommendation: Address critical issues first, then incrementally add missing features
- Library Overview
- Strengths
- Critical Issues
- Code Quality Issues
- Missing Core Features
- Missing Documentation
- Testing Gaps
- DevOps & Infrastructure
- Implementation Checklist
- Priority Matrix
SimpleRouter is a navigation/routing library that provides:
- Stack-based navigation with forward/back support
- Route lifecycle events (OnRouteChanging, OnRouteChanged)
- Factory-based route creation with parameter support
- Platform-agnostic core with Avalonia-specific extensions
- View resolution and hosting for Avalonia applications
Core Layer (SimpleRouter):
├── IRoute - Route interface
├── IRouter - Router interface
├── IRouterHost - Host interface
├── Router - Main router implementation
└── Events - Navigation event arguments
Avalonia Layer (SimpleRouter.Avalonia):
├── RouteViewHost - Content control for displaying routes
└── ViewLocatorBase - Abstract view locator
- Desktop application developers using MVVM pattern
- Avalonia UI developers (primary)
- WPF developers (with custom implementation)
- Framework-agnostic usage possible
✅ Clean Architecture
- Well-defined interfaces with single responsibility
- Good separation of concerns between core and UI layers
- Framework-agnostic core design
✅ Good Test Coverage
- 26 unit tests covering main scenarios
- Uses mocking appropriately
- Tests pass consistently
✅ Simple API
- Intuitive navigation methods (NavigateTo, NavigateBack, NavigateToAndReset)
- Generic and type-based overloads
- Event-driven design
✅ NuGet Package Ready
- Properly configured .csproj files
- Multi-targeting (netstandard2.1, net8.0)
- Package metadata configured
✅ CI/CD Setup
- GitHub Actions for testing
- Automated builds on PR and push
Location: SimpleRouter.Avalonia/RouteViewHost.cs:30
Issue: Event handler subscribed but never unsubscribed when Router changes or control is disposed.
router.OnRouteChanged += Router_OnRouteChanged;Impact: Memory leaks in applications with dynamic router changes.
Fix Required:
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof(Router):
// Unsubscribe from old router
if (change.OldValue is IRouter oldRouter)
{
oldRouter.OnRouteChanged -= Router_OnRouteChanged;
}
// Subscribe to new router
if (change.NewValue is IRouter newRouter)
{
newRouter.OnRouteChanged += Router_OnRouteChanged;
NavigateToRoute(newRouter.Current);
}
break;
}
}Location: SimpleRouter/Router.cs:12-15
Issue: Constructor accepts stackLimit parameter but never assigns it to _stackLimit field.
public Router(RouteFactory createRoute, int stackLimit = 50)
{
_createRoute = createRoute ?? throw new ArgumentNullException(nameof(createRoute));
// BUG: stackLimit parameter is ignored!
}Impact: Stack limit is always 50 regardless of constructor parameter.
Fix Required:
public Router(RouteFactory createRoute, int stackLimit = 50)
{
_createRoute = createRoute ?? throw new ArgumentNullException(nameof(createRoute));
_stackLimit = stackLimit;
}Location: SimpleRouter/Router.cs:1
Issue:
using System.Net.Sockets; // Not used anywhereFix: Remove the unused import.
Locations: Test files have 3 warnings for null literal conversions
Files:
AvaloniaRouteViewHostTests.cs:17,53RouterTests.cs:303
Fix: Use null! or proper nullable annotations in tests.
Issue: Many public APIs lack XML documentation comments.
Examples:
IRoute.RouteName- No descriptionIRoute.RouterHost- No descriptionRoutermethods - Minimal documentationEvents.cs- No documentation on event args properties
Impact: IntelliSense provides limited help to library users.
Location: Throughout Router.cs
Examples:
- Stack limit default of 50 (what's the rationale?)
- Stack index calculations using literals
Fix: Add constants with descriptive names and documentation.
Description: No way to prevent or intercept navigation.
Use Cases:
- Authorization checks before navigating
- Unsaved changes confirmation
- Conditional navigation based on application state
Proposed API:
public interface INavigationGuard
{
Task<bool> CanNavigateFrom(IRoute? current, IRoute? next);
Task<bool> CanNavigateTo(IRoute? current, IRoute? next);
}Description: No structured way to pass parameters beyond constructor args.
Use Cases:
- Deep linking
- Bookmarkable routes
- Passing optional parameters
Proposed API:
public interface IRoute
{
string RouteName { get; }
IRouterHost RouterHost { get; }
IReadOnlyDictionary<string, object>? Parameters { get; } // NEW
}
// Usage:
router.NavigateTo<UserProfileRoute>(new { userId = "123", tab = "settings" });Description: No way to cancel in-progress navigation.
Use Cases:
- Cancel navigation if user navigates away quickly
- Cancel if validation fails asynchronously
Proposed API:
public sealed class RouteChangingEventArgs(IRoute? previous, IRoute? next) : EventArgs
{
public IRoute? Previous { get; } = previous;
public IRoute? Next { get; } = next;
public bool Cancel { get; set; } // NEW
}Description: All navigation is synchronous.
Use Cases:
- Load data before navigating
- Async validation
- Async guards/middleware
Proposed API:
Task<IRoute> NavigateToAsync<T>() where T : IRoute;
Task<IRoute?> NavigateBackAsync();Description: No way to pass transient state between routes without constructor parameters.
Use Cases:
- Passing selected item from list to detail view
- Returning results from modal routes
- Wizard-style navigation with accumulated state
Proposed API:
router.NavigateTo<DetailRoute>(state: selectedItem);Description: No URI-based routing or route registration.
Use Cases:
- Restore application state from URL
- External navigation (e.g., from email links)
- Bookmarking specific views
Proposed API:
router.RegisterRoute<HomeRoute>("home");
router.RegisterRoute<UserProfileRoute>("users/{userId}");
router.NavigateToUri("users/123");Description: No hooks for route entry/exit logic.
Use Cases:
- Initialize data when entering route
- Cleanup resources when leaving route
- Track analytics
Proposed API:
public interface IRoutable : IRoute
{
Task OnNavigatedTo(NavigationContext context);
Task OnNavigatedFrom(NavigationContext context);
}Description: Basic stack only, no advanced history manipulation.
Missing:
- Clear history
- Remove specific items from stack
- Insert items at specific positions
- History length limits per route type
Description: No built-in logging or diagnostic information.
Needed:
- Navigation event logging
- Performance metrics
- Error tracking
- Debug mode with detailed traces
Proposed:
public interface IRouterLogger
{
void LogNavigation(IRoute? from, IRoute? to);
void LogNavigationFailed(IRoute? from, IRoute? to, Exception ex);
}Description: New route instance created on every navigation.
Use Cases:
- Reuse existing route instances
- Preserve scroll position and UI state
- Reduce memory allocations
Proposed:
public enum RouteCachingStrategy
{
AlwaysCreate, // Current behavior
ReuseIfExists, // Cache and reuse
Singleton // Only one instance ever
}Description: No compile-time code generation for route registration and factory optimization.
Current Problem:
RouteFactorydelegate relies on reflection or manual factory implementationsViewLocatorBase.TryDeduceControl()uses reflection (Type.GetType,Activator.CreateInstance)- Route creation has runtime overhead from reflection
- No compile-time validation of route configurations
Use Cases:
- Auto-generate optimized route factory implementations at compile time
- Eliminate reflection overhead in route and view creation
- Generate strongly-typed navigation extension methods
- Create compile-time route validation and diagnostics
- Reduce startup time and memory allocations
- Improve AOT (Ahead-of-Time) compilation compatibility
Benefits:
- Zero runtime reflection - All route types known at compile time
- Type safety - Compile-time errors for invalid routes or missing views
- Performance - 10-100x faster route creation (no
Activator.CreateInstance) - AOT-friendly - Works with Native AOT compilation
- Intellisense support - Better IDE experience with generated code
- Startup time - Faster application initialization
Real-World Example: Similar to StaticViewLocator by Wiesław Sołtes, which generates static view resolution code for Avalonia, eliminating reflection from view locators.
Proposed API:
// 1. Mark router for generation
[GeneratedRouter]
public partial class AppRouter : IRouter
{
// Source generator will implement the factory
}
// 2. Mark routes for registration
[Route]
public class HomeRoute : IRoute
{
public HomeRoute(IRouterHost host) { ... }
}
[Route]
public class UserProfileRoute : IRoute
{
public UserProfileRoute(IRouterHost host, string userId, int? tab = null) { ... }
}
// 3. Auto-generated code (example output)
public partial class AppRouter
{
// Generated static factory dictionary
private static readonly Dictionary<Type, Func<IRouterHost, object[], IRoute>> s_routeFactories = new()
{
[typeof(HomeRoute)] = (host, args) => new HomeRoute(host),
[typeof(UserProfileRoute)] = (host, args) => new UserProfileRoute(
host,
(string)args[0],
args.Length > 1 ? (int?)args[1] : null),
};
// Generated strongly-typed navigation extensions
public static class Extensions
{
public static HomeRoute NavigateToHome(this IRouter router)
=> (HomeRoute)router.NavigateTo<HomeRoute>();
public static UserProfileRoute NavigateToUserProfile(
this IRouter router,
string userId,
int? tab = null)
=> (UserProfileRoute)router.NavigateTo<UserProfileRoute>(userId, tab);
}
}
// 4. Usage in application
router.NavigateToUserProfile("user123", tab: 2); // Strongly-typed, no reflection!Generated ViewLocator Enhancement:
// Mark view locator for generation
[GeneratedViewLocator]
public partial class AppViewLocator : ViewLocatorBase
{
// Generated view resolution (similar to StaticViewLocator)
private static readonly Dictionary<Type, Func<Control>> s_views = new()
{
[typeof(HomeRoute)] = () => new HomeView(),
[typeof(UserProfileRoute)] = () => new UserProfileView(),
};
public override Control? ResolveControl(IRoute? route)
{
if (route is null) return null;
var type = route.GetType();
return s_views.TryGetValue(type, out var factory)
? factory()
: null;
}
}Implementation Notes:
- Use Roslyn IIncrementalSourceGenerator (C# 9.0+)
- Generate partial classes and extension methods
- Support incremental generation for fast builds
- Provide diagnostics for missing routes/views
- Convention-based:
*Route→*Viewmapping - Generate XML documentation for generated methods
- Support for nullable reference types
- Compatible with Native AOT compilation
Performance Impact:
- Route creation: ~100x faster (static instantiation vs reflection)
- View resolution: ~10-50x faster (dictionary lookup vs Type.GetType)
- Memory: Lower allocation (no runtime type resolution)
- Startup: Faster (no reflection scanning)
- AOT: Full support (no reflection metadata needed)
Description: Basic content switching, no transition control.
Note: Avalonia's TransitioningContentControl provides some support, but no programmatic control.
Description: No concept of modal vs. page routes.
Use Cases:
- Display route as modal dialog
- Block navigation while modal is active
- Return values from modal routes
Description: No explicit support for nested routing hierarchies.
Use Cases:
- Master-detail layouts with independent navigation
- Tab-based navigation
- Multi-pane applications
Description: No way to define route naming conventions or patterns.
Status: ❌ Missing
Needed: Track version changes, breaking changes, and migration guides.
Template:
# Changelog
## [1.0.3] - 2024-XX-XX
### Added
- New feature X
### Changed
- Breaking change Y
### Fixed
- Bug ZStatus:
Needed:
- XML comments on all public APIs
- Code examples in comments
- Remarks for complex behavior
- See also references
Status: ❌ Missing
Needed:
- Design decisions and rationale
- Extension points
- Performance characteristics
- Threading model
Status: ❌ Missing
Needed:
- How to contribute
- Code style guide
- PR process
- Development setup
Status: ❌ Missing
Needed: Community guidelines (standard for open source).
Status: ❌ Missing
Needed:
- Security policy
- How to report vulnerabilities
- Supported versions
Status: ❌ Missing
Needed:
- Migration from ReactiveUI routing
- Breaking changes between versions
Status:
Current: Basic sample app exists.
Needed:
- Nested navigation example
- Parameter passing example
- Event handling example
- Custom route factory example
- Error handling example
Status: ❌ Missing
Needed:
- Stack limit recommendations
- Memory usage patterns
- Performance best practices
Status: ❌ Missing
Needed:
- Concurrent navigation from multiple threads
- Race condition testing
- Thread-safe event subscription
Status: ❌ Missing
Needed:
- Verify event handlers are cleaned up
- Long-running navigation stress tests
- Dispose pattern verification
Status:
Missing:
- NavigateBack when stack has only one item (test exists but limited)
- NavigateTo same route multiple times
- Rapid navigation (spam navigation calls)
- Stack overflow scenarios
- Route factory returning null (test exists but could be expanded)
Status: ❌ Missing
Needed:
- End-to-end navigation flows
- Avalonia RouteViewHost integration tests
- View locator integration tests
Status: ❌ Missing
Needed:
- Navigation speed benchmarks
- Memory usage benchmarks
- Stack size impact tests
Status: ❌ Missing
Purpose: Verify test quality by introducing code mutations.
Status: ❌ Missing
Needed:
- Coverage badge in README
- Coverage reports in CI
- Minimum coverage threshold
Tools: Coverlet, Codecov, or Coveralls
Status:
Current: Manual version bumps and NuGet publishing.
Needed:
- Automated version tagging
- Automated NuGet package publishing
- Automated release notes generation
Status: ❌ Missing
Needed:
- Git hooks or CI checks for version increments
- Breaking change detection
Status: ❌ Missing
Needed:
- Format code automatically
- Run fast unit tests
- Lint commit messages
Status: ❌ Missing
Needed:
- Automated dependency update PRs
- Security vulnerability scanning
Status: ❌ Missing
Needed:
- Performance regression detection
- Benchmark results tracking over time
Tool: BenchmarkDotNet
-
Fix memory leak in RouteViewHost
- Unsubscribe from old router events
- Add disposal test
-
Fix stack limit bug in Router constructor
- Assign stackLimit parameter to field
- Add test to verify custom stack limits work
-
Remove unused import in Router.cs
- Delete
using System.Net.Sockets;
- Delete
-
Fix nullable reference warnings in tests
- Update test code to use proper null handling
-
Add thread safety to Router
- Add locking around stack operations
- Add concurrent access tests
-
Create CHANGELOG.md
- Document all versions since 1.0.0
- Establish changelog format
-
Add XML documentation comments
- Document all public APIs in SimpleRouter
- Document all public APIs in SimpleRouter.Avalonia
- Include code examples where helpful
-
Create CONTRIBUTING.md
- Document contribution process
- Add code style guidelines
- Include development setup instructions
-
Create SECURITY.md
- Define security policy
- Add vulnerability reporting process
-
Add navigation guards
- Define INavigationGuard interface
- Integrate guards into Router
- Add guard tests
- Document usage with examples
-
Add navigation cancellation
- Add Cancel property to RouteChangingEventArgs
- Implement cancellation logic
- Add cancellation tests
-
Add route parameters support
- Extend IRoute with Parameters property
- Update NavigateTo methods
- Add parameter tests
- Document parameter patterns
-
Add async navigation support
- Add async NavigateTo methods
- Add async NavigateBack
- Add async guard support
- Add async tests
-
Add thread safety tests
- Concurrent navigation tests
- Race condition tests
-
Add integration tests
- End-to-end navigation tests
- Avalonia integration tests
-
Set up code coverage
- Add Coverlet to tests
- Configure coverage reporting
- Add coverage badge to README
- Set minimum coverage threshold (80%+)
-
Add deep linking support
- Design URI routing API
- Implement route registration
- Add URI parsing and matching
- Add deep linking tests
-
Add route lifecycle hooks
- Define IRoutable interface
- Implement lifecycle method calls
- Add lifecycle tests
-
Add logging support
- Define IRouterLogger interface
- Add logging integration points
- Add logging tests
-
Add route caching
- Define caching strategies
- Implement route cache
- Add cache tests
-
Implement source generators for performance
- Create source generator project
- Generate route factory implementations
- Generate strongly-typed navigation extensions
- Add compile-time route validation
- Add generator tests and documentation
-
Set up release automation
- Automate NuGet publishing
- Generate release notes automatically
- Add version tagging workflow
-
Add pre-commit hooks
- Set up Husky or similar
- Configure format check
- Configure test run
-
Add dependency automation
- Enable Dependabot
- Configure update schedule
-
Add benchmarking
- Create BenchmarkDotNet project
- Add navigation benchmarks
- Track benchmark results
-
Create CODE_OF_CONDUCT.md
- Use Contributor Covenant or similar
-
Add architecture documentation
- Document design decisions
- Add sequence diagrams
- Document extension points
-
Add migration guides
- ReactiveUI migration guide
- Version migration guides
-
Expand examples
- Add advanced usage examples
- Add error handling examples
- Add nested navigation example
- Fix memory leak in RouteViewHost
⚠️ - Fix stack limit bug
⚠️ - Add thread safety
- XML documentation for all public APIs
- CHANGELOG.md
- Navigation guards
- Navigation cancellation
- Route parameters
- Async navigation
- Code coverage reporting
- CONTRIBUTING.md
- SECURITY.md
- Deep linking
- Route lifecycle hooks
- Logging support
- Route caching
- Source generators for performance
- Integration tests
- Performance tests
- Release automation
- Advanced examples
- Modal/dialog routes
- Nested routers
- Route templates
- Animation control
- Benchmarking suite
- ✅ Fix the memory leak - this is a critical bug
- ✅ Fix the stack limit bug - breaks documented API
- ✅ Remove unused import - code cleanliness
- ✅ Fix nullable warnings - clean builds
- ✅ Add comprehensive XML docs - developer experience
- Add thread safety with proper locking
- Create CHANGELOG.md and maintain it going forward
- Add navigation guards - commonly requested feature
- Add navigation cancellation - important for UX
- Set up code coverage reporting
- Implement route parameters
- Add async navigation support
- Create comprehensive documentation (CONTRIBUTING, SECURITY)
- Add integration and performance tests
- Implement deep linking
- Maintain high test coverage (>80%)
- Regular dependency updates
- Monitor and respond to community issues
- Consider additional platform support (WPF, MAUI)
- Performance optimization based on benchmarks
SimpleRouter has a solid foundation with clean architecture and good core functionality. The library successfully delivers on its promise of being "simple" while providing essential routing capabilities.
To be production-ready, the library needs:
- Critical bug fixes (memory leak, stack limit)
- Thread safety for concurrent usage
- Complete documentation (XML comments, CHANGELOG)
- Essential features (guards, cancellation, parameters)
- Quality assurance (coverage reporting, integration tests)
Estimated Effort:
- Critical fixes: 1-2 days
- Essential documentation: 2-3 days
- Core features: 1-2 weeks
- Testing & quality: 1 week
- Advanced features: 2-4 weeks (ongoing)
Current Status: 7/10 - Good foundation, needs refinement for production use. With Phase 1-3 Complete: 9/10 - Production-ready with room for enhancement.
The library is already usable for internal projects and prototypes. With the critical fixes and documentation improvements, it will be ready for production use in commercial applications.