All experienced Web developers have some familiarity with HyperText Transfer Protocol (HTTP) after all, it is the foundation of the World Wide Web that keeps us employed. However, extensive knowledge of its partner protocol is not quite as common these days. Many Web developers know relatively little about File Transfer Protocol (FTP) other than the fact that many of us commonly use it to manually deploy our code files to distant Web servers.
These days, Web services tend to be favored by software developers (rather than FTP) for sharing data between distributed systems. However, such architectures generally require relatively modern software tools, as well as cooperation between the companies, developers, and systems on both ends. This is a luxury that s not always available. Many legacy systems still support only FTP for data transfers.
Despite the common bias toward newer data transfer techniques, FTP can still be a valuable part of modern software systems. It can serve as an efficient and reliable way to transfer data (especially large files) between distributed systems. FTP should certainly be considered when data transfer requirements are as simple as the need to transfer one or more files between systems.
Version 1.x of the .NET Framework contained no direct programmatic support for FTP. In version 2.0, the System.Net namespace was enhanced with new FTP classes that were based on the preexisting WebRequest and WebResponse classes. Let s explore these FTPWebRequest and FTPWebResponse classes by exploring some ASP.NET code samples that employ them to transfer files between servers.
Delete a File
One of the simplest FTP tasks to achieve is file deletion. The code in Figure 1 logs into a remote FTP server and deletes the file named test.txt.
'Imports System.Net Dim ftpWebReq As FtpWebRequest Dim ftpWebResp As FtpWebResponse Dim ftpRoot As String = "ftp://contoso.com/" Dim status As String = "" 'Configure the request ftpWebReq = FtpWebRequest.Create(ftpRoot + "test.txt") ftpWebReq.Method = WebRequestMethods.Ftp.DeleteFile ftpWebReq.Credentials = _ New NetworkCredential(_userName, _password) Try 'Send the request ftpWebResp = ftpWebReq.GetResponse() 'deletes the file status = ftpWebResp.StatusDescription ftpWebResp.Close() Catch ex As Exception status = ex.Message End Try lblResult.Text = status
Figure 1: This code uses the System.Net namespace to delete a remote file using File Transfer Protocol (FTP).
The first code block declares the variables that will be needed, including one that specifies the address of the remote FTP server (in this case, ftp://contoso.com/).
The second code block starts by creating a reference to a specific file (named test.txt) located in the root of that FTP server. The FTPWebRequest s unfortunately named Method property is then used to specify the intention to delete that file. The final line of the second code block provides log-in credentials to the FTP server. This line would be unnecessary if the specified FTP server were configured to allow anonymous users.
The first line of code inside the Try..Catch block is the one that actually sends the delete request to the server. It also retrieves the FTP server s response, then closes the response. The final line of code in Figure 1 displays the status text retrieved from the response object (or from the error handler, if things went awry).
Assuming the FTP server successfully found and deleted the specified file, a response code of 250 (Command Successful) will be returned, as shown in Figure 2. An error code of 550 (File Unavailable) is returned if the specified file could not be found on the FTP server. An error code of 530 (Not Logged In) is returned if the log-in credentials were rejected by the FTP server.
Figure 2: This sample application demonstrates a variety of FTP commands and displays (in red) the result of each such request.
Upload a File
The code required to upload a file is similar to the file deletion code in Figure 1, requiring only a couple extra lines of code. Figure 3 lists code that uploads a file from a local Web server to a remote FTP server.
Dim sw As StreamWriter 'Imports System.IO Dim status As String = "" 'Configure the request _ftpWebReq = FtpWebRequest.Create(_ftpRoot + "test.txt") _ftpWebReq.Method = WebRequestMethods.Ftp.UploadFile _ftpWebReq.Credentials = _ New NetworkCredential(_userName, _password) _ftpWebReq.UseBinary = False Try 'Read the local file in preparation for upload sw = New StreamWriter(_ftpWebReq .GetRequestStream()) sw.Write(New StreamReader(Server.MapPath("test.txt")) _ .ReadToEnd) sw.Close() 'Send the request _ftpWebResp = _ftpWebReq .GetResponse() 'upload the file status = _ftpWebResp.StatusDescription _ftpWebResp.Close() Catch ex As Exception status = ex.Message End Try lblResult.Text = status
Figure 3: This code demonstrates how to programmatically upload a file to an FTP server.
Figure 3 begins by declaring a couple of variables, including a StreamWriter object from the System.IO namespace. The request is then configured in much the same way as Figure 1 s file deletion code. In this case, the Create method is used to specify the file name that should be assigned to the soon-to-be uploaded file (test.txt). This FTP filename doesn t necessarily have to be the same as the source file s name. Another notable difference from the previous code sample is the line of code that sets the method property to an upload request instead of a file deletion request.
You might also have noticed a new line of code that sets the FTPWebRequest s UseBinary property to False. While not strictly necessary, this optimization allows our simple text file to be transferred more efficiently by avoiding the overhead required to transfer more complex binary files. Most other (non-text) file types require a binary transfer, so in such cases the UseBinary property should be left at its default value of True.
The Try..Catch block of Figure 3 begins by opening the test.txt file from the local Web root and loads its entire contents into a StreamWriter object. (While the .NET Framework provides multiple techniques for opening and reading files, this technique is adequate for a small text file such as the one used in this example. Alternate techniques might be more optimal when dealing with large files.)
Just as in the previous file deletion example, the request is then sent to the FTP server and the response is retrieved and closed. The result is then displayed in a Label control on the page. A successful upload will return code 226 (Transfer Complete).
Download a File
The process for downloading a file is similar to the process for uploading a file. The code in Figure 4 can be used to request a file download from an FTP server.
Dim sw As StreamWriter 'Imports System.IO Dim status As String = "" 'Configure the request _ftpWebReq = WebRequest.Create(_ftpRoot + "test.txt") _ftpWebReq.Method = WebRequestMethods.Ftp.DownloadFile _ftpWebReq.Credentials = _ New NetworkCredential(_userName, _password) _ftpWebReq.UseBinary = False Try 'Send the request _ftpWebResp = _ftpWebReq .GetResponse() 'Save the downloaded file to the local web root. sw = New StreamWriter(Server.MapPath("test2.txt")) sw.Write(New StreamReader( _ _ftpWebResp.GetResponseStream()).ReadToEnd) sw.Close() status = _ftpWebResp.StatusDescription _ftpWebResp.Close() Catch ex As Exception status = ex.Message End Try lblResult.Text = status
Figure 4: This code requests a file download from an FTP server.
The download request is configured similarly to the previously listed upload request. The FTP server s source file name test.txt is specified, and the method property is set to DownloadFile. The request is then sent to the FTP server, and the contents of the response stream are saved into a new file named test2.txt, which is placed in the local Web root folder.
List Directory Contents
Transferring files to a server can become problematic if you are oblivious to the list of files that are already there. To ease such pains, you may want to harvest the current file list from the FTP server using the code shown in Figure 5.
Dim list As String = "" Dim status As String = "" 'Configure the request _ftpWebReq = FtpWebRequest.Create(_ftpRoot) _ftpWebReq.Method = WebRequestMethods.Ftp.ListDirectory _ftpWebReq.Credentials = _ New NetworkCredential(_userName, _password) Try 'Send the request _ftpWebResp = _ftpWebReq.GetResponse() list = New StreamReader(_ftpWebResp.GetResponseStream()) _ .ReadToEnd _ftpWebResp.Close() Catch ex As Exception status = ex.Message End Try 'Display the file list lblResult.Text = Server.HtmlEncode(list) _ .Replace(Chr(13), "
Figure 5: This code will retrieve and display a simple list of files found in the specified directory of the FTP server.
The code listed in Figure 5 begins in a familiar way, declaring variables and setting up the FTP request. This time, however, the ListDirectory method is specified in order to retrieve the contents of the directory specified in the Create method.
The request is then sent to the FTP server and its potentially large response is streamed into a local string variable named list. This text is then massaged a bit for improved readability. Carriage returns are replaced with HTML line breaks, and the rest of the output is HTML encoded to ensure the browser doesn t attempt to interpret the text as HTML.
The output (shown in Figure 6) is an alphabetized list of files and subdirectories. While this list may be adequate for simple needs, an unfortunate drawback of this technique is the inability to distinguish files from subdirectories. For situations where you need this or other details about the directory contents, an alternate request must be employed.
Figure 6: The code in Figure 5 outputs this simple alphabetical list of all the files and subdirectories retrieved from the FTP server.
List Directory Details
Thankfully, retrieving more advanced directory information from the FTP server doesn t require more advanced programming techniques. In fact, only one line from Figure 5 need be modified. The method property value needs to be changed from ListDirectory to ListDirectoryDetails, as shown in Figure 7.
Dim list As String = "" Dim status As String = "" 'Configure the request _ftpWebReq = FtpWebRequest.Create(_ftpRoot) _ftpWebReq.Method = _ WebRequestMethods.Ftp.ListDirectoryDetails _ftpWebReq.Credentials = _ New NetworkCredential(_userName, _password) Try 'Execute the request _ftpWebResp = _ftpWebReq.GetResponse() list = New StreamReader(_ftpWebResp.GetResponseStream()) _ .ReadToEnd _ftpWebResp.Close() Catch ex As Exception status = ex.Message End Try 'Display the file list lblResult.Text = Server.HtmlEncode(list) _ .Replace(Chr(13), "
Figure 7: This code will retrieve and display a more detailed list of files and subdirectories than the code in Figure 5.
Now the resulting output will include file dates and times, and files can be distinguished from subdirectories by the
Figure 8: The code listed in Figure 7 renders this more detailed directory information.
Other Useful FTP Commands
In addition to the examples listed so far, File Transfer Protocol also supports a number of other potentially useful file management functions. For example, a new FTP directory can be programmatically created with code that looks very similar to the samples you ve seen so far. The primary difference will be the value assigned to the Method property of the FTPWebRequest object. For example, to create a subdirectory on the FTP server, this line of code is the key:
_ftpWebReq.Method = WebRequestMethods.Ftp.MakeDirectory
Deleting a subdirectory is just as simple:
_ftpWebReq.Method = WebRequestMethods.Ftp.RemoveDirectory
The WebRequestMethods.Ftp type demonstrated above has several other handy members, too. They include: AppendFile, GetFileSize, and Rename.
Some developers shun FTP for being insecure based primarily on the fact that all data and credentials are sent in plain text by default (which could be intercepted by skilled hackers). However, this situation isn t much different than HTTP and the solution is the same: Secure Sockets Layer. SSL can be used to secure FTP communications in the same way it is used to secure HTTP requests. To encrypt an FTP request with SSL, set the FTPWebRequest s EnableSSL property to True.
The FTPWebRequest class also has several other security-related properties to help you meet your system s security requirements. For example, there are properties for working with certificates, adjusting authentication, and using impersonation.
When accessing a remote FTP server from ASP.NET code (as has been done in the code samples here), NTFS security settings may need to be adjusted on your local server(s) so that files downloaded from the FTP site can be successfully written to the desired location(s).
IIS 7.0 includes significantly improved FTP support over previous versions. Multiple FTP sites can be established on each server, and FTP directories can be independently configured to permit or deny various actions for individual users or groups of users. Standard NTFS and IIS security settings can be used to make such adjustments, so there really isn t much of a learning curve required.
Large File Transfers
Very large files can take a long time to transfer. Systems that need to support large file transfers may face unique design challenges. When large file transfers are initiated from ASP.NET code, it can cause a lengthy delay for the user and potentially violate one or more timeout periods. ASP.NET provides many standard ways to configure its timeout periods, and for simple situations this may suffice. The FTPWebRequest class also has a ReadWriteTimeout property that is set to 5 minutes by default (300,000 milliseconds). Even if no timeout occurs, it is likely that valuable Web request threads are being tied up unnecessarily during lengthy transfers, thus limiting scalability of the Web server.
More robust and scalable solutions rely on asynchronous communications. ASP.NET provides techniques to help with asynchronous requests, such as the Async page directive. Depending on your needs, that may be sufficient. Cutting-edge technologies such as AJAX may also be tempting solutions. Additionally, the .NET Framework has long provided useful patterns for building asynchronous solutions, such as the BeginInvoke and EndInvoke methods. If you ve done this kind of development before, you ll find the FTPWebRequest s built-in asynchronous methods to be quite intuitive. Specifically, these FTPWebRequest members are named BeginGetResponse, EndGetResponse, BeginGetRequestStream, and EndGetRequestStream.
File Transfer Protocol is useful for many more things than simply deploying your ASPX files to your Web server. It can be a valuable asset to your programming toolkit. Now that you know how to program with FTP, keep it in mind as a possible component in your future software projects!
C# and VB.NET source code accompanying this article is available for download.
Steve C. Orr is an ASPInsider, MCSD, Certified ScrumMaster, Microsoft MVP in ASP.NET, and author of Beginning ASP.NET 2.0 AJAX by Wrox. He s been developing software solutions for leading companies in the Seattle area for more than a decade. When he s not busy designing software systems or writing about them, he often can be found loitering at local user groups and habitually lurking in the ASP.NET newsgroup. Find out more about him at http://SteveOrr.net or e-mail him at mailto:[email protected].