Technical Principles
These principles define how the navigation system is implemented.
Prerequisites: Read Functional Principles first to understand what the system does and why.
Navigation classes are generic over TModel:
public class DocumentationSetNavigation<TModel>
where TModel : class, IDocumentationFile
Why Generic?
Covariance Enables Static Typing:
// Without covariance: always get base interface, requires runtime casts
INodeNavigationItem<INavigationModel, INavigationItem> node = GetNode();
if (node.Model is MarkdownFile markdown)
{
var content = markdown.Content;
}
// With covariance: query for specific type statically
INodeNavigationItem<MarkdownFile, INavigationItem> node = QueryForMarkdownNodes();
var content = node.Model.Content;
- Runtime check required
- ✓ No cast needed! Static type safety
Benefits:
- Type Safety: Query methods can return specific types like
INodeNavigationItem<MarkdownFile, INavigationItem> - No Runtime Casts: Access
.Model.Contentdirectly without casting - Compile-Time Errors: Type mismatches caught during compilation, not runtime
- Better IntelliSense: IDEs show correct members for specific model types
- Flexibility: Same navigation code works with different file models (MarkdownFile, ApiDocFile, etc.)
Example:
// Query for nodes with specific model type
var markdownNodes = navigation.NavigationItems
.OfType<INodeNavigationItem<MarkdownFile, INavigationItem>>();
foreach (var node in markdownNodes)
{
// No cast needed! Static typing
Console.WriteLine(node.Model.FrontMatter);
Console.WriteLine(node.Model.Content);
}
INavigationHomeProvider / INavigationHomeAccessor:
- Providers define context (PathPrefix, NavigationRoot)
- Accessors reference providers
- Decouples URL calculation from tree structure
- Enables context switching (re-homing)
Why This Enables Re-homing:
// Isolated build
node.HomeProvider = new NavigationHomeProvider("", docsetRoot);
// URLs: /api/rest/
// Assembler build - O(1) operation!
node.HomeProvider = new NavigationHomeProvider("/guide", siteRoot);
// URLs: /guide/api/rest/
Single reference change updates all descendant URLs.
See Home Provider Architecture for complete explanation.
FileNavigationLeaf implements smart URL caching:
private string? _homeProviderCache;
private string? _urlCache;
public string Url
{
get
{
if (_homeProviderCache == HomeProvider.Id && _urlCache != null)
return _urlCache;
_urlCache = CalculateUrl();
_homeProviderCache = HomeProvider.Id;
return _urlCache;
}
}
Strategy:
- Cache URL along with HomeProvider ID
- Invalidate cache when HomeProvider changes
- Recalculate only when needed
- O(1) for repeated access, O(depth) for calculation
Why HomeProvider.Id?
- Each HomeProvider has a unique ID
- Comparing IDs is cheaper than deep equality checks
- ID changes when provider is replaced during re-homing
- Automatic cache invalidation without explicit cache clearing
- Tree Construction: O(n) where n = number of files
- URL Calculation: O(depth) for first access, O(1) with caching
- Re-homing: O(1) - just replace HomeProvider reference
- Tree Traversal: O(n) for full tree, but rarely needed
- Memory: O(n) for nodes, URLs computed on-demand
Why Re-homing is O(1):
- Replace single HomeProvider reference
- No tree traversal required
- URLs lazy-calculated on next access
- Cache invalidation via ID comparison
- All descendants automatically use new provider