SFTP Client using .NET
Introduction You'd like to add, fetch, and possibly delete files on a locally available or remote server, and you're looking for the best way to do it. Well, SFTP is the solution you're looking for since it is, secure, offers authentication, easy to use, needs a single connection, offers resumable transfers, and is very well documented. Creating an SFTP server Once can create an SFTP server by following the instructions for their operating system. For instance, here is how to do it on Ubuntu/Debian. Also, one can use a readily available sftp server such as FileZilla's. Methods of usage Similar to hosting a server, one can either use an SFTP client or write a script to automate a process using SFTP. This article illustrates how to write an SFTP client using modern .NET. The Client The client is self explanatory and pretty easy to use by another service. One can also turn it into a library. Without further ado, let us start creating it. Dependency - SSH.NET SSH.NET is the dependency we would use in order to communicate with the SFTP server. Code The code works as follows: Connect to the SFTP server when the application starts. Be ready to do any SFTP-related operation using that connection. Close the connection upon program termination The service supports dependency injection out of the box The appsettings.json configuration file: // Configuration before... "Sftp": { "IsConnected": false, "Host": "192.168.XXX.YYY", "Username": "sftpUserHere", "Password": "sftpPasswordHere", "BasePath": "/Server/MyApp/Files" }, // Configuration after... Note that IsConnected is needed in cases where one needs to use dependency injection for their service/API while the sftp server is unavailable at the moment. The ISftpUtility interface public interface ISftpUtillity { Task DownloadFileBytesAsync(string remotePath); Task DownloadFileBase64Async(string remotePath); void DisposeConnection(); } And below is the SftpUtility using System; using Microsoft.Extensions.Configuration; using Renci.SshNet; public class SftpUtility : ISftpUtillity { private readonly SftpClient _sftpClient; public SftpUtility(IConfiguration configuration) { var isSftp = configuration.GetSection("Sftp:IsConnected").Get(); if(isSftp) { var host = configuration.GetSection("Sftp:Host").Get(); var username = configuration.GetSection("Sftp:Username").Get(); var password = configuration.GetSection("Sftp:Password").Get(); _sftpClient = new SftpClient(host ?? "", username ?? "", password ?? ""); _sftpClient.Connect(); } } public async Task DownloadFileBytesAsync(string remotePath) { try { using (var memoryStream = new MemoryStream()) { await Task.Run(() => _sftpClient.DownloadFile(remotePath, memoryStream)); return (memoryStream.ToArray(), GetContentTypeFromPath(remotePath)); } } catch (Exception ex) { Console.WriteLine($"Error downloading file: {ex.Message}"); return (null, null); } } public async Task DownloadFileBase64Async(string remotePath) { var (content, contentType) = await DownloadFileBytesAsync(remotePath); if (content != null) return (Convert.ToBase64String(content), contentType); return (null, null); } public void DisposeConnection() { _sftpClient?.Disconnect(); _sftpClient?.Dispose(); } private string GetContentTypeFromPath(string path) { string extension = Path.GetExtension(path).ToLowerInvariant(); switch (extension) { case ".pdf": return "application/pdf"; case ".jpg": case ".jpeg": return "image/jpeg"; case ".png": return "image/png"; case ".txt": return "text/plain"; case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; // Add more content types as needed default: return "application/octet-stream"; // Default for unknown types } } } Disposing the connection In ASP.NET, the connection can be disposed by tapping in the ApplicationStopping lifetime method, like so Program.cs app.Lifetime.ApplicationStopping.Register(() => { Console.WriteLine("Application is stopping..."); using (var scope = app.Services.CreateScope()) { var serviceProvider = scope.ServiceProvider; var sftpUtility = serviceProvider.GetRequiredService(); sftpUtility.DisposeConnection(); } }); Room for improvement One can easily use the same library to upload or delete files on the remote server. Conclusion This was a short tutorial illustrating the
Introduction
You'd like to add, fetch, and possibly delete files on a locally available or remote server, and you're looking for the best way to do it.
Well, SFTP is the solution you're looking for since it is, secure, offers authentication, easy to use, needs a single connection, offers resumable transfers, and is very well documented.
Creating an SFTP server
Once can create an SFTP server by following the instructions for their operating system. For instance, here is how to do it on Ubuntu/Debian. Also, one can use a readily available sftp server such as FileZilla's.
Methods of usage
Similar to hosting a server, one can either use an SFTP client or write a script to automate a process using SFTP. This article illustrates how to write an SFTP client using modern .NET.
The Client
The client is self explanatory and pretty easy to use by another service. One can also turn it into a library. Without further ado, let us start creating it.
Dependency - SSH.NET
SSH.NET is the dependency we would use in order to communicate with the SFTP server.
Code
The code works as follows:
- Connect to the SFTP server when the application starts.
- Be ready to do any SFTP-related operation using that connection.
- Close the connection upon program termination
- The service supports dependency injection out of the box
The appsettings.json
configuration file:
// Configuration before...
"Sftp": {
"IsConnected": false,
"Host": "192.168.XXX.YYY",
"Username": "sftpUserHere",
"Password": "sftpPasswordHere",
"BasePath": "/Server/MyApp/Files"
},
// Configuration after...
Note that IsConnected
is needed in cases where one needs to use dependency injection for their service/API while the sftp server is unavailable at the moment.
The ISftpUtility interface
public interface ISftpUtillity
{
Task<(byte[] Content, string ContentType)> DownloadFileBytesAsync(string remotePath);
Task<(string Base64Content, string ContentType)> DownloadFileBase64Async(string remotePath);
void DisposeConnection();
}
And below is the SftpUtility
using System;
using Microsoft.Extensions.Configuration;
using Renci.SshNet;
public class SftpUtility : ISftpUtillity
{
private readonly SftpClient _sftpClient;
public SftpUtility(IConfiguration configuration)
{
var isSftp = configuration.GetSection("Sftp:IsConnected").Get<bool>();
if(isSftp)
{
var host = configuration.GetSection("Sftp:Host").Get<string>();
var username = configuration.GetSection("Sftp:Username").Get<string>();
var password = configuration.GetSection("Sftp:Password").Get<string>();
_sftpClient = new SftpClient(host ?? "", username ?? "", password ?? "");
_sftpClient.Connect();
}
}
public async Task<(byte[] Content, string ContentType)> DownloadFileBytesAsync(string remotePath)
{
try
{
using (var memoryStream = new MemoryStream())
{
await Task.Run(() => _sftpClient.DownloadFile(remotePath, memoryStream));
return (memoryStream.ToArray(), GetContentTypeFromPath(remotePath));
}
}
catch (Exception ex)
{
Console.WriteLine($"Error downloading file: {ex.Message}");
return (null, null);
}
}
public async Task<(string Base64Content, string ContentType)> DownloadFileBase64Async(string remotePath)
{
var (content, contentType) = await DownloadFileBytesAsync(remotePath);
if (content != null)
return (Convert.ToBase64String(content), contentType);
return (null, null);
}
public void DisposeConnection()
{
_sftpClient?.Disconnect();
_sftpClient?.Dispose();
}
private string GetContentTypeFromPath(string path)
{
string extension = Path.GetExtension(path).ToLowerInvariant();
switch (extension)
{
case ".pdf": return "application/pdf";
case ".jpg":
case ".jpeg": return "image/jpeg";
case ".png": return "image/png";
case ".txt": return "text/plain";
case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
// Add more content types as needed
default: return "application/octet-stream"; // Default for unknown types
}
}
}
Disposing the connection
In ASP.NET, the connection can be disposed by tapping in the ApplicationStopping lifetime method, like so
Program.cs
app.Lifetime.ApplicationStopping.Register(() =>
{
Console.WriteLine("Application is stopping...");
using (var scope = app.Services.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var sftpUtility = serviceProvider.GetRequiredService<ISftpUtillity>();
sftpUtility.DisposeConnection();
}
});
Room for improvement
One can easily use the same library to upload or delete files on the remote server.
Conclusion
This was a short tutorial illustrating the usage and benefits of using SFTP in the case of writing an API or a service when file management is needed.