While I'm no stranger to FTP, I hadn't needed to perform an FTP operation from a web page in a very long time so I was curious how well .NET handled FTP. I did a little searching and came up with some code snippets that worked, but they all seemed unnecessarily complex. So I set out to see if I could make things a little simpler.
Deleting a File
In the project I was working on, the only actual requirement was to upload a file. However, since I knew that would be a little more complex, I decided to start with something simpler. The simplest FTP command I could think of was deleting a file. The reason it's so simple is because aside from providing the file name and telling the server you want to delete it, there's no real data to transfer. By starting with something simple like this you can get the basics working and smooth out some of the kinks before you move on to the more complex aspects involved in transferring data back and forth across the wire.
Here's the basic code involved in deleting a file on a remote FTP server.
Protected Sub btnDeleteFile_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
myFtpWebRequest = WebRequest.Create("ftp://ftp_server_name/filename.ext")
'myFtpWebRequest.Credentials = New NetworkCredential("username", "password")
myFtpWebRequest.Method = WebRequestMethods.Ftp.DeleteFile
myFtpWebResponse = myFtpWebRequest.GetResponse()
litResponse.Text = myFtpWebResponse.StatusDescription
myFtpWebResponse.Close()
End Sub
It's actually very similar to making an HTTP request. The way .NET handles it, each request is split into two parts: the request and the response. The request is the query you send to the FTP server and the response is the answer it sends back. In this case, our request is represented by the FtpWebRequest object and simply includes the name of the file we want to delete and the fact that we want to delete it. Once we provide that information, we call the GetResponse method of our FtpWebRequest object. The GetResponse method issues the request to the remote server and returns a handle to the reply as an FtpWebResponse object. Since there's really no data to be returned, all that's left for us to do is take a look at the StatusCode or StatusDescription property in order to see if the FTP server was able to our fulfill our request or not.
I did all my testing on an FTP server which accepted anonymous connections, but I've included a comment in the listing above that shows how you would provide a username and password if your FTP server requires one.
Uploading a File
Once I had the script to delete a file working, I moved on to the next part of the task... figuring out how to actually upload a file. I naturally started with the code to delete a file that I had just gotten working. After all, I'd still need to specify the server and file name, specify the type of request, issue the request, and retrieve the response. The only difference this time is that the request needed to include the body of the file I wanted to upload.
Protected Sub btnUploadFile_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
Dim myStreamWriter As StreamWriter
myFtpWebRequest = WebRequest.Create("ftp://ftp_server_name/filename.ext")
'myFtpWebRequest.Credentials = New NetworkCredential("username", "password")
myFtpWebRequest.Method = WebRequestMethods.Ftp.UploadFile
myFtpWebRequest.UseBinary = True
myStreamWriter = New StreamWriter(myFtpWebRequest.GetRequestStream())
myStreamWriter.Write(New StreamReader(Server.MapPath("filename.ext")).ReadToEnd)
myStreamWriter.Close()
myFtpWebResponse = myFtpWebRequest.GetResponse()
litResponse.Text = myFtpWebResponse.StatusDescription
myFtpWebResponse.Close()
End Sub
Looking at the code above, you'll see much of it is the same as the previous code listing. The first real change is that the method has changed from "DeleteFile" to "UploadFile". I also specify to transfer the file in binary mode.
Note: Binary mode can be slower, but while text files will transfer fine as binary, the reverse is not true. If you try to transfer a binary file as text you'll most likely end up with a corrupt file.
The next step is to get a handle on the stream of data being sent with our request. To do that we use the GetRequestStream method of the FtpWebRequest object. The method returns a Stream object. Since our goal is to write the data from our file to the Stream, the next step is to attach a StreamWriter to the Stream to make writing to it easier. We then call the Write method of the StreamWriter to write the data from the file to the RequestStream. Once we've written the data to the stream we close the stream and call the GetResponse method of our FtpWebRequest object just like we did before.
You may have noticed I glossed over one (no so) little thing -- how do we get the contents of the file into the StreamWriter. There are quite a few different ways, but the way I prefer, and the one shown above, is to simply use a StreamReader to read the file directly off the file system and pass it to the StreamWriter. The way I think of it, it's sort of like using a faucet to water flowers. Sure you can get a bucket, fill it with water, and then carry the bucket to the flowers, but it's a lot easier to simply hook a hose to the faucet and turn on the water. In our scenario it's connecting a StreamReader's Read method directly to a StreamWriter's Write method, but you get the picture. Why store the file's contents in a temporary byte array if you don't need to?
Downloading a File
The upload part was all I really needed, but I realize that that's really only about half the story for most people looking to do FTP from a web page. So here's the listing for downloading a file as well.
Protected Sub btnDownloadFile_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myFtpWebRequest As FtpWebRequest
Dim myFtpWebResponse As FtpWebResponse
Dim myStreamWriter As StreamWriter
myFtpWebRequest = WebRequest.Create("ftp://ftp_server_name/filename.ext")
'myFtpWebRequest.Credentials = New NetworkCredential("username", "password")
myFtpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile
myFtpWebRequest.UseBinary = True
myFtpWebResponse = myFtpWebRequest.GetResponse()
myStreamWriter = New StreamWriter(Server.MapPath("filename.ext"))
myStreamWriter.Write(New StreamReader(myFtpWebResponse.GetResponseStream()).ReadToEnd)
myStreamWriter.Close()
litResponse.Text = myFtpWebResponse.StatusDescription
myFtpWebResponse.Close()
End Sub
It's pretty much exactly the reverse of uploading. We're dealing with the ResponseStream instead of the RequestStream and we're reading from a stream to a file instead of reading from a file to a stream.
The Whole Picture
Here's the full listing for the sample page I wrote for this article.
<%@ Page Language="VB" EnableViewState="False" Debug="True" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.IO" %>
Please note that this is sample code. I've left out even the most basic error handling to keep things simple and easy to follow. Here's a list of some of the most common errors you're likely to encounter and should plan to handle:
* Is the URI provided actually an FTP address (ftp://) and not HTTP (http://)?
* Is the FTP server name able to be resolved via DNS?
* Do you need to specify credentials to log in to the FTP server?
* Do you have permission to upload/download/delete a file on the FTP server?
* Do you have NTFS permission to write a downloaded file to the server's file system?
* Are you trying to upload/download/delete a file that doesn't exist?
* If you're moving large files, have you thought about the transfer time involved?
They're all relatively easily addressed, but which ones you encounter and how you handle them will depend on your specific scenario.
Conclusion
I hope this article has shown you just how easy the .NET Framework makes it to handle FTP transfers. In the past you were forced to find, purchase, and configure a third-party component to handle this type of thing. These days it's just another tool in the box.
No comments:
Post a Comment