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
}
}