镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

About

A robust .NET library for implementing navigation stacks with undo/redo support, persistence capabilities, and event-driven architecture. Perfect for applications requiring navigation state management.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ktsu.Navigation

A robust .NET library for implementing navigation stacks with undo/redo support and persistence capabilities.

License NuGet Version NuGet Version NuGet Downloads GitHub commit activity GitHub contributors GitHub Actions Workflow Status

Overview

ktsu.Navigation provides a complete navigation stack implementation that supports:

  • Forward/Backward Navigation: Navigate through items with full browser-like back/forward functionality
  • Undo/Redo Operations: Complete undo/redo support for all navigation actions
  • Persistence: Save and restore navigation state to various storage backends
  • Event-Driven Architecture: React to navigation changes with comprehensive event notifications
  • Generic Design: Works with any navigation item type implementing INavigationItem
  • Factory Pattern: Easy creation of navigation stacks with different configurations

Perfect for applications that need robust navigation management, such as:

  • Code editors with file navigation
  • Image viewers with browsing history
  • Document management systems
  • Any application requiring navigation state management

Features

Core Navigation

  • ✅ NavigateTo, GoBack, GoForward operations
  • ✅ Current item tracking with CanGoBack/CanGoForward status
  • ✅ Complete navigation history management
  • ✅ Forward history clearing when navigating to new items
  • ✅ Thread-safe operations

Undo/Redo System

  • ✅ Full undo/redo support for navigation operations
  • ✅ Configurable history size limits
  • ✅ State change notifications
  • ✅ Error handling during undo/redo operations
  • ✅ Redirects made by a NavigationChanged handler during an undo or redo are not recorded as new actions, so the redo history survives them

Persistence

  • ✅ JSON file persistence provider
  • ✅ In-memory persistence for testing
  • ✅ Async operations with cancellation token support
  • ✅ Custom persistence provider support

Events & Extensibility

  • ✅ Navigation change events (NavigateTo, GoBack, GoForward, Clear)
  • ✅ Metadata support for navigation items
  • ✅ Generic design for custom navigation item types
  • ✅ Factory pattern for easy configuration

Installation

Add the NuGet package:

dotnet add package ktsu.Navigation

Quick Start

Basic Navigation

using ktsu.Navigation.Models;
using ktsu.Navigation.Services;

// Create navigation items
var page1 = new NavigationItem("page1", "Home Page");
var page2 = new NavigationItem("page2", "About Page");
var page3 = new NavigationItem("page3", "Contact Page");

// Create a navigation stack
var navigation = new Navigation<NavigationItem>();

// Navigate through items
navigation.NavigateTo(page1);  // Current: Home Page
navigation.NavigateTo(page2);  // Current: About Page
navigation.NavigateTo(page3);  // Current: Contact Page

// Go back and forward
var previous = navigation.GoBack();    // Current: About Page
var next = navigation.GoForward();     // Current: Contact Page

// Check navigation state
Console.WriteLine($"Current: {navigation.Current?.DisplayName}");
Console.WriteLine($"Can go back: {navigation.CanGoBack}");
Console.WriteLine($"Can go forward: {navigation.CanGoForward}");

With Undo/Redo Support

using ktsu.Navigation.Services;

// Create navigation with undo/redo support
var undoRedoProvider = new SimpleUndoRedoProvider(maxHistorySize: 50);
var navigation = new Navigation<NavigationItem>(undoRedoProvider);

navigation.NavigateTo(page1);
navigation.NavigateTo(page2);

// Undo the last navigation
undoRedoProvider.Undo();  // Back to page1

// Redo the navigation
undoRedoProvider.Redo();  // Forward to page2 again

With Persistence

using ktsu.Navigation.Services;

// Create navigation with JSON file persistence
var persistenceProvider = new JsonFilePersistenceProvider<NavigationItem>("navigation-state.json");
var navigation = new Navigation<NavigationItem>(undoRedoProvider: null, persistenceProvider: persistenceProvider);

navigation.NavigateTo(page1);
navigation.NavigateTo(page2);

// Save navigation state
await navigation.SaveStateAsync();

// Later... restore navigation state
await navigation.LoadStateAsync();
Console.WriteLine($"Restored to: {navigation.Current?.DisplayName}");

Saving subclasses of the item type

JsonFilePersistenceProvider<T> writes each item as T. If a Navigation<NavigationItem> holds a subclass such as DocumentPage : NavigationItem, JSON can't save its type or the properties it adds. So SaveStateAsync throws NotSupportedException naming the subclass, and the last save stays in place. To keep several page types in one stack, configure polymorphism for T through the JSON options:

using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers =
        {
            typeInfo =>
            {
                if (typeInfo.Type == typeof(NavigationItem))
                {
                    typeInfo.PolymorphismOptions = new JsonPolymorphismOptions
                    {
                        DerivedTypes = { new JsonDerivedType(typeof(DocumentPage), "document") },
                    };
                }
            },
        },
    },
};

var persistenceProvider = new JsonFilePersistenceProvider<NavigationItem>("navigation-state.json", options);

Every subclass you save needs a JsonDerivedType entry. Plain NavigationItem items keep working alongside them.

Complete Example with All Features

using ktsu.Navigation.Services;

// Create providers
var undoRedoProvider = new SimpleUndoRedoProvider();
var persistenceProvider = new JsonFilePersistenceProvider<NavigationItem>("app-navigation.json");

// Create navigation with all features
var navigation = new Navigation<NavigationItem>(undoRedoProvider, persistenceProvider);

// Subscribe to navigation events
navigation.NavigationChanged += (sender, e) =>
{
    Console.WriteLine($"Navigation: {e.NavigationType}");
    Console.WriteLine($"From: {e.PreviousItem?.DisplayName ?? "None"}");
    Console.WriteLine($"To: {e.CurrentItem?.DisplayName ?? "None"}");
};

// Use factory for easier creation
var factory = new NavigationStackFactory(undoRedoProvider);
var anotherNavigation = factory.CreateNavigationStack<NavigationItem>(undoRedoProvider, persistenceProvider);

Custom Navigation Items

public class DocumentNavigationItem : INavigationItem
{
    public string Id { get; }
    public string DisplayName { get; set; }
    public DateTime CreatedAt { get; }
    public IReadOnlyDictionary<string, object> Metadata => _metadata.AsReadOnly();
    
    private readonly Dictionary<string, object> _metadata = new();
    
    public DocumentNavigationItem(string filePath, string displayName)
        : this(filePath, displayName, DateTime.UtcNow, null)
    {
        SetMetadata("FilePath", filePath);
        SetMetadata("FileSize", new FileInfo(filePath).Length);
    }
    
    // JsonFilePersistenceProvider reloads saved items through this constructor. System.Text.Json
    // needs each parameter to have the same name and type as the property it sets.
    [JsonConstructor]
    public DocumentNavigationItem(string id, string displayName, DateTime createdAt, IReadOnlyDictionary<string, object>? metadata)
    {
        Id = id;
        DisplayName = displayName;
        CreatedAt = createdAt;
        foreach (var entry in metadata ?? new Dictionary<string, object>())
        {
            _metadata[entry.Key] = entry.Value;
        }
    }
    
    public void SetMetadata(string key, object value) => _metadata[key] = value;
    public bool RemoveMetadata(string key) => _metadata.Remove(key);
}

// Use with navigation
var navigation = new Navigation<DocumentNavigationItem>();
var document = new DocumentNavigationItem("/path/to/file.txt", "My Document");
navigation.NavigateTo(document);

Architecture

The library follows clean architecture principles with well-separated concerns:

  • Models: NavigationItem, NavigationState - Core data structures
  • Contracts: Interfaces defining the contracts for all components
  • Services: Implementation classes for navigation, persistence, and undo/redo

Key Interfaces

  • INavigation<T>: Main navigation operations interface
  • INavigationItem: Contract for items that can be navigated to
  • IPersistenceProvider<T>: Contract for persistence implementations
  • IUndoRedoProvider: Contract for undo/redo implementations

Extension Points

Create custom persistence providers by implementing the IPersistenceProvider<T> interface:

public class DatabasePersistenceProvider<T> : IPersistenceProvider<T> where T : INavigationItem
{
    public async Task SaveStateAsync(INavigationState<T> state, CancellationToken cancellationToken = default)
    {
        // Save to database
        await Task.CompletedTask;
    }

    public async Task<INavigationState<T>?> LoadStateAsync(CancellationToken cancellationToken = default)
    {
        // Load from database
        return await Task.FromResult<INavigationState<T>?>(null);
    }

    // Implement HasSavedState, ClearState, etc.
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License. Copyright (c) ktsu.dev

About

A robust .NET library for implementing navigation stacks with undo/redo support, persistence capabilities, and event-driven architecture. Perfect for applications requiring navigation state management.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages