Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing FtpWebRequest

I'm trying to make a simple method to download a file from an FTP using FtpWebRequest with the method WebRequestMethods.Ftp.DownloadFile. The problem is that I wan't to display the progress of downloading and thus need to know the file size ahead to be able to calculate the percentage transfered. But when I call GetResponse in FtpWebRequest the ContentLength member is -1.

OK - so I get the size of the file in advance using the method WebRequestMethods.Ftp.GetFileSize. No problem. Then after getting the size I download the file.

This is where the problem in question appears...

After getting the size I try to reuse the FtpWebRequest and resets the method to WebRequestMethods.Ftp.DownloadFile. This causes an System.InvalidOperationException saying something like "Can't perform this action after sending the request." (may not be the exact formulation - translated from the one I get in Swedish).

I've found elsewhere that as long as I set the KeepAlive property to true, it doesn't matter, the connection is kept active. This is what I don't understand... The only object I've created is my FtpWebRequest object. And if I create another one, how can it know what connection to use? And what credentials?

Pseudo code:

Create FtpWebRequest
Set Method property to GetFileSize
Set KeepAlive property to true
Set Credentials property to new NetworkCredential(...)
Get FtpWebResponse from the request
Read and store ContentLength

Now I got the file size. So it's time to download the file. Setting Method now causes the exception mentioned above. So do I create a new FtpWebRequest? Or is there anyway to reset the request to be reused? (Closing the response made no difference.)

I don't understand how to move forward without re-creating the object. I could do that, but it just doesn't feel right. So i'm posting here in hope to find the correct way of doing this.

Here's the (non working) code (Inputs are sURI, sDiskName, sUser and sPwd.) :

FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(sURI);
request.Method = WebRequestMethods.Ftp.GetFileSize;
request.Credentials = new NetworkCredential(sUser, sPwd);
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;

FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
int contLen = (int)resp.ContentLength;
resp.Close();

request.Method = WebRequestMethods.Ftp.DownloadFile;

resp = (FtpWebResponse)request.GetResponse();

Stream inStr = resp.GetResponseStream();
byte[] buff = new byte[16384];

sDiskName = Environment.ExpandEnvironmentVariables(sDiskName);
FileStream file = File.Create(sDiskName);

int readBytesCount;
int readTotal=0;

while ((readBytesCount = inStr.Read(buff, 0, buff.Length)) > 0)
{
    readTotal += readBytesCount;
    toolStripProgressBar1.Value = 100*readTotal/contLen;
    Application.DoEvents();
    file.Write(buff, 0, readBytesCount);
}
file.Close();

I hope someone can explain how this is supposed to work. Thanks in advance.

like image 273
SamWhan Avatar asked Jul 03 '13 15:07

SamWhan


2 Answers

I don't think this will be answered so I'm "closing it" by telling you how I solved it.

Well, I didn't really solve it. I did however test the download by recreating the FtpWebRequest and noticed that on the FTP server it behaved as I wanted i.e. only one log on and then sequentially executing my requests.

This is how the code getting the file size and starting the download ended up:

// Start by fetching the file size
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(sURI);

request.Method = WebRequestMethods.Ftp.GetFileSize;
NetworkCredential nc = new NetworkCredential(sUser, sPwd);
request.Credentials = nc;
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;

// Get the result (size)
FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
Int64 contLen = resp.ContentLength;

// and now download the file
request = (FtpWebRequest)FtpWebRequest.Create(sURI);
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.Credentials = nc;
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;

resp = (FtpWebResponse)request.GetResponse();

So no answer on if it's possible to reset the FtpWebRequest for re-use. But at least I know there's no redundant information being transferred.

Thanks to everybody who took an interest and spent time thinking of an answer.

like image 195
SamWhan Avatar answered Nov 02 '22 12:11

SamWhan


FtpWebRequest can be used for only 1 request, like getting the file size or to download the file, but not both. You have to create 2 FtpWebRequests. Behind the scene, FtpWebRequest notices that it is the same URL and credentials and will reuse the same ftp connection without closing it, as long IsKeepAlieve is true, which is the default setting.

This is a sad example of bad design by Microsoft. Instead of letting us explicitly open and close a connection, they want to do it automatically for us and confuse everyone.

like image 5
Peter Huber Avatar answered Nov 02 '22 12:11

Peter Huber