65.9K
CodeProject is changing. Read more.
Home

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

Jul 14, 2020

CPOL

1 min read

viewsIcon

41392

downloadIcon

1002

Using SFTP/FTP 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