FtpWebRequest does not have explicit support for recursive file operations (including loading). You must implement recursion yourself:
- Remote Directory List
- Iterate over records, upload files and recurse to subdirectories (list them again, etc.).
The hard part is identifying files from subdirectories. There is no way to do this in a portable way using FtpWebRequest . Unfortunately, FtpWebRequest does not support the MLSD command, which is the only portable way to get a list of directories with file attributes in the FTP protocol. See Also Checking if an object on an FTP server is a file or directory .
Your options:
- Perform an operation on the file name, which is likely to fail for the file and be successful for directories (or vice versa). That is, you can try to download the "name". If it succeeds, it is a file; if it fails, it is a directory.
- You may be lucky, and in your particular case, you can specify a file from the directory by file name (i.e. all your files have an extension, but not in subdirectories).
- You are using a long list of directories (
LIST command = ListDirectoryDetails method) and try to ListDirectoryDetails server related list. Many FTP servers use the * nix-style list, where you identify the directory with d at the very beginning of the entry. But many servers use a different format. The following example uses this approach (assuming the format is * nix)
void DownloadFtpDirectory(string url, NetworkCredential credentials, string localPath) { FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url); listRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails; listRequest.Credentials = credentials; List<string> lines = new List<string>(); using (FtpWebResponse listResponse = (FtpWebResponse)listRequest.GetResponse()) using (Stream listStream = listResponse.GetResponseStream()) using (StreamReader listReader = new StreamReader(listStream)) { while (!listReader.EndOfStream) { lines.Add(listReader.ReadLine()); } } foreach (string line in lines) { string[] tokens = line.Split(new[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries); string name = tokens[8]; string permissions = tokens[0]; string localFilePath = Path.Combine(localPath, name); string fileUrl = url + name; if (permissions[0] == 'd') { if (!Directory.Exists(localFilePath)) { Directory.CreateDirectory(localFilePath); } DownloadFtpDirectory(fileUrl + "/", credentials, localFilePath); } else { FtpWebRequest downloadRequest = (FtpWebRequest)WebRequest.Create(fileUrl); downloadRequest.Method = WebRequestMethods.Ftp.DownloadFile; downloadRequest.Credentials = credentials; using (FtpWebResponse downloadResponse = (FtpWebResponse)downloadRequest.GetResponse()) using (Stream sourceStream = downloadResponse.GetResponseStream()) using (Stream targetStream = File.Create(localFilePath)) { byte[] buffer = new byte[10240]; int read; while ((read = sourceStream.Read(buffer, 0, buffer.Length)) > 0) { targetStream.Write(buffer, 0, read); } } } } }
Use the following function:
NetworkCredential credentials = new NetworkCredential("user", "mypassword"); string url = "ftp://ftp.example.com/directory/to/download/"; DownloadFtpDirectory(url, credentials, @"C:\target\directory");
If you want to avoid problems with parsing directory list formats for a specific server, use a third-party library that supports the MLSD command and / or parsing various LIST list formats; and recursive downloads.
For example, building WinSCP.NET you can load an entire directory with a single call to Session.GetFiles :
// Setup session options SessionOptions sessionOptions = new SessionOptions { Protocol = Protocol.Ftp, HostName = "ftp.example.com", UserName = "user", Password = "mypassword", }; using (Session session = new Session()) { // Connect session.Open(sessionOptions); // Download files session.GetFiles("/directory/to/download/*", @"C:\target\directory\*").Check(); }
Internally, WinSCP uses the MLSD command, if supported by the server. If not, it uses the LIST command and supports dozens of different listing formats.
The Session.GetFiles method is recursive by default.
(I am the author of WinSCP)