Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET CORE: Using SFTP/FTP in ASP.NET CORE Projects

0.00/5 (No votes)
14 Jul 2020 1  
Using SFTP/FTP in ASP.NET CORE projects
This post is going to demonstrate how to do SFTP/FTP interactions and operations in ASP.NET CORE projects.

Background

In this article, we will check FTP/SFTP usages in ASP.NET CORE projects. We are going to create some FTP/SFTP client class to connect and do operations on FTP/SFTP systems.

The operation includes:

  • Connecting/disconnecting with the server
  • Uploading/downloading files
  • Checking files/directories
  • Creating/deleting files/directories
  • And others

Common

Here are some common Interfaces to create some adapter classes for SFTP and FTP clients.

using System;

namespace FileSystem.Core.Remote
{
  public interface IFileSystem
  {
    bool FileExists(string filePath);
    bool DirectoryExists(string directoryPath);
    void CreateDirectoryIfNotExists(string directoryPath);
    void DeleteFileIfExists(string filePath);
  }

    public interface IRemoteFileSystemContext : IFileSystem, IDisposable 
  {
    bool IsConnected();
    void Connect();
    void Disconnect();

    void SetWorkingDirectory(string path);
    void SetRootAsWorkingDirectory();

    void UploadFile(string localFilePath, string remoteFilePath);
    void DownloadFile(string localFilePath, string remoteFilePath);

    string ServerDetails();
  }
}

FTP

For FTP works, we are going to use FluentFTP. We can add the below code to our .csproj file or install it using NuGet.

<ItemGroup>
 <PackageReference Include="FluentFTP" Version="19.2.2" />
</ItemGroup>

Let's create the adapter class by implementing the interface.

using FluentFTP;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;

namespace FileSystem.Core.Remote
{
  /*FluentFTP example : https://github.com/robinrodricks/FluentFTP
   * Local Directory:       @"C:\Files\Temp\file.csv"
   *                @"C:\Files\Temp"
   * Ftp Directory(FluentFTP):   @"/Files/Temp/file.csv"
   *                @"/Files/Temp/
   */
  public abstract class FtpContext : IRemoteFileSystemContext
  {
    protected IFtpClient FtpClient { get; set; }

    public void Connect()
    {
      FtpClient.Connect();
    }

    public void Disconnect()
    {
      FtpClient.Disconnect();
    }

    public void Dispose()
    {
      if (FtpClient != null && !FtpClient.IsDisposed)
      {
        FtpClient.Dispose();
      }
    }

    /*actions*/
    public bool FileExists(string filePath)
    {
      return FtpClient.FileExists(filePath);
    }

    public void DeleteFileIfExists(string filePath)
    {
      if (!FileExists(filePath))
      {
        FtpClient.DeleteFile(filePath);
      }
    }

    public void UploadFile(string localFilePath, string remoteFilePath)
    {
      FtpClient.UploadFile(localFilePath, remoteFilePath);
    }

    public bool DirectoryExists(string directoryPath)
    {
      return FtpClient.DirectoryExists(directoryPath);
    }

    public void CreateDirectoryIfNotExists(string directoryPath)
    {
      if (!DirectoryExists(directoryPath))
      {
        FtpClient.CreateDirectory(directoryPath);
      }
    }

    public void DownloadFile(string localFilePath, string remoteFilePath)
    {
      FtpClient.DownloadFile(localFilePath, remoteFilePath);
    }

    public bool IsConnected()
    {
      return FtpClient.IsConnected;
    }

    public void SetWorkingDirectory(string directoryPath)
    {
      FtpClient.SetWorkingDirectory(directoryPath);
    }

    public void SetRootAsWorkingDirectory()
    {
      SetWorkingDirectory("");
    }

    public abstract string ServerDetails();
  }
}

Inheriting the adapter class to set connection details from setting:RemoteSystemSetting object.

using FileSystem.Core.Remote;
using FluentFTP;

namespace ConsoleApp.Test
{
  class FtpRemoteFileSystem : FtpContext
  {
    private string _serverDetails;

    public FtpRemoteFileSystem(RemoteSystemSetting setting)
    {
      _serverDetails = FtpHelper.ServerDetails
           (setting.Host, setting.Port.ToString(), setting.UserName, setting.Type);
      FtpClient = new FtpClient(setting.Host);
      FtpClient.Credentials = new System.Net.NetworkCredential
                                   (setting.UserName, setting.Password);
      FtpClient.Port = setting.Port;
    }

    public override string ServerDetails()
    {
      return _serverDetails;
    }
  }
}

SFTP

For FTP works, we are going to use SSH.NET. We can add below code to our .csproj file or install it using NuGet.

<ItemGroup>
 <PackageReference Include="SSH.NET" Version="2016.1.0" />
</ItemGroup>

Let's create the adapter class by implementing the interface.

using FluentFTP;
using Renci.SshNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace FileSystem.Core.Remote
{
  /* SshNet example : https://github.com/sshnet/SSH.NET
   * Local Directory:       @"C:\Files\Temp\file.csv"
   *                @"C:\Files\Temp"
   * Ftp Directory(FluentFTP):   @"/Files/Temp/file.csv"
   *                @"/Files/Temp/
   */
  public abstract class SftpContext : IRemoteFileSystemContext
  {
    protected SftpClient SftpClient { get; set; }
     
    public void Connect()
    {
      SftpClient.Connect();
    }

    public void Disconnect()
    {
      SftpClient.Disconnect();
    }

    public void Dispose()
    {
      if (SftpClient != null)
      {
        SftpClient.Dispose();
      }
    }

    /*actions*/
    public bool FileExists(string filePath)
    {
      return SftpClient.Exists(filePath);
    }

    public void DeleteFileIfExists(string filePath)
    {
      if (!FileExists(filePath))
      {
        SftpClient.DeleteFile(filePath);
      }
    }

    public void UploadFile(string localFilePath, string remoteFilePath)
    {
      var fileStream = new FileStream(localFilePath, FileMode.Open);
      SftpClient.UploadFile(fileStream, remoteFilePath);
    }

    public bool DirectoryExists(string directoryPath)
    {
      return SftpClient.Exists(directoryPath);
    }

    public void CreateDirectoryIfNotExists(string directoryPath)
    {
      if (!DirectoryExists(directoryPath))
      {
        SftpClient.CreateDirectory(directoryPath);
      }
    }

    public void DownloadFile(string localFilePath, string remoteFilePath)
    {
      using (Stream fileStream = File.Create(localFilePath))
      {
        SftpClient.DownloadFile(remoteFilePath, fileStream);
      }
    }

    public bool IsConnected()
    {
      return SftpClient.IsConnected;
    }

    public void SetWorkingDirectory(string directoryPath)
    {
      SftpClient.ChangeDirectory(directoryPath);
    }

    public void SetRootAsWorkingDirectory()
    {
      SetWorkingDirectory("");
    }

    public abstract string ServerDetails();
  }
}

Inheriting the adapter class to set connection details from setting:RemoteSystemSetting object.

using FileSystem.Core.Remote;
using Renci.SshNet;

namespace ConsoleApp.Test
{
  public class SftpRemoteFileSystem : SftpContext
  {
    private string _serverDetails;

    public SftpRemoteFileSystem(RemoteSystemSetting setting)
    {
      _serverDetails = FtpHelper.ServerDetails
           (setting.Host, setting.Port.ToString(), setting.UserName, setting.Type);
      var connectionInfo = new ConnectionInfo
           (setting.Host, setting.Port, setting.UserName, 
           new PasswordAuthenticationMethod(setting.UserName, setting.Password));
      SftpClient = new SftpClient(connectionInfo);
    }

    public override string ServerDetails()
    {
      return _serverDetails;
    }
  }
}

Using the Code

Here are the uses of the SFTP/FTP adapter classes:

/*Set your SFTP/FTP server details here*/
RemoteSystemSetting setting = new RemoteSystemSetting()
{
   Host = "xx.xx.xx.xx",  /*host ip*/
   Port = 21,        /*ftp:21, sftp:22*/
   UserName = "xyz",
   Password = "abc"
};

IRemoteFileSystemContext remote = new FtpRemoteFileSystem(setting);        
/*to use SFTP remote = new SftpRemoteFileSystem(setting);*/

remote.Connect();                    /*establish connection*/
remote.SetRootAsWorkingDirectory();           /*set root as work directory*/
remote.DownloadFile("C:\\1.txt", "/files/test/1.txt");  /*download file*/
remote.UploadFile("C:\\2.txt", "/files/test/2.txt");   /*upload upload file*/

/*others*/
bool isConnected = remote.IsConnected();         /*check connection done or not*/
remote.Disconnect();                   /*stop connection*/
remote.Dispose();                    /*dispose*/
remote.DirectoryExists("/files/test/");         /*check if directory exists or not*/
remote.CreateDirectoryIfNotExists("/files/test/");    /*create directory*/
remote.FileExists("/files/test/1.txt");         /*check if file exists or not*/
remote.DeleteFileIfExists("/files/test/1.txt");     /*delete file*/
remote.SetWorkingDirectory("/files/test");        /*set other directory as root*/

Future Works

Going to add them soon.

/*get all file names*/
/*get all directory names*/
/*download all files*/
/*upload all files*/

Other Helper Class

Local File System Helper

This helper class can be used to manage the local path, directory, and file.

using System.IO;

namespace ConsoleApp.Test
{
  public class FileSystemHelper
  {
    /// <summary>
    /// string p1 = "c:\\temp\\";
    /// string p2 = "\\subdir\\file\\";
    /// to c:\temp\subdir\file
    /// </summary>
    public static string CombineDirectory
              (string rootDirectoryPath, string childDirectoryPath)
    {
      rootDirectoryPath = rootDirectoryPath.TrimEnd('\\');
      childDirectoryPath = childDirectoryPath.Trim('\\');
      return Path.Combine(rootDirectoryPath, childDirectoryPath);
    }

    /// <summary>
    /// string p1 = "c:\\temp\\";
    /// string p2 = "\\file.text";
    /// to c:\temp\file.text
    /// </summary>
    public static string CombineFile(string rootDirectoryPath, string filePathOrName)
    {
      rootDirectoryPath = rootDirectoryPath.TrimEnd('\\');
      filePathOrName = filePathOrName.Trim('\\');
      return Path.Combine(rootDirectoryPath, filePathOrName);
    }

    public static void CreateDirectoryIfNotExists(string directoryPath)
    {
      if (!DirectoryExists(directoryPath))
      {
        Directory.CreateDirectory(directoryPath);
      }
    }

    public static void DeleteFileIfExists(string filePath)
    {
      if (FileExists(filePath))
      {
        File.Delete(filePath);
      }
    }

    public static bool DirectoryExists(string directoryPath)
    {
      return Directory.Exists(directoryPath);
    }

    public static bool FileExists(string filePath)
    {
      return File.Exists(filePath);
    }

    /*file*/
    public static void MoveFile(string fromFilePath, string toFilePath)
    {
      File.Move(fromFilePath, toFilePath);
    }

    public static void FileAppendAllText(string filePath, string contents)
    {
      /*create file if doesn't exist and add line*/
      File.AppendAllText(filePath, contents);
    }
  }
}

Remote FTP/SFTP File System Helper

This helper class can be used to manage the FTP/SFTP server path, directory, and file.

using System;

namespace ConsoleApp.Test
{
  public class FtpHelper
  {
    /// <summary>
    /// string p1 = "/temp";
    /// to /temp/
    /// </summary>
    public static string FtpDirectory(string rootDirectory)
    {
      rootDirectory = rootDirectory.Trim('/');
      return string.Format(@"/{0}/", rootDirectory);
    }

    /// <summary>
    /// string p1 = "/temp/";
    /// string p2 = "/subdir/file/";
    /// to /temp/subdir/file/
    /// </summary>
    public static string CombineDirectory(string rootDirectory, string childDirectory)
    {
      rootDirectory = rootDirectory.Trim('/');
      childDirectory = childDirectory.Trim('/');
      return string.Format(@"/{0}/{1}/", rootDirectory, childDirectory);
    }

    /// <summary>
    /// string p1 = "/temp/";
    /// string p2 = "file.text";
    /// to /temp/file.text
    /// </summary>
    public static string CombineFile(string rootDirectory, string filePathOrName)
    {
      rootDirectory = rootDirectory.Trim('/'); ;
      filePathOrName = filePathOrName.Trim('/'); ;
      return string.Format(@"/{0}/{1}", rootDirectory, filePathOrName);
    }

    public static string ServerDetails
        (string host, string port, string userName, string type = "FTP")
    {
      return String.Format("Type: '{3}' Host:'{0}' Port:'{1}' 
                                 User:'{2}'", host, port, userName, type);
    }
  }
}

Local Machine Details Helper

This helper class can be used to get current machine details.

using System;

namespace ConsoleApp.Test
{
  public class LocalMachineHelper
  {
    /*
      https://stackoverflow.com/questions/1768198/how-do-i-get-the-computer-name-in-net
      https://stackoverflow.com/questions/1233217/
           difference-between-systeminformation-computername-environment-machinename-and
     */
    public static string ServerDetails()
    {
      string machineName = String.Empty;
      string hostName = String.Empty;
      string computerName = String.Empty;
      try
      {
        machineName = Environment.MachineName;
        hostName = System.Net.Dns.GetHostName();
        computerName = Environment.GetEnvironmentVariable("COMPUTERNAME");
      }
      catch (Exception ex)
      {

      }

      string details = String.Format("MachineName:'{0}' HostName:'{1}' 
                            ComputerName:'{2}'", machineName, hostName, computerName);
      return details;
    }
  }
}

About the Source Code

It a Visual Studio 2017 solution and ASP.NET Core 2.2 projects.

  • ConsoleApp.Test: console app
  • FileSystem.Core: SFTP/FTP codes here

The code may throw unexpected errors for untested inputs. If any, just let me know.

How to do the Same for ASP.NET?

History

  • 14th July, 2020: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here