Mesch.RealTree
0.0.2
See the version list below for details.
dotnet add package Mesch.RealTree --version 0.0.2
NuGet\Install-Package Mesch.RealTree -Version 0.0.2
<PackageReference Include="Mesch.RealTree" Version="0.0.2" />
<PackageVersion Include="Mesch.RealTree" Version="0.0.2" />
<PackageReference Include="Mesch.RealTree" />
paket add Mesch.RealTree --version 0.0.2
#r "nuget: Mesch.RealTree, 0.0.2"
#:package Mesch.RealTree@0.0.2
#addin nuget:?package=Mesch.RealTree&version=0.0.2
#tool nuget:?package=Mesch.RealTree&version=0.0.2
Mesch.RealTree
A tree hierarchy management abstraction with plugin architecture and eventing.
Overview
RealTree provides a flexible tree implementation where:
- Containers can hold both other containers and items
- Items can hold containers but not other items
- Operations support middleware-style actions and fire-and-forget events
- Validation prevents cyclic references automatically
Core Concepts
Node Types
IRealTreeNode // Base interface for all nodes
├── IRealTreeContainer // Can contain containers and items
├── IRealTreeItem // Can contain containers only
└── IRealTree // Root node with global operations (extends IRealTreeNode)
Operations Service
All tree modifications go through IRealTreeOperations which provides:
- Middleware-style action pipelines (can intercept/modify operations)
- Event notifications (fire-and-forget, executed after operations)
- Bulk operations for performance
- Copy operations with recursive traversal
Quick Start
// Create tree
var factory = new RealTreeFactory();
var tree = factory.CreateTree(name: "MyTree");
var operations = new RealTreeOperations(factory);
// Add nodes
var container = await operations.AddContainerAsync(tree, null, "Documents");
var item = await operations.AddItemAsync(container, null, "Report.pdf");
// Navigate
var found = tree.FindByPath("/Documents/Report.pdf");
Console.WriteLine(found?.Name); // "Report.pdf"
Middleware Actions
Actions execute before the operation and can intercept, modify, or cancel it:
container.RegisterAddContainerAction(async (context, next) =>
{
// Pre-operation logic
// Parent is IRealTreeNode, use ParentAsContainer helper for container-specific access
var parentName = context.ParentAsContainer?.Name ?? context.Parent.Name;
Console.WriteLine($"Adding {context.Container.Name} to {parentName}");
// Validate
if (context.Container.Name.StartsWith("temp"))
throw new InvalidOperationException("Temp folders not allowed");
// Continue pipeline
await next();
// Post-operation logic
Console.WriteLine("Container added successfully");
});
Event Handling
Events fire after operations complete and run in parallel:
container.RegisterContainerAddedEvent(async context =>
{
// Log to database
await LogActivity($"Container {context.Container.Name} added");
});
container.RegisterContainerAddedEvent(async context =>
{
// Send notification (runs in parallel with logging)
await NotifyUsers($"New folder: {context.Container.Name}");
});
Bulk Operations
Efficient operations for multiple nodes:
var containers = new List<IRealTreeContainer>
{
factory.CreateContainer(name: "Folder1"),
factory.CreateContainer(name: "Folder2"),
factory.CreateContainer(name: "Folder3")
};
await operations.BulkAddContainersAsync(tree, containers);
Common Patterns
Hierarchical Permissions
// Register at tree level - applies to all operations
tree.RegisterAddContainerAction(async (context, next) =>
{
var user = GetCurrentUser();
var parent = context.Parent;
if (!HasPermission(user, parent, "create"))
throw new UnauthorizedAccessException();
await next();
});
Audit Logging
// Update events
tree.RegisterNodeUpdatedEvent(async context =>
{
await auditLogger.LogAsync(new AuditEntry
{
UserId = GetCurrentUser().Id,
Action = "Update",
NodeId = context.Node.Id,
OldName = context.OldName,
NewName = context.NewName,
Timestamp = DateTime.UtcNow
});
});
// Removal events - strongly typed contexts
tree.RegisterContainerRemovedEvent(async context =>
{
await auditLogger.LogAsync(new AuditEntry
{
UserId = GetCurrentUser().Id,
Action = "RemoveContainer",
NodeId = context.Container.Id,
NodeName = context.Container.Name,
ParentId = context.Parent.Id,
Timestamp = DateTime.UtcNow
});
});
tree.RegisterItemRemovedEvent(async context =>
{
await auditLogger.LogAsync(new AuditEntry
{
UserId = GetCurrentUser().Id,
Action = "RemoveItem",
NodeId = context.Item.Id,
NodeName = context.Item.Name,
ParentId = context.Parent.Id,
Timestamp = DateTime.UtcNow
});
});
Validation with Context
container.RegisterAddItemAction(async (context, next) =>
{
var parent = context.Parent;
var item = context.Item;
// Business rule: max 10 items per container
if (parent.Items.Count >= 10)
throw new InvalidOperationException("Container full");
// Check for duplicates
if (parent.Items.Any(i => i.Name == item.Name))
throw new DuplicateNameException(item.Name);
await next();
});
Context Types
Strongly-Typed Removal Contexts
Removal operations use type-specific contexts for better type safety:
// Container removal - access .Container and .Parent (both strongly typed)
tree.RegisterRemoveContainerAction(async (context, next) =>
{
Console.WriteLine($"Removing container: {context.Container.Name}");
Console.WriteLine($"From parent: {context.Parent.Name}");
// context.Container is IRealTreeContainer
// and context.Parent is IRealTreeContainer
await next();
});
// Item removal - access .Item and .Parent (both strongly typed)
tree.RegisterRemoveItemAction(async (context, next) =>
{
Console.WriteLine($"Removing item: {context.Item.Name}");
Console.WriteLine($"From parent: {context.Parent.Name}");
// context.Item is IRealTreeItem
// and context.Parent is IRealTreeContainer
await next();
});
AddContainerContext Helpers
AddContainerContext.Parent is IRealTreeNode because both containers and items can hold containers. Use helper properties to avoid manual casting:
container.RegisterAddContainerAction(async (context, next) =>
{
// Access parent as specific type
if (context.ParentAsContainer != null)
{
Console.WriteLine($"Parent has {context.ParentAsContainer.Items.Count} items");
}
else if (context.ParentAsItem != null)
{
Console.WriteLine($"Parent is an item: {context.ParentAsItem.Name}");
}
await next();
});
Advanced Usage
Custom Node Types
public class FileContainer : RealTreeContainer
{
public string ContentType { get; set; }
public long MaxSize { get; set; }
public FileContainer(string contentType, long maxSize)
: base(name: contentType)
{
ContentType = contentType;
MaxSize = maxSize;
}
}
Operation Context Access
tree.RegisterMoveAction(async (context, next) =>
{
var node = context.Node;
var oldPath = node.Path;
await next(); // Perform the move
var newPath = node.Path;
await UpdateReferences(oldPath, newPath);
});
Delegate Management
// Store reference for later removal
AddContainerDelegate validator = async (context, next) => { /* validation */ };
container.RegisterAddContainerAction(validator);
// Remove when no longer needed
container.DeregisterAddContainerAction(validator);
Exception Handling
// Actions can throw exceptions to cancel operations
container.RegisterAddContainerAction(async (context, next) =>
{
if (SomeValidationFails())
throw new TreeValidationException("Operation not allowed");
await next();
});
// Events log exceptions but don't propagate them
container.RegisterContainerAddedEvent(async context =>
{
try
{
await SomeExternalService.NotifyAsync(context);
}
catch (Exception ex)
{
// Exception logged automatically, operation continues
}
});
Thread Safety
- Tree structure modifications are not thread-safe by design
- Use external synchronization if needed
- Events execute in parallel but individual event handlers should be thread-safe
ReaderWriterLockSlimis available onRealTreeRoot.Lockfor custom locking
Dependency Injection
services.AddSingleton<IRealTreeFactory, RealTreeFactory>();
services.AddScoped<IRealTreeOperations, RealTreeOperations>();
// With logging
services.AddScoped<IRealTreeOperations>(provider =>
{
var factory = provider.GetService<IRealTreeFactory>();
var logger = provider.GetService<ILogger<RealTreeOperations>>();
return new RealTreeOperations(factory, logger);
});
Performance Considerations
- Use bulk operations for multiple related changes
- Event handlers execute in parallel but should be lightweight
- Consider unregistering delegates when no longer needed
- Path lookups are O(depth), ID lookups are O(n)
- Metadata dictionaries are not optimized for large datasets
Error Types
TreeValidationException // Base validation error
├── DuplicateNameException // Duplicate sibling names
├── CyclicReferenceException // Would create cycle
└── InvalidContainmentException // Invalid parent-child relationship
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Logging (>= 9.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.