FTPS client for xamarin based apps

Hi Community,

Today’s post is a quick recipe to enabling FTPS capabilities to our Xamarin applications. Please refer to code below to see it fully.

Angel

ObjectInfo Class

using System;


namespace WebUtils {

    /// <summary>

    /// 

    /// </summary>

    public class ObjectInfo {

            /// <summary>

            /// Gets or sets the name.

            /// </summary>

            /// <value>

            /// The name.

            /// </value>

            public string Name {

                get; set;

            }

            /// <summary>

            /// Gets or sets the size.

            /// </summary>

            /// <value>

            /// The size.

            /// </value>

            public long Size {

                get; set;

            }


            /// <summary>

            /// Gets or sets the type.

            /// </summary>

            /// <value>

            /// The type.

            /// </value>

            public ObjectType Type {

                get; set;

            }


            /// <summary>

            /// Gets or sets the created.

            /// </summary>

            /// <value>

            /// The created.

            /// </value>

            public DateTime Created {

                get; set;

            }

        }

}

ExecutionResult Class

using System;


namespace WebUtils  {

    /// <summary>

    ///  Class to be used as HRESULT

    /// </summary>

    public class ExecutionResult {

        /// <summary>

        /// Gets or sets a value indicating whether this <see cref="ExecutionResult"/> is succeeded.

        /// </summary>

        /// <value>

        ///   <c>true</c> if succeeded; otherwise, <c>false</c>.

        /// </value>

        public bool Succeeded {

            get; set;

        }


        /// <summary>

        /// Gets or sets the caught exception.

        /// </summary>

        /// <value>

        /// The caught exception.

        /// </value>

        public Exception CaughtException {

            get; set;

        }


        /// <summary>

        /// Gets or sets the tag.

        /// </summary>

        /// <value>

        /// The tag.

        /// </value>

        public object Tag {

            get; set;

        }


        /// <summary>

        /// The empty

        /// </summary>

        public static ExecutionResult Empty = new ExecutionResult() { Succeeded = false };


    }

}

IFtpsClient Interface

using System;

using System.Collections.Generic;

using System.Net;


namespace WebUtils {

    /// <summary>

    /// 

    /// </summary>

    public enum ObjectType {

        File,

        Folder

    }


    /// <summary>

    /// 

    /// </summary>

    /// <typeparam name="T"></typeparam>

    /// <typeparam name="TV">The type of the v.</typeparam>

    /// <typeparam name="TW">The type of the w.</typeparam>

    /// <param name="t">The t.</param>

    /// <param name="tv">The tv.</param>

    /// <param name="tw">The tw.</param>

    public delegate void GenericHandler<in T, in TV, in TW>(T t, TV tv, TW tw);


    /// <summary>

    /// 

    /// </summary>

    public struct FtpResult {

        /// <summary>

        /// Gets or sets the code.

        /// </summary>

        /// <value>

        /// The code.

        /// </value>

        public FtpStatusCode Code {

            get; set;

        }

        /// <summary>

        /// Gets or sets the message.

        /// </summary>

        /// <value>

        /// The message.

        /// </value>

        public string Message {

            get; set;

        }

    }


    /// <summary>

    /// 

    /// </summary>

    public interface IFtpsClient {

        /// <summary>

        /// Occurs when [on exception caught].

        /// </summary>

        event GenericHandler<object, Exception, object> OnExceptionCaught;


        /// <summary>

        /// Occurs when [on FTP request completed].

        /// </summary>

        event GenericHandler<object, FtpResult, object> OnFtpRequestCompleted;


        /// <summary>

        /// Occurs when [on upload progress].

        /// </summary>

        event GenericHandler<object, long, TimeSpan> OnUploadProgress;


        /// <summary>

        /// Downloads the specified downloaded file path.

        /// </summary>

        /// <param name="downloadedFilePath">The downloaded file path.</param>

        /// <param name="fileName">Name of the file.</param>

        /// <returns></returns>

        ExecutionResult Download(string downloadedFilePath, string fileName);


        /// <summary>

        /// Deletes the specified file name.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <returns></returns>

        ExecutionResult Delete(string fileName);


        /// <summary>

        /// Uploads the specified file name.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <param name="remoteFileName">Name of the remote file.</param>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        ExecutionResult Upload(string fileName, string remoteFileName, string remoteFolder = null);


        /// <summary>

        /// Renames the file.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <param name="remoteFileName">Name of the remote file.</param>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        ExecutionResult RenameFile(string fileName, string remoteFileName, string remoteFolder = null);


        /// <summary>

        /// Makes the directory.

        /// </summary>

        /// <param name="remoteDirName">Name of the remote dir.</param>

        /// <returns></returns>

        ExecutionResult MakeDirectory(string remoteDirName);


        /// <summary>

        /// Gets the directory listing.

        /// </summary>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        List<ObjectInfo> GetDirectoryListing(string remoteFolder = null);

    }

}

FtpsClient Class

using System;

using System.Collections.Generic;

using System.Globalization;

using System.IO;

using System.Net;

using System.Text;




namespace WebUtils {

    /// <summary>

    /// 

    /// </summary>

    public class FtpsClient : IFtpsClient {

        #region "Constants"


        /// <summary>

        /// The buffer size

        /// </summary>

        private const int BufferSize = 2048; // 2KB buffer size for chunks


        #endregion


        #region "Events"

        /// <summary>

        /// Occurs when [on exception caught].

        /// </summary>

        public event GenericHandler<object, Exception, object> OnExceptionCaught;

        /// <summary>

        /// Occurs when [on FTP request completed].

        /// </summary>

        public event GenericHandler<object, FtpResult, object> OnFtpRequestCompleted;

        /// <summary>

        /// Occurs when [on upload progress].

        /// </summary>

        public event GenericHandler<object, long, TimeSpan> OnUploadProgress;


        #endregion


        #region "Properties"


        /// <summary>

        /// Gets or sets the credentials.

        /// </summary>

        /// <value>

        /// The credentials.

        /// </value>

        protected NetworkCredential Credentials {

            get; set;

        }


        /// <summary>

        /// Gets or sets the name of the host.

        /// </summary>

        /// <value>

        /// The name of the host.

        /// </value>

        protected string HostName {

            get; set;

        }


        #endregion


        #region "Ctor"


        /// <summary>

        /// Initializes a new instance of the <see cref="FtpsClient" /> class.

        /// </summary>

        /// <param name="hostName">Name of the host.</param>

        /// <param name="credential">The credential.</param>

        public FtpsClient(string hostName, NetworkCredential credential) {

            if (string.IsNullOrEmpty(hostName) || credential == null)

                throw new ArgumentException("Unable to construct FtpsClient due to arguments mismatch.");


            HostName = hostName;

            Credentials = credential;

        }


        #endregion


        #region "Helper methods"


        /// <summary>

        /// Gets the request.

        /// </summary>

        /// <param name="requestUri">The request URI.</param>

        /// <returns></returns>

        private FtpWebRequest GetRequest(string requestUri) {

            FtpWebRequest retval = null;


            try {

                ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

                retval = (FtpWebRequest)WebRequest.Create(new Uri($"ftp://{HostName}/{ requestUri ?? string.Empty}"));

                retval.EnableSsl = retval.UseBinary = true;

                retval.Credentials = Credentials;

            } catch (Exception ex) {

                OnExceptionCaught?.Invoke(this, ex, requestUri);

            }


            return retval;

        }



        /// <summary>

        /// Parses the FTP list item.

        /// </summary>

        /// <param name="listItem">The list item.</param>

        /// <returns></returns>

        private ObjectInfo ParseFtpListItem(string listItem) {

            string[] parts;

            ObjectInfo retval = null;


            if (!string.IsNullOrEmpty(listItem) && (parts = listItem.Split(' ')).Length > 0) {

                long tryParse = 0;

                DateTime formattedDate;

                retval = new ObjectInfo { Name = parts[parts.Length - 1] };


                // Let's extract date and time

                if (parts.Length >= 2 &&

                    DateTime.TryParseExact(parts[0], "MM-dd-yyyy", CultureInfo.InvariantCulture,

                    DateTimeStyles.None, out formattedDate)) {

                    var t = formattedDate.ToShortDateString() + " " + parts[2];

                    formattedDate = DateTime.Parse(t);

                    retval.Created = formattedDate;

                }


                // Let's extract the file size (if applicable)

                if (long.TryParse(parts[parts.Length - 2], out tryParse)) {

                    retval.Size = tryParse;

                    retval.Type = ObjectType.File;

                } else

                    retval.Type = ObjectType.Folder;

            }


            return retval;

        }


        #endregion


        #region "Main Functionality"


        /// <summary>

        /// Downloads the specified downloaded file path.

        /// </summary>

        /// <param name="downloadedFilePath">The downloaded file path.</param>

        /// <param name="fileName">Name of the file.</param>

        /// <returns></returns>

        public ExecutionResult Download(string downloadedFilePath, string fileName) {

            var retval = ExecutionResult.Empty;


            if (!string.IsNullOrEmpty(downloadedFilePath) && !string.IsNullOrEmpty(fileName)) {

                try {

                    var readCount = 0;

                    var serializedCount = 0;

                    var startTime = DateTime.Now;

                    var request = GetRequest(fileName);

                    request.Method = WebRequestMethods.Ftp.DownloadFile;

                    var targetFile = Path.Combine(downloadedFilePath, fileName);


                    if (File.Exists(targetFile))

                        File.Delete(targetFile);


                    using (var fs = new FileStream(targetFile, FileMode.Create)) {

                        using (var response = (FtpWebResponse)request.GetResponse()) {

                            using (var stream = response.GetResponseStream()) {

                                var buffer = new byte[BufferSize];

                                var responseLength = response.ContentLength;

                                serializedCount = readCount = stream.Read(buffer, 0, BufferSize);


                                while (readCount > 0) {

                                    stream.Write(buffer, 0, BufferSize);

                                    OnUploadProgress?.Invoke($"Downloading {fileName}...", serializedCount, DateTime.Now - startTime);

                                    readCount = stream.Read(buffer, 0, BufferSize);

                                    serializedCount += readCount;

                                }


                                var ftpResult = new FtpResult { Code = FtpStatusCode.CommandOK, Message = "Download completed." };

                                OnFtpRequestCompleted?.Invoke(this, ftpResult, $"Download took {DateTime.Now - startTime}");

                                retval = new ExecutionResult() { Succeeded = true, Tag = ftpResult };

                                stream.Close();

                                response.Close();

                            }

                        }

                    }

                } catch (Exception ex) {

                    var msg = $"Unable to download file - {fileName}";

                    retval = new ExecutionResult() { CaughtException = ex, Succeeded = false, Tag = msg };

                    OnExceptionCaught?.Invoke(this, ex, msg);

                }

            }


            return retval;

        }


        /// <summary>

        /// Deletes the specified file name.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <returns></returns>

        public ExecutionResult Delete(string fileName) {

            var retval = ExecutionResult.Empty;


            if (!string.IsNullOrEmpty(fileName)) {

                var result = string.Empty;

                var request = GetRequest(fileName);

                request.KeepAlive = false;

                request.Method = WebRequestMethods.Ftp.DeleteFile;


                try {

                    using (var response = (FtpWebResponse)request.GetResponse()) {

                        var responseLength = response.ContentLength;

                        using (var stream = response.GetResponseStream()) {

                            using (var sr = new StreamReader(stream)) {

                                result = sr.ReadToEnd();

                                sr.Close();

                            }

                            stream.Close();

                            var ftpResult = new FtpResult { Code = response.StatusCode, Message = response.StatusDescription };

                            retval = new ExecutionResult { Succeeded = true, Tag = ftpResult };

                            OnFtpRequestCompleted?.Invoke(this, ftpResult, new {

                                Result = result, Size = responseLength

                            });

                        }

                        response.Close();

                    }

                } catch (Exception ex) {

                    var msg = $"Unable to delete file - {fileName}";

                    retval = new ExecutionResult() { CaughtException = ex, Succeeded = false, Tag = msg };

                    OnExceptionCaught?.Invoke(this, ex, msg);

                }

            }


            return retval;

        }


        /// <summary>

        /// Uploads the specified file name.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <param name="remoteFileName">Name of the remote file.</param>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        public ExecutionResult Upload(string fileName, string remoteFileName, string remoteFolder = null) {

            var retval = ExecutionResult.Empty;


            if (!string.IsNullOrEmpty(fileName) && File.Exists(fileName) && !string.IsNullOrEmpty(remoteFileName)) {

                long uploaded = 0;

                var contentLength = 0;

                var fi = new FileInfo(fileName);

                var requestUri = $"{remoteFolder}/{remoteFileName}";

                var startTime = DateTime.Now;

                var request = GetRequest(requestUri);

                request.KeepAlive = false;

                request.Method = WebRequestMethods.Ftp.UploadFile;

                request.ContentLength = fi.Length;

                var buffer = new byte[BufferSize];


                try {

                    using (var fs = fi.OpenRead()) {

                        using (var stream = request.GetRequestStream()) {

                            uploaded = contentLength = fs.Read(buffer, 0, BufferSize);

                            while (contentLength != 0) {

                                stream.Write(buffer, 0, contentLength);

                                OnUploadProgress?.Invoke($"Uploading {fileName}...", uploaded, DateTime.Now - startTime);

                                contentLength = fs.Read(buffer, 0, BufferSize);

                                uploaded += contentLength;

                            }

                            var ftpResult = new FtpResult { Code = FtpStatusCode.CommandOK, Message = "Upload completed." };

                            OnFtpRequestCompleted?.Invoke(this, ftpResult, $"Upload took {DateTime.Now - startTime}");

                            retval = new ExecutionResult() { Succeeded = true, Tag = ftpResult };

                            stream.Close();

                            fs.Close();

                        }

                    }

                } catch (Exception ex) {

                    var msg = $"Unable to upload file - {fileName}";

                    retval = new ExecutionResult() { CaughtException = ex, Succeeded = false, Tag = msg };

                    OnExceptionCaught?.Invoke(this, ex, msg);

                }

            }

            return retval;

        }


        /// <summary>

        /// Renames the file.

        /// </summary>

        /// <param name="fileName">Name of the file.</param>

        /// <param name="remoteFileName">Name of the remote file.</param>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        public ExecutionResult RenameFile(string fileName, string remoteFileName, string remoteFolder = null) {

            var retval = ExecutionResult.Empty;


            if (!string.IsNullOrEmpty(fileName) && !string.IsNullOrEmpty(remoteFileName)) {

                var requestUri = $"{remoteFolder}/{remoteFileName}";

                var request = GetRequest(requestUri);

                request.KeepAlive = false;

                request.Method = WebRequestMethods.Ftp.Rename;

                request.RenameTo = fileName;


                try {

                    using (var response = (FtpWebResponse)request.GetResponse()) {

                        var ftpResult = new FtpResult { Code = response.StatusCode, Message = response.StatusDescription };

                        retval = new ExecutionResult { Succeeded = true, Tag = ftpResult };

                        OnFtpRequestCompleted?.Invoke(this, ftpResult, retval);

                        response.Close();

                    }

                } catch (Exception ex) {

                    var msg = $"Unable to rename file - {requestUri} to {fileName}";

                    retval = new ExecutionResult() { CaughtException = ex, Succeeded = false, Tag = msg };

                    OnExceptionCaught?.Invoke(this, ex, msg);

                }

            }


            return retval;

        }


        /// <summary>

        /// Makes the directory.

        /// </summary>

        /// <param name="remoteDirName">Name of the remote dir.</param>

        /// <returns></returns>

        public ExecutionResult MakeDirectory(string remoteDirName) {

            var retval = ExecutionResult.Empty;


            if (!string.IsNullOrEmpty(remoteDirName)) {

                try {

                    var request = GetRequest(remoteDirName);

                    request.Method = WebRequestMethods.Ftp.MakeDirectory;


                    using (var response = (FtpWebResponse)request.GetResponse()) {

                        using (var stream = response.GetResponseStream()) {

                            var ftpResult = new FtpResult { Code = response.StatusCode, Message = response.StatusDescription };

                            retval = new ExecutionResult { Succeeded = true, Tag = ftpResult };

                            OnFtpRequestCompleted?.Invoke(this, ftpResult, retval);

                            stream?.Close();

                        }

                        response.Close();

                    }

                } catch (Exception ex) {

                    var msg = $"Unable to Create Directory - {remoteDirName}";

                    retval = new ExecutionResult() { CaughtException = ex, Succeeded = false, Tag = msg };

                    OnExceptionCaught?.Invoke(this, ex, msg);

                }

            }


            return retval;

        }


        /// <summary>

        /// Gets the directory list.

        /// </summary>

        /// <param name="remoteFolder">The remote folder.</param>

        /// <returns></returns>

        public List<ObjectInfo> GetDirectoryListing(string remoteFolder = null) {

            string[] items = null;

            List<ObjectInfo> retval = null;


            try {

                var buffer = new StringBuilder();

                var request = GetRequest(remoteFolder);

                request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;


                using (var response = (FtpWebResponse)request.GetResponse()) {

                    using (var reader = new StreamReader(response.GetResponseStream())) {

                        var line = reader.ReadLine();

                        while (!string.IsNullOrEmpty(line)) {

                            buffer.AppendLine(line);

                            buffer.Append("\n");

                            line = reader.ReadLine();

                        }


                        // Did we find anything?

                        if (buffer.Length > 0) {

                            retval = new List<ObjectInfo>();

                            buffer.Remove(buffer.ToString().LastIndexOf('\n'), 1);

                            items = buffer.ToString().Split('\n');


                            Array.ForEach(items, _ => {

                                var item = ParseFtpListItem(_);

                                if (item != null)

                                    retval.Add(item);

                            });

                        }


                        var itemCount = items != null ? items.Length : 0;


                        OnFtpRequestCompleted?.Invoke(this, new FtpResult { Code = response.StatusCode, Message = response.StatusDescription },

                                                      $"GetDirectoryListing returned {itemCount} items.");

                        reader.Close();

                        response.Close();

                    }

                }

            } catch (Exception ex) {

                retval = null;

                OnExceptionCaught?.Invoke(this, ex, $"Unable to get directory listing on {remoteFolder}");

            }


            return retval;

        }


        #endregion

    }

}

Leave a Reply

Your email address will not be published. Required fields are marked *