Repository navigation
Driver Memory
In-memory storage backed by Dictionary<string, ...> + MemoryStream. Ships in the core FileHub package. Primary use case: tests.
using FileHub.Memory;
var hub = new MemoryFileHub(); // Root.Name = "root"
var hub = new MemoryFileHub("mnt"); // Root.Name = "mnt"Nested directory paths ("a/b/c") walk the in-process dictionary hierarchy — intermediate directories always materialise, no I/O involved.
public interface IMemoryFileHub : IFileHub { }
public class MemoryFileHub : IMemoryFileHub { ... }public class ReportService(IFileHub hub) { ... }
// in a test
var hub = new MemoryFileHub();
var svc = new ReportService(hub);
svc.Save("report.pdf", body);
Assert.Equal(body, hub.Root.OpenFile("report.pdf").ReadAllText());More patterns: Testing.
GetFiles / GetDirectories use Win32-style glob, aligned with what LocalFile accepts. Tests against MemoryFileHub match prod behaviour against LocalFileHub.
| Pattern | Matches |
|---|---|
*, *.*
|
everything |
*.txt |
suffix |
report_* |
prefix |
q?.pdf |
single-char wildcard (e.g. q1.pdf, q4.pdf) |
[abc]*.log |
character class — names starting with a, b, or c
|
name (no wildcards) |
exact, case-insensitive |
All matching is case-insensitive. ? and * cannot match path separators (separators don't appear in a single directory's listing anyway).
FileExists(name) and DirectoryExists(name) are simple dictionary lookups.
CreateFile("a/b/c.txt") (and OpenFile / TryOpenFile) creates the file at the nested location, auto-creating the intermediate MemoryDirectory instances. Both / and \ separators work; .. is rejected with FileHubException.
hub.Root.CreateFile("reports/2026/q1.pdf").SetText("...");MemoryFile shares one MemoryStream between reads and writes, wrapped so callers' Dispose doesn't destroy the storage. Concurrent-access rules:
- While a write stream is open, no other stream can be opened (throws).
- Multiple concurrent read streams are allowed.
var f = hub.Root.CreateFile("a.txt");
using (var w = f.GetWriteStream())
{
w.Write(bytes, 0, bytes.Length);
// f.GetReadStream() here would throw — write lock held
}
using (var r = f.GetReadStream()) { /* ok */ }MemoryFileHub reports Features.Metadata == true and keeps per-object metadata in memory, so it round-trips FileWriteOptions the same way the cloud drivers do — handy for testing metadata-dependent code without a real bucket.
var f = hub.Root.CreateFile("img.bin");
f.SetBytes(bytes, new FileWriteOptions
{
ContentType = "image/png",
CacheControl = "public,max-age=3600",
Metadata = new Dictionary<string, string> { ["owner"] = "team-x" },
});
var meta = f.GetMetadata();
// meta.ContentType == "image/png"
// meta.CacheControl == "public,max-age=3600"
// meta.Tags["owner"] == "team-x"ContentType, CacheControl, and user Metadata are stored and returned. Driver-specific typed fields (S3 storage class / SSE) are not modelled — Memory ignores options it doesn't support, never throws. The returned FileMetadata is an immutable snapshot, same as every driver.
- Everything lives in process memory; nothing persists. Two hubs are independent.
-
Delete()clears collections and disposes the backing object — callers holding the old reference will seeExists()returnfalse. -
Renameon a directory disposes the old instance and returns a new one. Keep the returned reference. -
Renameis leaf-only — anewNamecontaining/or\throwsArgumentException; useMoveToto relocate.Renamenever overwrites — a name already taken (file or directory) throwsFileAlreadyExistsException.CopyTo/MoveToacceptoverwrite(defaultfalse): an existing destination throwsFileAlreadyExistsException, leaving the source intact; passoverwrite: trueto replace it. DirectoryMoveTo(dir, name, overwrite: true)merges into an existing destination.
-
IUrlAccessible— in-process only, no URL.
FileHub — unified file & directory API for .NET. Core is dependency-free; drivers are opt-in packages.