Regira.IO.Storage 6.1.2

dotnet add package Regira.IO.Storage --version 6.1.2
                    
NuGet\Install-Package Regira.IO.Storage -Version 6.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Regira.IO.Storage" Version="6.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Regira.IO.Storage" Version="6.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Regira.IO.Storage" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Regira.IO.Storage --version 6.1.2
                    
#r "nuget: Regira.IO.Storage, 6.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Regira.IO.Storage@6.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Regira.IO.Storage&version=6.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Regira.IO.Storage&version=6.1.2
                    
Install as a Cake Tool

Regira IO.Storage

Regira IO.Storage provides a unified abstraction for file storage operations across multiple backends. All implementations share the same IFileService interface, making storage backends interchangeable in consuming code.

Projects

Project Package Backend File services
Common.IO.Storage Regira.IO.Storage Local file system BinaryFileService
Windows network share (UNC) NetworkFileService
Zip file system ZipFileService
IO.Storage.Azure Regira.IO.Storage.Azure Azure Blob Storage BinaryBlobService
IO.Storage.SSH Regira.IO.Storage.SSH SFTP / SSH server SftpService
IO.Storage.GitHub Regira.IO.Storage.GitHub GitHub repository GitHubService (writes commit to a branch)

Installation


<PackageReference Include="Regira.IO.Storage" Version="6.*" />


<PackageReference Include="Regira.IO.Storage.Azure" Version="6.*" />


<PackageReference Include="Regira.IO.Storage.SSH" Version="6.*" />


<PackageReference Include="Regira.IO.Storage.GitHub" Version="6.*" />

Quick Start

services.AddSingleton<IFileService>(_ =>
    new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/uploads" }));

// In a service
var bytes = await storage.GetBytes("invoices/2024/inv-001.pdf");
await storage.Save("exports/report.pdf", pdfBytes);

IFileService

All backends implement this interface. Identifiers are relative paths within the storage root (e.g. "folder/file.pdf"). URIs are the absolute addresses returned by GetAbsoluteUri.

Read

Task<bool>                Exists(string identifier)
Task<byte[]?>             GetBytes(string identifier)
Task<Stream?>             GetStream(string identifier)
Task<IEnumerable<string>> List(FileSearchObject? so = null)

Write

Task<string> Save(string identifier, byte[] bytes,  string? contentType = null)
Task<string> Save(string identifier, Stream stream, string? contentType = null)
Task         Move(string sourceIdentifier, string targetIdentifier)
Task         Delete(string identifier)

Save returns the final identifier used — it may differ if the backend renames on conflict.

Passing an IMemoryFile to Save? Read it with GetBytes(), not .Bytes. IMemoryFile extends both IMemoryBytesFile (Bytes) and IMemoryStreamFile (Stream), and a producer fills exactly one — which one varies per method, not per implementation. .Bytes is therefore null for anything built from a stream (an Excel export, a PDF merge, a database backup), and Save(identifier, null!) writes an empty file with no exception. GetBytes()/GetStream() (MemoryFileExtensions, Regira.IO.Extensions in Regira.Common) normalise both halves.

URI helpers

string  Root { get; }                        // storage root URI / path
string  GetAbsoluteUri(string identifier)    // relative → absolute
string  GetIdentifier(string uri)            // absolute → relative
string? GetRelativeFolder(string identifier) // extract parent folder

File Identification

Files are addressed uniformly across all backends using three coordinated concepts.

IFileService.Root

Every IFileService implementation exposes a Root — the backend-specific base address for that storage scope:

Implementation Root example
BinaryFileService /var/app/storage (local path)
BinaryBlobService https://account.blob.core.windows.net/my-container
SftpService /home/deploy/files (remote base directory)
GitHubService https://api.github.com/repos/owner/repo/contents/

All IFileService methods (GetBytes, Save, List, …) accept and return identifiers — paths relative to this root — keeping consuming code independent of the backend.

IBinaryFile.Identifier and IBinaryFile.Prefix

BinaryFileItem (and anything implementing IBinaryFile / IStorageFile) carries two address parts:

Property Description Example
Prefix Sub-folder path below the root, excluding the filename "invoices/2024/"
Identifier Prefix + FileName — the relative key used in all IFileService calls "invoices/2024/inv-001.pdf"
Path Full absolute address — Root + Identifier /var/app/storage/invoices/2024/inv-001.pdf
Root        →  /var/app/storage/
Prefix      →                   invoices/2024/
FileName    →                                 inv-001.pdf
Identifier  →                   invoices/2024/inv-001.pdf
Path        →  /var/app/storage/invoices/2024/inv-001.pdf

Converting between identifier and absolute URI

IFileService provides helpers to move between the two representations:

IFileService service = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });

string absolute = service.GetAbsoluteUri("invoices/2024/inv-001.pdf");
// → /var/app/storage/invoices/2024/inv-001.pdf  (or the equivalent Azure/SFTP URL)

string identifier = service.GetIdentifier("/var/app/storage/invoices/2024/inv-001.pdf");
// → invoices/2024/inv-001.pdf

string? folder = service.GetRelativeFolder("invoices/2024/inv-001.pdf");
// → invoices/2024

Use Identifier as the portable key that survives a backend swap; only resolve to Path when you need the actual physical/network address.

FileSearchObject

Filter parameter for List().

Property Type Default Description
FolderUri string? null Restrict to this folder
Extensions ICollection<string>? null Filter by extension — e.g. [".jpg", ".png"]
Recursive bool false Include subdirectories
Type FileEntryTypes All Files, Directories, or All
IFileService storage = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });

var images = await storage.List(new FileSearchObject
{
    FolderUri  = "products/",
    Extensions = [".jpg", ".webp"],
    Recursive  = true,
    Type       = FileEntryTypes.Files
});

Implementations

File System (BinaryFileService)

Stores files on the local disk.

Package: Regira.IO.Storage

var service = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });

Network shares — for a UNC path protected by a username & password, use NetworkFileService with a NetworkShareCommunicator. The communicator authenticates against the share lazily on the first file operation (or eagerly via await communicator.Open()); dispose it on application shutdown to release the connection.

services.AddSingleton(new NetworkFileSystemOptions
{
    RootFolder = @"\\fileserver\share\uploads",
    UserName   = configuration["Storage:Share:UserName"]!,
    Password   = configuration["Storage:Share:Password"],
    Domain     = configuration["Storage:Share:Domain"]   // optional — or "DOMAIN\user" as UserName
});
services.AddSingleton<NetworkShareCommunicator>();
services.AddSingleton<IFileService, NetworkFileService>();
Option Type Default Description
RootFolder string (required) UNC path — \\server\share[\folder]
UserName string (required) Login username
Password string? null Login password
Domain string? null Optional domain, prepended as DOMAIN\user
Contained bool true Reject identifiers that escape RootFolder

Windows only (uses the WNet API). On Linux/macOS, mount the share at OS level (e.g. mount.cifs) and use plain FileSystemOptions. Connections are ref-counted per share + user across the process — the share connection is only released when the last communicator on it is disposed. If the connection is cancelled outside the process (e.g. net use /delete), call await communicator.Reconnect() to re-establish it; Close() only drops this communicator's reference.

Text files — use TextFileService directly, or wrap any IFileService with the DefaultTextFileService decorator:

var text = new DefaultTextFileService(anyFileService, Encoding.UTF8);
string? content = await text.GetContents("config/app.json");
await text.Save("config/app.json", jsonString);

Azure Blob Storage (BinaryBlobService)

Package: Regira.IO.Storage.AzureNuGet dependency: Azure.Storage.Blobs

var communicator = new AzureCommunicator(new AzureOptions
{
    ConnectionString = "DefaultEndpointsProtocol=https;AccountName=…",
    ContainerName    = "my-container"
});
await communicator.Open();   // idempotent — safe to call multiple times

var service = new BinaryBlobService(communicator);
Option Type Default Description
ConnectionString string? null Azure Storage connection string
ContainerName string? null Blob container name
CreateContainerIfNotExists bool true Create the container when missing — set false to fail fast on misconfigured names

SSH / SFTP (SftpService)

Package: Regira.IO.Storage.SSHNuGet dependency: SSH.NET

var communicator = new SftpCommunicator(new SftpConfig
{
    Host          = "sftp.example.com",
    Port          = 22,
    UserName      = "deploy",
    Password      = "s3cr3t",
    ContainerName = "/home/deploy/files"
});

var service = new SftpService(communicator);
Option Type Default Description
Host string (required) SSH server hostname
Port int 22 SSH port
UserName string (required) Login username
Password string? null Login password
ContainerName string? "/" Remote base directory
Contained bool true Reject identifiers that escape ContainerName

SftpCommunicator holds a single persistent connection. Dispose it on application shutdown.


GitHub (GitHubService)

Package: Regira.IO.Storage.GitHubNuGet dependency: none (uses HttpClient)

ISerializer jsonSerializer = new JsonSerializer();   // e.g. Regira.Serializing.Newtonsoft

var service = new GitHubService(
    new GitHubCommunicator(new GitHubOptions
    {
        Uri       = "https://api.github.com/repos/owner/repo",
        Key       = "ghp_xxxxxxxxxxxx",   // PAT — optional for public-repo reads
        UserAgent = "MyApp/1.0"
    }),
    jsonSerializer
);
Option Type Description
Uri string GitHub API repository endpoint (https://api.github.com/repos/{owner}/{repo})
Key string? Personal Access Token (required for writes and private repos)
UserAgent string? User-Agent header — GitHub requires a non-empty value
Branch string Branch used for writes (default "main")
CommitMessage string? Commit message for Save/Delete (default: auto-generated)
ContentPath string? Sub-path within the repository used as Root

Fully implements IFileServiceSave/Move/Delete create commits on Branch, so it's not suited for high-frequency writes.

ZIP / Compression

ZipFileService — browse an archive via IFileService

ZipFileService implements IFileService and IDisposable. Construct it with a ZipFileCommunicator that points to an existing archive or starts a fresh one:

// Open an existing zip file
using var zipService = new ZipFileService(new ZipFileCommunicator { SourceFile = existingZip });
var entries = await zipService.List();
var bytes   = await zipService.GetBytes("report.pdf");

// Start a new empty archive
using var newZip = new ZipFileService(new ZipFileCommunicator());
await newZip.Save("data.csv", csvBytes);
ZipFileCommunicator Type Description
SourceFile IMemoryFile? Existing zip to open — omit to start empty
Password string? Currently not consumed by ZipFileService — for password-protected ZIPs use Regira.IO.Compression.SharpZipLib (see Compression)

ZipFileServiceFactory is a convenience wrapper: new ZipFileServiceFactory().Create(sourceFile, password) is equivalent to constructing ZipFileService directly.

ZipBuilder — create archives

byte[] pdfBytes = [/* … */], csvBytes = [/* … */];

IMemoryFile zip = await new ZipBuilder()
    .For([new BinaryFileItem { FileName = "report.pdf", Bytes = pdfBytes },
          new BinaryFileItem { FileName = "data.csv",   Bytes = csvBytes }])
    .Build();

ZipUtility — zip/unzip helpers

Zip is an extension method; Unzip is a static method on ZipUtility.

IMemoryFile zipFromFiles    = files.Zip();                             // collection → zip
IMemoryFile zipFromPaths    = paths.Zip(baseFolder: "/var/exports");   // paths → zip
BinaryFileCollection items  = ZipUtility.Unzip(existingZip);           // zip → collection
string[] extracted          = ZipUtility.Unzip(existingZip, targetDirectory: "/tmp/out");

Helpers

FileProcessor — recursive processing

IFileService fileService = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });

await new FileProcessor(fileService).ProcessFiles(
    new FileSearchObject { FolderUri = "exports/", Recursive = true },
    async (identifier, svc) => { /* process each file */ }
);

FileNameHelper — unique filenames

IFileService fileService = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });

var helper = new FileNameHelper(fileService);
string safe = await helper.NextAvailableFileName("invoices/report.pdf");
// → "invoices/report-(1).pdf" when "invoices/report.pdf" already exists

Customise the pattern: new FileNameHelper.Options { NumberPattern = " ({0})" }

ExportHelper — copy between services

IFileService source = new BinaryFileService(new FileSystemOptions { RootFolder = "/var/app/storage" });
IFileService target = new BinaryFileService(new FileSystemOptions { RootFolder = "/mnt/backup" });

await new ExportHelper(source, target)
    .Export(new FileSearchObject { FolderUri = "backups/", Recursive = true });

FileNameUtility — path helpers

FileNameUtility.GetAbsoluteUri("folder/file.txt", root)
FileNameUtility.GetRelativeUri(absolutePath, root)
FileNameUtility.GetCleanFileName("folder/sub/file.txt")  // → "file.txt"
FileNameUtility.Combine("folder", "sub", "file.txt")
FileNameUtility.SanitizeFilename(@"CON\report.txt")      // → @"_XXX_\report.txt" — replaces path segments that exactly match a Windows reserved name ("con.txt" is left as-is)
FileNameUtility.GetUncShareRoot(@"\\server\share\sub")   // → @"\\server\share" (null for non-UNC)

Overview

  1. Index — Overview, interface, and implementation reference
  2. Examples — Backend swap, transform & re-upload, GitHub→Azure mirror, ZIP export, safe upload
  3. Compression — Password-protected ZIP via SharpZipLib

License

Apache License 2.0 — this package contains no license validation and no runtime limits. See LICENSE. A few companion packages are commercially licensed with a free tier; see the licensing overview.

Product 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (7)

Showing the top 5 NuGet packages that depend on Regira.IO.Storage:

Package Downloads
Regira.Entities

Abstractions and base types for the Regira CRUD entity framework.

Regira.Invoicing.Billit

Billit API integration for invoicing in Regira.

Regira.IO.Storage.GitHub

GitHub-based file storage implementation for Regira IO storage.

Regira.IO.Storage.Azure

Azure Blob Storage implementation of Regira's IO storage abstractions.

Regira.System.Projects

Project file and solution structure utilities for Regira system tools.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.1.2 135 8/16/2026
6.1.1 209 8/12/2026
6.1.0 277 8/10/2026