DKNet

Configure and operate local storage

Part of DKNet.Svc.BlobStorage.Local. Set the root folder, then work with the save, listing, and delete behavior specific to this provider.

// appsettings.json
// { "BlobStorage": { "LocalFolder": { "RootFolder": "/var/app/storage" } } }

builder.Services.AddLocalDirectoryBlobService(builder.Configuration);

RootFolder is the base of every path this provider touches. Left unset, it falls back to {CurrentDirectory}/LocalStore — fine for a quick local run, but set it explicitly for anything you deploy, because the current directory is whatever the process happened to start in.

var blob = new BlobDetails.BlobData("reports/2026/q1.pdf", BinaryData.FromString("..."))
{
    Overwrite = true // replace an existing file instead of throwing
};
var location = await blobService.SaveAsync(blob); // "reports/2026/q1.pdf"

SaveAsync validates against BlobServiceOptions, then throws InvalidOperationException("File already existed") when the target exists and Overwrite is false (the shared default). Missing parent directories are created automatically, and the write goes through a FileStream opened with FileShare.None, so two writers never interleave on the same path. GetAsync throws FileNotFoundException when the file does not exist — every other built-in provider returns null instead (see Abstractions).

ListItemsAsync resolves the path with GetFinalPath, checks whether it is a directory with Directory.Exists, and either walks every entry under it in one recursive EnumerateFileSystemInfos pass or yields the single matching file, interleaving files and folders in whatever order the file system returns them.

The diagram does not show that a non-existent, non-directory path yields an empty sequence rather than throwing — the same “not found ⇒ nothing” shape S3 and Azure Storage use for listing. Names come back relative to RootFolder, computed with Path.GetRelativePath — correct even when a subfolder shares the root folder’s own name.

⚙️ Configuration reference

LocalDirectoryOptions extends BlobServiceOptions, documented on the Abstractions feature page:

Option Type Default Effect
RootFolder string? null → {CurrentDirectory}/LocalStore at runtime Base directory for every blob; also the boundary the traversal guard enforces.
LocalDirectoryOptions.Name (static) string "BlobStorage:LocalFolder" Configuration section key AddLocalDirectoryBlobService binds from — note the BlobStorage: prefix, unlike the cloud providers’ BlobService:.

The package also ships one public helper outside this options type: LocalDirectorySetup.IsDirectory(this string path), declared in the Microsoft.Extensions.DependencyInjection namespace so it is in scope wherever the registration call is. It is a thin wrapper over Directory.Exists(path) — it returns false for a missing or non-directory path and never throws.

🧱 Where it fits

LocalBlobService derives from the BlobService base class in Abstractions, so validation and path normalization behave the same as on the cloud providers. Application code depends on IBlobService only; swap this registration for S3 or Azure per environment without touching a consumer. Test projects register this provider against a temporary directory to exercise blob-writing code paths without a storage emulator.

⚠️ Gotchas & limits