Binary data storage
BinaryDataStorageManager routes binary content by a prefix in its path. An application that keeps a record of a file somewhere - a database row, a link, an entity - and the bytes somewhere else, stores a short path such as files://a3f2c1.png and lets the manager find the storage that holds it.
This is a standalone helper. It is not part of the FileSystem pipeline, and nothing in the library calls it.
Registering a storage
Section titled “Registering a storage”A storage implements IBinaryDataStorage, three methods over whatever holds the bytes:
internal class DiskStorage(string root) : IBinaryDataStorage{ public Stream Read(string contentPath) => File.OpenRead(Path.Combine(root, contentPath));
public string Write(string? extension, Stream stream) { var name = Guid.NewGuid().ToString("N") + extension; using (var target = File.Create(Path.Combine(root, name))) stream.CopyTo(target);
return name; }
public bool Delete(string contentPath) { var full = Path.Combine(root, contentPath); if (!File.Exists(full)) return false;
File.Delete(full); return true; }}Each storage is registered under a prefix:
var manager = new BinaryDataStorageManager();manager.RegisterStorage("files://", new DiskStorage(@"C:\data\files"));A prefix ends with :// and the part before it holds only letters, digits, - and _. It is stored in lower case and matched without regard to case, so FILES://a3f2c1.png reaches the same storage. BinaryDataStorageInfo.Code exposes the prefix without the separator.
Reading, writing and deleting
Section titled “Reading, writing and deleting”Write takes the resource, so the storage can use its extension, and returns the path to keep:
var contentPath = manager.Write(resource, stream); // "files://8f14e45f.png"Read and Delete accept either that path or the resource that carries it:
using var content = manager.Read(contentPath);var removed = manager.Delete(contentPath); // true when the storage held itThe prefix decides which storage answers. A path with no registered prefix throws BinaryDataStorageNotFoundException, and a resource whose ContentPath is empty throws InvalidOperationException.
Several storages
Section titled “Several storages”Registration order matters. Write sends new content to the storage the resource already uses, and falls back to the first registered one:
var manager = new BinaryDataStorageManager();manager.RegisterStorage("s3://", new S3Storage()); // new content goes heremanager.RegisterStorage("files://", new DiskStorage(...)); // old content still readsThis is what makes a migration possible without rewriting the stored paths. Content written earlier as files://... keeps reading from disk, and everything new lands in the storage registered first. A resource already stored under s3:// is rewritten in place rather than moved.
GetCurrentStorage reports where a resource is held now, GetPreferredStorage where it would be written. GetPreferredStorage is virtual, so a different policy - by extension, by size, by tenant - is a subclass away.