0

I have a project with ftp and sftp calls (using System.Net.FtpClient and Renci.SshNet). I would like to have standard calls in both cases, i.e. call a Connect function and Upload function with the same parameters. I feel like I have to use an interface but I am green with interfaces and I am stuck. I need some directions here. I implemented 2 functions so far to test but it's not elegant and there must be some other way. For example I am passing an object as a parameter in GetWorkingDirectory but this feels wrong and I can't see how to do this correctly.

Here is the interface I have:

interface IRemoteCopy
{
    object Connect(string Host, int Port, string Username, string Password, string Fingerprint);
    string GetWorkingDirectory(object objRemote, string Directory);
}

Here are the classes I have:

public class FTPCopy : IRemoteCopy
{
    public object Connect(string Host, int Port, string Username, string Password, string Fingerprint)
    {
        int ftpPort = 21;      // default ftp port

        if (Port == 0)
            ftpPort = Port;

        FtpClient ftp = new FtpClient();

        ftp.Host = Host;
        ftp.Port = ftpPort;
        ftp.Credentials = new NetworkCredential(Username, Password);

        return ftp;
    }

    public string GetWorkingDirectory(object objftp, string Directory)
    {
        FtpClient ftp = (FtpClient)objftp;

        ftp.SetWorkingDirectory(Directory);

        return ftp.GetWorkingDirectory();
    }
}

public class SFTPCopy : IRemoteCopy
{
    public object Connect(string Host, int Port, string Username, string Password, string Fingerprint)
    {
        int sftpPort = 22;      // default sftp port

        if (Port == 0)
            sftpPort = Port;

        ConnectionInfo connInfo = new ConnectionInfo(Host, sftpPort, Username, new AuthenticationMethod[]{
                new PasswordAuthenticationMethod(Username, Password)
            });

        SftpClient sftp = new SftpClient(connInfo);
        sftp.HostKeyReceived += delegate(object sender, HostKeyEventArgs e)
        {
            if (Fingerprint.ToLower() == (e.HostKeyName + " " + e.KeyLength + " " + BitConverter.ToString(e.FingerPrint).Replace("-", ":")).ToLower())
                e.CanTrust = true;
            else
                e.CanTrust = false;
        };

        sftp.Connect();

        return sftp;
    }

    public string GetWorkingDirectory(object objftp, string Directory)
    {
        return Directory;
    }
}

Can anyone guide me here?

2
  • You have two distinct connection types. Either, you use the object as you're doing, and cast it, or, better yet, just have one class with overloaded CTORs, and based on which CTOR is used will determine which class with the base functionality is used. Commented Mar 25, 2016 at 22:07
  • 1
    Wtat about generics? FtpCopy : IRemoteCopy<FtpClient> and SFTPCopy : IRemoteCopy<SftpClient>. Commented Mar 25, 2016 at 22:17

2 Answers 2

1

There are several ways you could solve this. I would suggest you write an interface IWorkingDirectory which the ftp clients implement, that has a method

GetWorkingDirectory(string dir). 

The interface of IRemoteCopy's Connect method would become:

IWorkingDirectory Connect(string Host, int Port, string Username, string Password, string Fingerprint);

Then you could simplify the call to

public string GetWorkingDirectory(IWorkingDirectory client, string dir)
{
    return client.GetWorkingDirectory(dir);
}

Depending on how this is called, you might even consider not implementing this method in IRemoteCopy at all, because most likely you will call Connect before that anyway, so you will be able to call the simple

var client = remoteCopy.Connect(...);
client.GetWorkingDirectory(dir);

You could then effectively make IRemoteCopy an abstract factory of IFtpClient (as the two client classes probably share more than the GetWorkingDirectory method I would suggest making an interface that offers all of the shared functionality).

I propose you check out this site about the open/closed principle: http://joelabrahamsson.com/a-simple-example-of-the-openclosed-principle/

EDIT: I just realized, the client classes were in different assemblies, so are not within your code. In this case it might be best to change the IRemoteCopy method signatures to not receive the clients as parameters. You could have a private field in the concrete classes that hold the respective clients, as obviously they will be always the same per class. You can then set those fields on the Connect call, or directly at class instantiation. I would prefer the latter, because it seems less error prone for the users of IRemoteCopy.

Another possibilty would be to pack those clients into wrappers of your own (eiter by building an adapter - https://en.wikipedia.org/wiki/Adapter_pattern - or if the base clients are not sealed by inheriting from them), so you could use the interface base approach I proposed earlier.

Sign up to request clarification or add additional context in comments.

3 Comments

OK. Almost there. I changed the implementation to hold the FTP/SFTP object but when I try to define a function like: public JsonResult RemoteBrowse(string Protocol, string Host, int Port, string Username, string Password, string Directory) { var Connector; if (Protocol == "ftp") Connector = new FTPConnector(); else if (Protocol == "sftp") Connector = new SFTPConnector(); } there is a problem with initialization of Connector. How can I define this function so that I don't have to duplicate code?
I guess I could have both FtpClient and SFtpClient as members in the class but then that defeats the purpose of having an interface and we're back with a solution using overloading.
I am not sure where the RemoteBrowse method is defined. But I guess the class has a field that holds an IRemoteCopy, right? If you have an interface, it can hold both implementing classes. You might consider creating a RemoteCopyFactory that returns the correct implementation of IRemoteCopy depending on the protocol with a method like GetRemoteCopy(string protocol).
0

After exploring generics, overloaded methods and interfaces, I came up with this solution. Let me know if you think there is something better:

IDirectory.cs:

using System;
using System.Text;

namespace ReleaseManager.Interfaces
{
    interface IDirectory
    {
        void Connect(string Host, int Port, string Username, string Password, string Fingerprint);
        string GetWorkingDirectory(string Directory);
        void ListDirectory(string Directory);
        void CreateDirectory(string Directory);
        void DeleteDirectory(string Directory);
        Boolean DirectoryExists(string Directory);
        void WriteAllText(string FileName, string Content, Encoding enc);
        void CopyFile(string srcFile, string dstFile);
        void Disconnect();
    }

}

Directory.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace ReleaseManager.BusinessObjects
{
    public class RemoteDirInfo
    {
        public string Name { get; set; }
        public string ModifiedDate { get; set; }
    }

    public class RemoteFileInfo
    {
        public string Name { get; set; }
        public string ModifiedDate { get; set; }
        public long Size { get; set; }
    }

    public class RemoteInfo
    {
        public string Error { get; set; }
        public string CurrentDirectory { get; set; }
        public List<RemoteDirInfo> DirInfo { get; set; }
        public List<RemoteFileInfo> FileInfo { get; set; }
    }

}

FTPDirectory.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ReleaseManager.Interfaces;
using System.Net.FtpClient;
using System.Net;
using System.Text;
using System.IO;


namespace ReleaseManager.BusinessObjects
{
    public class FTPDirectory : IDirectory
    {
        private FtpClient client;
        public RemoteInfo Info;

        public FTPDirectory()
        {
            this.client = new FtpClient();

            this.Info = new RemoteInfo();
            this.Info.DirInfo = new List<RemoteDirInfo>();
            this.Info.FileInfo = new List<RemoteFileInfo>();
            this.Info.Error = "";
        }

        public void Connect(string Host, int Port, string Username, string Password, string Fingerprint)
        {
            int ftpPort = 21;      // default ftp port

            if (Port != 0)
                ftpPort = Port;

            this.client.Host = Host;
            this.client.Port = ftpPort;
            this.client.Credentials = new NetworkCredential(Username, Password);
        }

        public string GetWorkingDirectory(string Directory)
        {
            this.client.SetWorkingDirectory(Directory);

            return this.client.GetWorkingDirectory();
        }

        public void ListDirectory(string Directory)
        {
            this.client.SetWorkingDirectory(Directory);

            foreach (var item in this.client.GetListing(this.client.GetWorkingDirectory()))
            {
                switch (item.Type)
                {
                    case FtpFileSystemObjectType.Directory:
                        if (item.Name != "." && item.Name != "..")
                            this.Info.DirInfo.Add(new RemoteDirInfo
                            {
                                Name = item.Name,
                                ModifiedDate = item.Modified.ToString("yyyy/MM/dd HH:mm:ss")
                            });

                        break;

                    case FtpFileSystemObjectType.File:
                        this.Info.FileInfo.Add(new RemoteFileInfo
                        {
                            Name = item.Name,
                            ModifiedDate = item.Modified.ToString("yyyy/MM/dd HH:mm:ss"),
                            Size = item.Size
                        });

                        break;
                }
            }
        }

        public void CreateDirectory(string Directory)
        {
            this.client.CreateDirectory(Directory);
        }

        public void DeleteDirectory(string Directory)
        {
            this.client.DeleteDirectory(Directory, true, FtpListOption.Recursive);
        }

        public void CopyFile(string srcFile, string dstFile)
        {
            using (var fileStream = System.IO.File.OpenRead(srcFile))
            using (var ftpStream = this.client.OpenWrite(dstFile.Replace("\\", "/")))
            {
                var buffer = new byte[8 * 1024];
                int count;

                while ((count = fileStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ftpStream.Write(buffer, 0, count);
                }
            }
        }

        public void WriteAllText(string Filename, string Content, Encoding enc = null)
        {
            byte[] byteArray;

            if (enc == null)
                byteArray = Encoding.ASCII.GetBytes(Content);
            else
                byteArray = enc.GetBytes(Content);

            using (MemoryStream stream = new MemoryStream(byteArray))
            using (var ftpStream = this.client.OpenWrite(Filename.Replace("\\", "/")))
            {
                var buffer = new byte[8 * 1024];
                int count;

                while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ftpStream.Write(buffer, 0, count);
                }
            }
        }

        public Boolean DirectoryExists(string Directory)
        {
            return this.client.DirectoryExists(Directory);
        }

        public void Disconnect()
        {
            this.client.Disconnect();
        }
    }

}

SFTPDirectory.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ReleaseManager.Interfaces;
using Renci.SshNet;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
using System.Text;
using System.IO;

namespace ReleaseManager.BusinessObjects
{
    public class SFTPDirectory : IDirectory
    {
        public SftpClient client;
        public RemoteInfo Info;

        public SFTPDirectory()
        {
            this.Info = new RemoteInfo();
            this.Info.DirInfo = new List<RemoteDirInfo>();
            this.Info.FileInfo = new List<RemoteFileInfo>();
            this.Info.Error = "";
        }

        public void Connect(string Host, int Port, string Username, string Password, string Fingerprint)
        {
            int sftpPort = 22;      // default sftp port

            if (Port != 0)
                sftpPort = Port;

            ConnectionInfo connInfo = new ConnectionInfo(Host, sftpPort, Username, new AuthenticationMethod[]{
                    new PasswordAuthenticationMethod(Username, Password)
                });

            this.client = new SftpClient(connInfo);
            this.client.HostKeyReceived += delegate(object sender, HostKeyEventArgs e)
            {
                if (Fingerprint.ToLower() == (e.HostKeyName + " " + e.KeyLength + " " + BitConverter.ToString(e.FingerPrint).Replace("-", ":")).ToLower())
                    e.CanTrust = true;
                else
                    e.CanTrust = false;
            };

            this.client.Connect();
        }

        public string GetWorkingDirectory(string Directory)
        {
            return Directory;
        }

        public void ListDirectory(string Directory)
        {
            List<SftpFile> files = this.client.ListDirectory(Directory).ToList();

            foreach (var file in files)
            {
                if (file.IsDirectory)
                {
                    if (file.Name != "." && file.Name != "..")
                        this.Info.DirInfo.Add(new RemoteDirInfo
                        {
                            Name = file.Name,
                            ModifiedDate = file.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss")
                        });
                }
                else
                {
                    this.Info.FileInfo.Add(new RemoteFileInfo
                    {
                        Name = file.Name,
                        ModifiedDate = file.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss"),
                        Size = file.Length
                    });
                }
            }
        }

        public void CreateDirectory(string Directory)
        {
            this.client.CreateDirectory(Directory);
        }

        public void DeleteDirectory(string Directory)
        {
            this.client.DeleteDirectory(Directory);
        }

        public void CopyFile(string srcFile, string dstFile)
        {
            using (var uplfileStream = System.IO.File.OpenRead(srcFile))
            {
                this.client.UploadFile(uplfileStream, dstFile.Replace("\\", "/"), true);
            }
        }

        public void WriteAllText(string Filename, string Content, Encoding enc = null)
        {
            byte[] byteArray;

            if (enc == null)
                byteArray = Encoding.ASCII.GetBytes(Content);
            else
                byteArray = enc.GetBytes(Content);

            using (MemoryStream stream = new MemoryStream(byteArray))
            {
                this.client.UploadFile(stream, Filename.Replace("\\", "/"), true);
            }
        }

        public Boolean DirectoryExists(string Directory)
        {
            return this.client.Exists(Directory);
        }

        public void Disconnect()
        {
            this.client.Disconnect();
        }
    }
}

LocalDirectory.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ReleaseManager.Interfaces;
using System.Net;
using System.Text;
using System.IO;


namespace ReleaseManager.BusinessObjects
{
    public class LocalDirectory : IDirectory
    {
        public RemoteInfo Info;

        public LocalDirectory()
        {
            this.Info = new RemoteInfo();
            this.Info.DirInfo = new List<RemoteDirInfo>();
            this.Info.FileInfo = new List<RemoteFileInfo>();
            this.Info.Error = "";
        }

        public void Connect(string Host, int Port, string Username, string Password, string Fingerprint)
        {
        }

        public string GetWorkingDirectory(string Directory)
        {
            return Directory;
        }

        public void ListDirectory(string Directory)
        {
            DirectoryInfo di = new DirectoryInfo(Directory);

            foreach (var item in di.EnumerateDirectories("*"))
            {
                if (item.Name != "." && item.Name != "..")
                    this.Info.DirInfo.Add(new RemoteDirInfo
                    {
                        Name = item.Name,
                        ModifiedDate = item.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss")
                    });
            }

            foreach (var item in di.EnumerateFiles("*"))
            {
                this.Info.FileInfo.Add(new RemoteFileInfo
                {
                    Name = item.Name,
                    ModifiedDate = item.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss"),
                    Size = item.Length
                });
            }
        }

        public void CreateDirectory(string Directory)
        {
            System.IO.Directory.CreateDirectory(Directory);
        }

        public void DeleteDirectory(string Directory)
        {
            System.IO.Directory.Delete(Directory, true);
        }

        public void CopyFile(string srcFile, string dstFile)
        {
            System.IO.File.Copy(srcFile, dstFile);
        }

        public void WriteAllText(string Filename, string Content, Encoding enc = null)
        {
            if (enc == null)
                System.IO.File.WriteAllText(Filename, Content);
            else
                System.IO.File.WriteAllText(Filename, Content, enc);
        }

        public Boolean DirectoryExists(string Directory)
        {
            return System.IO.Directory.Exists(Directory);
        }

        public void Disconnect()
        {
        }
    }

}

Implementation of RemoteBrowse method:

public JsonResult RemoteBrowse(string Protocol, string Host, int Port, string Username, string Password, string Directory, string Fingerprint = "")
{
    dynamic Connector = null;

    MyMember.Init(User.Identity.Name);

    if (MyMember.ID_Member > 0)
    {
        if (Protocol == "ftp")
            Connector = new FTPDirectory();
        else if (Protocol == "sftp")
            Connector = new SFTPDirectory();
        else if (Protocol == "local")
            Connector = new LocalDirectory();

        if (Connector != null)
        {
            try
            {
                Connector.Connect(Host, Port, Username, Password, Fingerprint);

                while (true)
                {
                    Boolean Mod = false;

                    if (Directory.Length >= 2)
                    {
                        if (Directory.Substring(0, 2) == "//")
                        {
                            Directory = Directory.Substring(1);
                            Mod = true;
                        }
                        else if (Directory.Substring(0, 2) == "..")
                        {
                            Directory = Directory.Substring(2);
                            Mod = true;
                        }
                    }
                    else if (Directory.Length >= 3)
                    {
                        if (Directory.Substring(0, 3) == "/..")
                        {
                            Directory = Directory.Substring(3);
                            Mod = true;
                        }
                    }

                    if (!Mod)
                        break;
                }

                if (Directory.Length > 1 && Directory != "/")
                {
                    if (Directory.Substring(0, 1) != "/")
                        Directory = "/" + Directory;

                    if (Directory.Substring(Directory.Length - 1) == "/")
                        Directory = Directory.Substring(0, Directory.Length - 1);

                    if (Directory.Substring(Directory.Length - 3) == "/..")     // go one directory up
                    {
                        Directory = Directory.Substring(0, Directory.Length - 3);
                        Directory = Directory.Substring(0, Directory.LastIndexOf('/'));
                    }
                }

                if (Directory == "")
                    Directory = "/";

                Connector.Info.CurrentDirectory = Connector.GetWorkingDirectory(Directory);

                Connector.ListDirectory(Directory);
                Connector.Disconnect();
            }
            catch (Exception ex)
            {
                Connector.Info.Error = ex.Message;

                if (ex.InnerException != null)
                    Connector.Info.Error += '\n' + ex.InnerException.Message;
            }
        }
    }

    return Json((Connector != null) ? Connector.Info : null, JsonRequestBehavior.AllowGet);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.