Table of Contents

IFileSystem and IFileProvider

Two adapter packages let code written against .NET's file abstractions use files on an SSH server, unchanged, over SFTP. Nothing is mounted: only your application sees the files.

Package Type Interface
SshSharp.IO.Abstractions SftpFileSystem System.IO.Abstractions' IFileSystem: File, Directory, Path, FileInfo, streams. Read and write.
SshSharp.FileProviders SftpFileProvider Microsoft.Extensions.FileProviders.IFileProvider: ASP.NET Core static files, Razor views, configuration. Read-only.

When to use

  • IFileSystem: you already have (or want) code that takes an IFileSystem, so it can run against the local disk, an in-memory file system in tests, or a server.
  • IFileProvider: a framework component reads files through IFileProvider and you want it to read them from a server.
  • For transfers and full control, use SftpClient directly: the adapters add convenience, not speed.

Code against IFileSystem

dotnet add package SshSharp.IO.Abstractions
/// <summary>Code written against IFileSystem: it does not know, or care, where the files are.</summary>
public sealed class ReportWriter(IFileSystem fileSystem)
{
    public async Task WriteAsync(string directory, string name, string text, CancellationToken cancellationToken)
    {
        fileSystem.Directory.CreateDirectory(directory);
        var path = fileSystem.Path.Combine(directory, name);
        await fileSystem.File.WriteAllTextAsync(path, text, cancellationToken);
    }

    public IEnumerable<string> List(string directory) =>
        fileSystem.Directory.EnumerateFiles(directory, "*.txt", SearchOption.AllDirectories);
}
await using var sftp = await client.OpenSftpAsync(cancellationToken);
IFileSystem remote = new SftpFileSystem(sftp, new SftpFileSystemOptions { OperationTimeout = TimeSpan.FromSeconds(30) });

var writer = new ReportWriter(remote);                    // the same class works with new FileSystem() for local files
await writer.WriteAsync("reports/2026", "summary.txt", "All green.", cancellationToken);
foreach (var file in writer.List("reports"))
{
    Console.WriteLine(file);                               // POSIX paths: reports/2026/summary.txt
}

Streams from the adapter are ordinary streams, so local and remote combine:

// A local file into a remote one, stream to stream.
await using var sftp = await client.OpenSftpAsync(cancellationToken);
IFileSystem remote = new SftpFileSystem(sftp);

await using var source = File.OpenRead("build/app.zip");
await using var target = remote.File.Create("/srv/drop/app.zip");
await source.CopyToAsync(target, cancellationToken);

Serve files with IFileProvider

dotnet add package SshSharp.FileProviders
// Static files served from /srv/www on the server. The provider keeps one SFTP session for the application's lifetime.
var ssh = await SshClient.ConnectAsync(app.Configuration["Ssh:Alias"] ?? "web-content");
var sftp = await ssh.OpenSftpAsync();
var provider = new SftpFileProvider(sftp, "/srv/www", new SftpFileProviderOptions { CacheDuration = TimeSpan.FromSeconds(30) });

app.UseStaticFiles(new StaticFileOptions { FileProvider = provider, RequestPath = "/content" });
app.Lifetime.ApplicationStopped.Register(() =>
{
    sftp.DisposeAsync().AsTask().GetAwaiter().GetResult();   // the provider does not own the session
    ssh.DisposeAsync().AsTask().GetAwaiter().GetResult();
});

It also feeds configuration:

// Read a JSON configuration file that lives on the server (read once: SFTP has no change notifications).
var provider = new SftpFileProvider(sftp, "/etc/myapp");
configuration.AddJsonFile(provider, "appsettings.json", optional: false, reloadOnChange: false);

Notes and pitfalls

  • Paths are POSIX. SftpFileSystem.Path uses / on every client OS, and \ is an ordinary character. Relative paths start at the login directory.
  • Synchronous calls block. IFileProvider is synchronous by contract, and so are many IFileSystem methods: each waits for an SFTP round trip and holds a thread meanwhile. Prefer the ...Async methods of IFileSystem under load, and keep the provider's cache on (SftpFileProviderOptions.CacheDuration, 5 seconds by default).
  • Timeouts. SftpFileSystemOptions.OperationTimeout (60 s) and SftpFileProviderOptions.OperationTimeout (30 s) bound each request; for whole-file reads and writes the file system's timeout bounds time without progress, so large files are not cut off.
  • Errors. SftpFileSystem throws the System.IO exceptions code expects (FileNotFoundException, UnauthorizedAccessException, IOException). SftpFileProvider reports a missing or unreadable file as Exists == false.
  • No change notifications. SFTP has none: FileSystemWatcher is not supported, and the provider's Watch never fires, so reloadOnChange has no effect.
  • Ownership. Neither adapter owns the SftpClient. Dispose it (and the SshClient) after the adapter is no longer used.
  • Not supported by SftpFileSystem: DriveInfo, FileVersionInfo, RandomAccess, encryption, Replace; SFTP has no creation times. The SFTP guide has the full table.

See also