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

C# FTP Client Library

0.00/5 (No votes)
6 Jul 2012 1  
Easy to use FTP client library with features in mind.

Introduction

System.Net.FtpClient is a client implementation of the FTP protocol written entirely in C#. It supports SSL/TLS encryption for the control and data connections. It is comprised of many classes however most people will only be interested in a select few. The starting place is FtpClient which is a high level implementation RFC959 and various extensions to the protocol. The library supports many more features than described in this article however the information here should be enough to get most people started using the library in their own projects.

Getting Started

The FtpClient class is pretty simple and straight forward. The class is setup to try to use explicit (TLS) encryption by default. This means that upon connection, FtpClient will execute the AUTH command before sending any credentials. This behavior can be controlled through the SslMode property which is an FtpSslMode enumeration. Using SSL means that your code needs to be prepared to handle certain scenarios such as an invalid SSL certificate or in the case of TLS your code needs to be prepared to handle cases where the server does not support or possibly allow the AUTH command. The example below shows basic usage of the FtpClient class with the above considerations taken into account:

using System.Net.FtpClient;

...

static void Main(string[] args) {
	using (FtpClient cl = new FtpClient("user", "pass", "ftp.server.tld")) {
		/////
		// Logging the transaction
		/////
		// log the ftp transactions to stdout
		cl.FtpLogStream = Console.OpenStandardOutput();
		// flush the output buffer so that the transaction
		// stays in sync with out output going to screen
		cl.FtpLogFlushOnWrite = true;
		
		// Default, use the EPSV command for data channels. Other
		// options are Passive (PASV), ExtenedActive (EPRT) and
		// Active (PORT). EPSV should work for most people against
		// a properly configured server and firewall.
		cl.DataChannelType = FtpDataChannelType.ExtendedPassive;

		//////
		// SSL/TLS configuration
		// If you're going to use encryption you should handle the 2 events
		// below according to your needs. It's not uncommon for a server to
		// have a self signed certificate or name mismatch so at the very least
		// you should handle the InvalidCertificate event.
		//////
		// default, use AUTH command to setup encryption
		// FtpSslMode.Implicit - Connecting to port where SSL is implied, FTPS, 993.
		// FtpSslMode.None - Don't use encryption at all
		// The implicit option DOES NOT automatically change the port number from 21 to 993.
		cl.SslMode = FtpSslMode.Explicit;
		// If you do not handle this event and the AUTH command fails the
		// login credentials will be sent in plain text!!!! See the event args
		// for this event handler.
		cl.SecurityNotAvailable += new SecurityNotAvailable(OnSecurityNotAvailable);
		cl.InvalidCertificate += new FtpInvalidCertificate(OnInvalidCertficate);
	}
}

static void OnSecurityNotAvailable(FtpSecurityNotAvailable e) {
	// SSL/TLS could not be negotiated with the AUTH command.
	// If you do not want login credentials to be sent in plain
	// text set the e.Cancel property true to cancel the login.
	// Doing so will trigger a FtpCommandException to be thrown
	// for the failed AUTH command.
	e.Cancel = false;
}

static void OnInvalidCertficate(FtpChannel c, InvalidCertificateInfo e) {
	// we don't care if a certificate is invalid
	e.Ignore = true;
}

Downloading and Uploading Files

There are two methods for downloading and uploading files. The first is to attach to the TransferProgress event of FtpClient and use the Download and Upload methods or to open stream which you can read or write accordingly. The Download and Upload methods handle everything for you. The TransferProgress event is fired everytime data is read from the underlying data stream. The EventArgs contain progress information (if available) and has a property that indicates if the transfer is an upload or download. You only need one event handler for downloads and uploads. Here's an example expanding on the above FtpClient example:

try {
	cl.TransferProgress += new FtpTransferProgress(OnTransferProgress);
	cl.Download("/path/to/file.ext");
}
catch (Exception ex) {
	Console.WriteLine();
    Console.WriteLine(ex.ToString());
}

....

static void OnTransferProgress(FtpTransferInfo e) {
	// e.TransferType == FtpTransferType.Download
	Console.Write("\r{0}/{1} {2}% {3}/s       ",
		FormatBytes(e.Transferred), FormatBytes(e.Length), e.Percentage, FormatBytes(e.BytesPerSecond));

	if (e.Complete) {
		Console.WriteLine();
	}
}

static string FormatBytes(long size) {
	string[] units = new string[] { "B", "KB", "MB", "GB" };
	double val = size;
	int count = 0;

	while (val > 1024 && count < units.Length) {
		val /= 1024;
		count++;
	}

	return string.Format("{0}{1}", Math.Round(val, 2), units[count]);
}

The Download and Upload methods are overloaded. You can transfer to or from local files or streams. If you want more control over the download process you can open a FtpDataStream object. The streams are not bi-directional. The FTP protocol does not support reading and writing to the same stream mode data connection. FtpDataStream objects are designed to be used and thrown away. Here's an example expanding on the FtpClient code from the first listing:

// cl = new FtpClient().....
using (FtpDataStream istream = cl.OpenRead("/path/to/file")) {
	byte[] buf = new byte[istream.ReceiveBufferSize];
	int read = 0;
	
	// istream.Length is the size of the file being downloaded
	// IF the server supports file sizes, 0 otherwise.

	while ((read = istream.Read(buf, 0, buf.Length)) > 0) {
		// write the bytes read somewhere
	}
}

Uploading files is nearly an identical process except you will use FtpClient.Upload() or FtpClient.OpenWrite() accordingly.

Listing Files

Listing files can be done in two ways. The bare minimal way is to use FtpClient.GetListing() which returns an array of FtpListItem objects. These objects contain the file name (not path), file size and the last write time however there are some exceptions where not all of the information will be available or, in regards to modification times, necessarily accurate. System.Net.FtpClient attempts to parse LIST and MLSD formats. MLSD formats should always be 100% accurate unless there is a bug in System.Net.FtpClient itself. With that said, it should be preferred that your client is connecting to a modern ftp server that supports the MLST/MLSD extensions. LIST formats come in many flavors, most commonly UNIX long listing style and DOS long listing styles (IIS). The LIST formats are very difficult to parse and do not always provide the most accurate date/time representations. In addtion, there is no written specification on how LIST results should be formatted so it's possible some servers may give results that are not even handled by System.Net.FtpClient. With that said, the preferred way to work with files and directories is to use the FtpFile and FtpDirectory classes. These classes are very similar to System.IO.FileInfo and System.IO.DirectoryInfo. They attempt to do all of the dirty work for loading the correct modification times and file sizes when System.Net.FtpClient is dealing with a server that does not support the MLST/MLSD extensions. Here's an example of listing files and directories:

// cl = new FtpClient().....
foreach(FtpFile o in cl.CurrentDirectory.Files) {
	// do something with the file
}

foreach(FtpDirectory o in cl.CurrentDirectory.Directories) {
	// do something with the directory
}

// or the more minimalist approach:
foreach(FtpListItem o in cl.GetListing("/path")) {
	// if the server used LIST to get a file listing
	// the modify date probably isn't accurate so lets
	// pull an accurate one with MDTM. most servers
	// that I have encountered do not support MDTM on
	// directories but that may not always be true.
	if (!cl.HasCapability(FtpCapability.MLSD) && cl.HasCapability(FtpCapability.MDTM)) {
		DateTime modify = cl.GetLastWriteTime(string.Format("/path/{0}", item.Name));

		if (modify != DateTime.MinValue) {
			item.Modify = modify;
		}
	}
	
	if(o.Type == FtpObjectType.File) {
	
	}
	else if(o.Type == FtpObjectType.Directory) {
	
	}
}

If you are in control of the server software you are advised to use a modern implementation that supports the MLST/MLSD extensions if you want the best possible results and reliability.

Custom Parsers

As mentioned before, LIST formats can and do vary. If you run across a server that is giving file listings in formats not handled by System.Net.FtpClient you can add your own parsers to the static FtpListFormatParser.Parsers collection. In order to make use of this feature you must be handy with Regular Expressions. Because this functionality extends well beyond basic usage you won't find more than an acknowledgement that it exists in this article. If you would like to make use of this feature please refer to the Documentation on the CodePlex site. If you need further help feel free to contact the developers.

Bulk Transfers

File listings and transfers require the use of a secondary connection called the data connection. This connection is opened and thrown away when it's finished. If a new connection is needed a new socket is created. This is the basis of stream mode data transfers. Closing the stream signifies EOF between the server and client. This matters because a used socket goes into a linger state for a period of time defined by the operating system. If you are transferring a large number of small files on a fast connection it's possible that you can exhaust the available client sockets the OS has available. Please take this into consideration when recursively transferring and listing files. On windows, the number of sockets and the length of the linger state can be modified via the registry however any changes you make are at your own risk. Reducing the linger state time can cause upredictable problems with all network enabled applications. 

Thread Safety

Let me just start by saying the FTP protocol is not thread safe. You cannot execute multiple commands on the same command channel simultaneously. You can't even execute a command on the command channel while you are reading from a data channel. There are some exceptions to that rule however they are just that, exceptions. Some severs will indeed allow you to execute commands on the command channel while you're, for instance, downloading a large file but these servers are the exception and not the rule. If you need to access a single instance of a FtpClient object from multiple threads you should block access until a given operation (transaction) is completed, that includes a file download. An example of safe usage of FtpClient from 2 or more threads:

static readonly object m_FtpLock = new object();

static void Thread1() {
     lock(m_FtpLock) {
        DateTime modify = m_ftpClient.GetLastWriteTime(file1);
     }
}

static void Thread2() {
     lock(m_FtpLock) {
        DateTime modify = m_ftpClient.GetLastWriteTime(file2);
     }
}

If you try to open multiple data streams on a single command channel there are 2 likely outcomes. The server will close any existing data stream and let you proceed or it will give you a permanent failure reply. So as a general rule of thumb, if you want to transfer files or get file listings simultaneously you need exactly 1 instance of the FtpClient class per data connection. Two downloads and a file listing running in parallel would require 3 instances of FtpClient.

Final Thoughts

System.Net.FtpClient is an actively changing project. Not all features are described in this article and the behavior may change in the future. The CodePlex site includes a CHM API Reference as well as several example projects that are kept up to date with the current state of affairs. If you run across bugs or have feature requests I would greatly appreciate that you use the bug tracker on CodePlex. With that said I hope you find this code easy to use and incorporate into your own projects!

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