Introduction
One annoying omission from the 1.x framework for .NET was support for FTP. This could be rectified by various libraries (some free, others commercial) that filled this gap. However, with Visual Studio 2005 and 2.0 of the .NET framework, FTP makes a welcome appearance.
As well as adding FTP, Microsoft has moved support for web, mail and FTP requests out of System.Web and into System.Net which is a more logical approach.
There is still a problem however: the FTP support isn't actually an FTP client, it's just support for the protocol in FtpWebRequest, in the same way as HttpWebRequest supports web requests. There is no "download a file" or "get a directory listing" function - you're still left to sort this out yourself.
This is where I hope my library FTPclient will come in useful. It's not a full-featured and comprehensive client but it provides all the most frequently used functions and can act as a base to add any missing ones if you need them.
Background
I assume here that you've got .NET 2.0 or one of the betas. This library was written on beta 2 of VS2005, so if you have a later version or the released version some changes may be required. I'll try to update the code if any framework changes break it.
I wrote this library to support my own application which needed to upload and download files to a supplier's FTP server: this runs on Linux, but I also tested it against the Microsoft FTP server that comes with NT and XP.
FTPClient Design
FTPclient is designed to operate in a stateless mode, in a similar way to how a web request would work. It does not hold open a connection but instead will connect, perform the requested action, and disconnect for each request.
This does mean it's very suitable for single-action operations but not ideal in performance terms if you want to hold open a connection while performing multiple requests. However the library could be adapted to operate in this way if someone is willing to take the time.
FtpWebRequest Basics
Making any type of FTP requests can be broken down into six steps:
- Create a web request for a URL.
- Set the login credentials (username, password).
- Set the required options and the action to perform.
- Upload data required (not used by some actions).
- Download data or results (again, not used by some actions).
- Close the request (and connection).
Although this might seem simple enough, there are several problems that can catch you out (they did for me!). One is that FtpWebRequest can support connections using the KeepAlive property, which is set to True by default. In my class it's turned off so that each connection is closed once the command completes.
An Example: Download a file
Here is an example of the steps in action, using FtpWebRequest to download a file:
Const localFile As String = "C:\myfile.bin"
Const remoteFile As String = "/pub/myftpfile.bin"
Const host As String = "ftp://ftp.myhost.com"
Const username As String = "myuserid"
Const password As String = "mypassword"
Dim URI As String = host & remoteFile
Dim ftp As System.Net.FtpWebRequest = _
CType(FtpWebRequest.Create(URI), FtpWebRequest)
ftp.Credentials = New _
System.Net.NetworkCredential(username, password)
ftp.KeepAlive = False
ftp.UseBinary = True
ftp.Method = System.Net.WebRequestMethods.Ftp.DownloadFile
Using response As System.Net.FtpWebResponse = _
CType(ftp.GetResponse, System.Net.FtpWebResponse)
Using responseStream As IO.Stream = response.GetResponseStream
Using fs As New IO.FileStream(localFile, IO.FileMode.Create)
Dim buffer(2047) As Byte
Dim read As Integer = 0
Do
read = responseStream.Read(buffer, 0, buffer.Length)
fs.Write(buffer, 0, read)
Loop Until read = 0
responseStream.Close()
fs.Flush()
fs.Close()
End Using
responseStream.Close()
End Using
response.Close()
End Using
Note (1): I found that using Loop Until read < buffer.Size does not work because sometimes data less than the buffer size is returned by a remote server, and it was possible to have this condition true before the end of the stream is reached. I found that read = 0 seems to only occur once the stream is finished.
In this particular example, steps 1 and 2 would be repeated for any type of FTP operation in the same way, so I put them into a function that can be re-used. Step 3 is largely dependent on the operation you will perform, as is the type of upload or download, but I created a generic function GetResponseString that will read a textual response (e.g. a directory listing). This code also lacks any error handling.
Using FtpClient
To use FtpClient, create a new instance of the object, defining the host, username and password.
Dim myFtp As New FtpClient(hostname, username, password)
To get a directory listing of the FTP server's /pub directory:
Dim fullList As FtpDirectory = myFtp.GetDirectoryDetail("/pub/")
To determine which of these are files, use the GetFiles function:
Dim filesOnly As FtpDirectory = fullList.GetFiles()
To download or upload a file - a simple example:
myFtp.Download("/pub/myfile.bin", "C:\myfile.bin")
myFtp.Upload("C:\myfile.bin", "/pub/myfile.bin")
Or a more complex example, downloading all the files from a directory.
For Each file As FtpFileInfo In myFtp.GetDirectoryDetail("/pub/").GetFiles
myFtp.Download(file, "C:\" & file.Filename)
Next file
If a target file already exists for either uploads or downloads, the client will throw an exception by default to prevent unwanted overwrites. To turn off this behaviour, set the last, optional parameter PermitOverwrite to True.
Reading FTP Directories
Reading an FTP directory is simple enough: use either ListDirectory or ListDirectoryDetails request methods. ListDirectory is very simple - it returns a List(Of String) - but there is no distinction between a file or directory entry in the list so it may not be of use in most cases.
ListDirectoryDetails provides a lot more information about each file. It uses the detailed FTP listing which returns a collection of FtpFileInfo objects. An FtpFileInfo object contains the full path, name, date/time and file size of the entry as read from the detailed directory listing, in a similar way to FileInfo from System.IO.
Detailed FTP directory listings output varies according to the FTP server and the operating system it runs on. In particular, the NT/XP FTP server can be very different to UNIX and Linux results. The constructor for FtpFileInfo takes the text of the listing as a parameter and attempts to parse this with several regular expression patterns (held in _ParseFormats).
If you have errors with a particular FTP server reading detailed directories, you may need to add your own regular expressions to the _ParseFormats string array to get the library to work. I would expect the ones provided will work with most servers. Let me know if you find any new patterns that are needed.
Current Directory
I included the capability to store a current directory in the design in the same style as a standard FTP client application, although I've not used this myself. To set the directory, use FtpClient.CurrentDirectory = "/path". This comes into play if you don't specify a path for a remote file.
Dim myFtp As New FtpClient(hostname, username, password)
myFtp.CurrentDirectory = "/pub"
myFtp.Download("fileInPub.bin", "C:\test\fileInPub.bin")
myFtp.CurrentDirectory = "/pub/etc"
myFtp.Upload("C:\MyFile.bin")
Possible Improvements
As mentioned already, this client does not support using an open connection, and has to log in for each request. In my application this wasn't a big issue, and I found the support for keeping the connection alive and performing multiple requests inadequately documented, so I decided to KISS (Keep It Simple).
Another improvement could be adding support for asynchronous operations which the FtpWebRequest object supports, but again KISS prevailed in my project.
Anyway, I hope you find this a useful little library that should do the basic operations you need for FTP.
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
Hi folks, I'm a bit of a newbie when it comes to FTP, so please excuse me if the answer to my question is utterly obvious to some of you.
I threw together the following code to try out Howards marvellous FTP Client :-
Public Sub FtpSplurgeTest() Const FileName As String = "LITT09101314.csv" 'To use FtpClient, create a new instance of the object, defining the host, username and password. Dim myFtp As New Utilities.FTP.FTPclient("ftp://" & FtpHostName, FtpUserName, FtpPassword) 'To get a directory listing of the FTP server's /pub directory: Dim DirList As List(Of String) = myFtp.ListDirectory("/") Dim fullList As Utilities.FTP.FTPdirectory = myFtp.ListDirectoryDetail("/") fullList = myFtp.ListDirectoryDetail("/Test/") 'To determine which of these are files, use the GetFiles function: Dim filesOnly As Utilities.FTP.FTPdirectory = fullList.GetFiles() 'To download or upload a file - a simple example: myFtp.Download("/Test/" & FileName, ".\TestData\" & FileName) myFtp.Upload(".\TestData\" & FileName, "/Test/" & FileName & "-1") 'Or a more complex example, downloading all the files from a directory. For Each file As Utilities.FTP.FTPfileInfo In myFtp.ListDirectoryDetail("/Test/").GetFiles myFtp.Download(file, ".\TestData\" & file.Filename) Next file End Sub
But I get the error mentioned in the Thread Subject on the first line that actually tries to get Directory information. The example function from the CodeProject article works fine and downloads a file from my server to my client PC. Could this problem have anything to do with the KeepAlive ? I'm using VB Express 2008 on XP and my server is FileZilla and is running on XP Embedded.
Thanks in advance for any help that you give me.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi once again folks,
I now have a good sized portion of well scambled egg on my face
I had been printing out the parameters that I was using to log in to the FTP Server, both the set that worked from the first, long hand, example supplied by Howard against the set of data being used by my "Splurge" function and I couldn't see any difference, more fool me !!! I had accidentally substituted a '_' for a '-' in the User Name 
My apologies for wasting everybodies time, and let that be a lesson to us all, NEVER assume that anything is correct just because you've typed that string SOoooo many times before !
Please consider this thread closed due to being solved.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
Thank you for your magnanimous response Howard.
Yes we have all been there, (though we hate to admit it) But not normally quite so publically !!
Anyway, having got logged in, I managed to get all the functionality you provide working in very short order. It really is a very nice library, and very easy to use.
Thanks again, and kep up the good work.
Cheers Peter
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
 | c#  sharmila03 | 20:04 11 Oct '09 |
|
 |
I received the below error while download more than one file from ftp using ftpwebrequest. Error message: "The remote server returned an error: (500) Syntax error, command unrecognized."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I received the below error while download more than one file from ftp using ftpwebrequest. Error message: "The remote server returned an error: (500) Syntax error, command unrecognized."
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
hey there^^
hmm I'm a c# programmer and not very good in vb.net...
this is the part which you meant, right?
Dim di As FTPdirectory = Me.ListDirectoryDetail(currentPath) Dim found As Boolean = False For Each dir As FTPfileInfo In di.GetDirectories() If dir.NameOnly.Equals(pathStep, StringComparison.CurrentCulture) Then found = True currentPath &= CStr(IIf(currentPath <> "/", "/", "")) & pathStep Exit For End If If Not found Then Return False Next
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
here is the code in c#
1. public bool FtpDirectoryExists(string path) 2. { 3. if (!path.StartsWith("/")) throw new ApplicationException("Path should start with /"); 4. 5. string[] paths = path.Trim('/').Split('/'); 6. 7. string currentPath = "/"; 8. 9. foreach (string pathStep in paths) { 10. 11. if (string.IsNullOrEmpty(pathStep)) throw new ApplicationException("Invalid path specified"); 12. 13. FTPdirectory di = this.ListDirectoryDetail(currentPath); 14. bool found = false; 15. foreach (FTPfileInfo dir in di.GetDirectories()) { 16. 17. if (dir.NameOnly.Equals(pathStep, StringComparison.CurrentCulture)) { 18. 19. found = true; 20. currentPath += (string)(currentPath != "/" ? "/" : "") + pathStep; 21. break; 22. } 23. if (!found) return false; 24. } 25. } 26. return true; 27. }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
How can I move back current directory 2 level? (parent of parent path). I can get all files in child folder but parent folder i cannot move back directory
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
When i try to download the file i am getting this error , could some please send me the regex for this Unable to parse line: total 2-rw-r--r-- 1 sys 52 Sep 12 12:28 abc.txt Its urgent
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi I want download multiple files simultaneously. I do know how to do that using threading.
Pls guide me.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am not sure about threading support - the library was not designed to be thread safe. You will need to check up on FtpWebRequest class in the framework to see if that is thread-safe first
'Howard
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Hi, Actually the following downloading function in the thread. If i want to abort the thread problem occurs at responsestream.close. Because stream not in the eof. How to exit from the do loop
Using fs As FileStream = targetFI.OpenWrite Try Dim buffer(2047) As Byte Dim read As Integer = 0 Do read = Stream.Synchronized(responseStream).Read(buffer, 0, buffer.Length) fs.Write(buffer, 0, read) If blThrdAbort = True Then ret = False Exit Do End If Loop Until read = 0 responseStream.Flush() responseStream.Close() fs.Flush() fs.Close() Catch ex As Exception 'catch error and delete file only partially downloaded fs.Close() 'delete target file as it's incomplete targetFI.Delete() 'Throw ret = False End Try End Using
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
I was very excited about your library till I tried to use it in my web project. But I cannot connect to an FTP server.
Is it only to use within a windows app?
I tried to use it from VS 2008 and in my production environment. No success. Some errors say "can't find the local file", other "can'T connect to the server"
Thanks
Calle
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
There is no interface code in FtpClient so it could be used in a web project.
However, it is rare that FTP operations would take place in an ASP.NET application, due to the stateless nature of web apps in general.
I would assume that you web app is trying (during a page lifecycle) to connect, download/upload a file and disconnect. In this instance you definitely would not use "KeepAlive"
'Howard
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, I'd like to keep the connection alive because I want to do several operations. so I tried to use ur latest version in codeplex, but it shows some error like The underlying connection was closed: An unexpected error occurred on a receive.
so am decided to use this version with some modifications like set every function request keepalive = true in ftpclient class
'return a simple list of filenames in directory Dim ftp As Net.FtpWebRequest = GetRequest(GetDirectory(directory)) 'Set request to do simple list ftp.Method = Net.WebRequestMethods.Ftp.ListDirectory
ftp.Proxy = Nothing ftp.KeepAlive = True
and some modification in credentials
Private Function GetCredentials() As Net.ICredentials 'Return New Net.NetworkCredential(Username, Password) If _credentials Is Nothing Then _credentials = New Net.NetworkCredential(Username, Password) End If Return _credentials End Function Private _credentials As Net.NetworkCredential = Nothing
and create a global variable in the main form
Public ftpNew As New FTPclient
ftpNew = New FTPclient(cmbHost.Text, cmbUser.Text, txtPassword.Text)
in this ftpNew instance I'll call the client functions.
Its work fine. Is it correct? Anything wrong in the concept, let me know and I just want to know how many connection made between local machine and server when conti. request like listdirectory,upload, download, Fileexisit send to server.
guide me
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
I'm using ur FtpClient version with some modification. I think modified code of mine and ur ftpclient2 are partially same and now i want know how to disconnect the ftp connection with out close the application.
and also I want know the connections made between server and local. Pls guide me.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The is no way to explicitly close an FTP connection with FtpWebRequest from the .NET classes when using KeepAlive=true
'Howard
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
With the FTPClient my application download automaticly every day 3 files from a bank. every thursday morning the ftp client can not download the first file of these 3 files. i belive, that the bank boots his server every tuesday morning and therefore it can not download it. when i try to download the file that is not downloaded manually, it works. every other day it works fine.
example (thursday)
file1: 07:55 (not downloaded) file2: 08:00 (downloaded) file3: 08:05 (downloaded)
i get always the ftp error message 421.
what can i do, that it download the first file on thursday?
kind regards and thank you
modified on Thursday, September 3, 2009 7:58 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Look, no offence, but did you even try search google with this text?: ftp error message 421[^]. If you did, I'm curious if you have google settings to only give results in your language.
The first result gave me a link to http://help.globalscape.com/help/support/Error_Codes/FTP_Codes.htm[^], which provides a very nice document explaining probable causes for this error code.
Admins sometimes protect their servers by:
- Limiting total TCP connections to the server, use TCPView.exe from Sysinternals[^]. - Limiting how many times 1 user can be logged on at the same time. - You can only download files during the morning/day/night to reduce overall network load.
Hope this helps.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|