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 anIFileSystem, so it can run against the local disk, an in-memory file system in tests, or a server.IFileProvider: a framework component reads files throughIFileProviderand you want it to read them from a server.- For transfers and full control, use
SftpClientdirectly: 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.Pathuses/on every client OS, and\is an ordinary character. Relative paths start at the login directory. - Synchronous calls block.
IFileProvideris synchronous by contract, and so are manyIFileSystemmethods: each waits for an SFTP round trip and holds a thread meanwhile. Prefer the...Asyncmethods ofIFileSystemunder load, and keep the provider's cache on (SftpFileProviderOptions.CacheDuration, 5 seconds by default). - Timeouts.
SftpFileSystemOptions.OperationTimeout(60 s) andSftpFileProviderOptions.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.
SftpFileSystemthrows theSystem.IOexceptions code expects (FileNotFoundException,UnauthorizedAccessException,IOException).SftpFileProviderreports a missing or unreadable file asExists == false. - No change notifications. SFTP has none:
FileSystemWatcheris not supported, and the provider'sWatchnever fires, soreloadOnChangehas no effect. - Ownership. Neither adapter owns the
SftpClient. Dispose it (and theSshClient) 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
- File transfer (SFTP) (in-depth guide)
- ASP.NET Core and dependency injection
- SftpFileSystem and SftpFileProvider in the API reference