Search code examples
c#asp.netdownloadxlsxftpwebrequest

How to download ".xlsx" file using FTPWebRequest through C# ASP.NET


I've tried everything to get an xlsx file to download through an FTPWebRequest -- to no avail. I've tried converting the file type from xlsx to xls and even to csv, but it just gives me a file full of indecipherable symbols. All file types download through Chrome as expected with no exceptions with the code provided, except for xlsx files.

When I try to download an xlsx file using this code, I get the following error from excel: "We found a problem with some content in 'filename.xlsx'. Do you want us to recover as much as we can? If you trust the source of this workbook, click 'Yes'." Upon clicking on 'Yes', the message "Microsoft Excel was attempting to open and repair the file. To start this process again, choose Open and Repair from the Open file dialog." Proceeding with those instructions just creates an endless loop of the same messages.

Any help on this would be great! Also, I've tried changing the content-type to "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" and "application/vnd.ms-excel", and neither works.

string ftpfilename = "ftp://ftpserverinfo/randomfilename.xlsx";                                
string filename = "randomfilename.xlsx";

FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpfilename));
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.Credentials = new NetworkCredential(username, password);
request.UseBinary = true;
request.KeepAlive = true;
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
byte[] bytes = new byte[2048];
int i = 0;
MemoryStream mStream = new MemoryStream();
do { i = stream.Read(bytes, 0, bytes.Length); mStream.Write(bytes, 0, i); } while (i != 0);
context.Response.Clear();
context.Response.ClearHeaders();
context.Response.ClearContent();
context.Response.ContentType = MimeMapping.GetMimeMapping(filename);
context.Response.AddHeader("content-disposition", @"attachment;filename=" + filename);
context.Response.BinaryWrite(mStream.GetBuffer());

Solution

  • You're not actually telling FtpWebRequest which file to download, ftpfilename only contains the FTP server URL. Try modifying this line:

    FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpfilename));
    

    To:

    var requestUri = new Uri(ftpfilename + filename);
    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(requestUri);
    

    I would suggest extracting your FtpWebRequest code out into a separate method with a signature like public byte[] FtpDownloadBinary(Uri fromUri) which you can implement and test separately, then you can pass the result of that method into context.Response.BinaryWrite(fileBytes);

    == edit ==

    Here is an example method that FTP downloads a given binary URL:

    string username = "anonymous";
    string password = "anonymous@example.org";
    
    public byte[] FtpDownloadBinary(Uri fromUri)
    {
        FtpWebRequest request = (FtpWebRequest)WebRequest.Create(fromUri);
        request.Method = WebRequestMethods.Ftp.DownloadFile;
        request.Credentials = new NetworkCredential(username, password);
        request.UseBinary = true;
        request.KeepAlive = true;
    
        FtpWebResponse response = (FtpWebResponse)request.GetResponse();
        using (var responseStream = response.GetResponseStream())
        using (var memoryStream = new MemoryStream())
        {
            responseStream.CopyTo(memoryStream);
            return memoryStream.ToArray();
        }
    }
    

    You can test that this works by saving the result to a local file on your system with something like the code below, the resultant file can be opened with 7-Zip, WinRAR, WinZip, etc.:

    string ftpfilename = "ftp://ftp.ubuntu.com/ubuntu/ls-lR.gz"; //1.7MB example
    var requestUri = new Uri(ftpfilename);
    var fileBytes = FtpDownloadBinary(requestUri);
    
    using (var fileStream = new FileStream("ls-lR.gz", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
    {
        fileStream.Write(fileBytes, 0, fileBytes.Length);
    }