2
votes

The FTP protocol is designed to support a control channel, and use that control channel to tell the server to open TCP connections and transfer files.

The server sending or receiving files does NOT have to be the same as the server that the FTP control channel is connected to. It can be a "triangle" type connection.

It also allows the client to log in once on the control channel, and repeatedly tell the server to transfer files, without re-logging into the control channel.

Apparently, this concept has completely escaped MS when they created the C# FtpWebRequest class.

I need to do exactly what the FTP protocol was designed to do:

  1. Connect to a server

  2. Pass in credentials

  3. Create directories (and happily ignore an 'already exists' error)

  4. Repeatedly transfer files up to the server

  5. Log out of the control channel

I sure am not seeing that ability in the FtpWebRequest class. Or anything that would appear to allow that kind of a flow in C# code.

I have looked:

But none of this seems to allow control that way it was meant to be.

I can specify the KeepAlive property, but the loop has to repeatedly call the WebRequest.Create(targetName); function, which would create a NEW connection, and get a NEW response. Then they fall out of scope or are orphaned, so by definition, they are destroyed. Therefore the connection MUST be closed, and then will have to be reopened. For a data connection, that's okay, but where is the ability to manipulate the CONTROL port?

The class doesn't allow the user to differentiate from a CONTROL port and a DATA port, as the FTP specification defines.

Is there a ways to use a C# class to do FTP the way it was meant to be? Because in Microsoft's narrow mindset, the whole world looks like an HTTP Get/Response protocol.

Any advice is appreciated.

-Scotty

2
Have you tried using WinSCP? it's a more fully featured implementation of FTP than the native out of the box objects.DiskJunky

2 Answers

5
votes

FtpWebRequest works on top of a connection pool. So it actually implicitly reuses an underlying FTP connection, as long as the FtpWebRequest.KeepAlive is set to its default value of true.

When the KeepAlive is set to true, the underlying FTP (control) connection is not closed, when a request finishes. When you create another instance of the FtpWebRequest with the same host, port and username, the connection from previous request(s) is reused.


Take this simple example of two file upload requests to the same FTP server:

WebRequest request1 = WebRequest.Create("ftp://ftp.example.com/file1.zip");
request1.Credentials = new NetworkCredential("username", "password");
request1.Method = WebRequestMethods.Ftp.UploadFile;

using (Stream fileStream = File.OpenRead(@"C:\path\file1.zip"))
using (Stream ftpStream = request1.GetRequestStream())
{
    fileStream.CopyTo(ftpStream);
}

WebRequest request2 = WebRequest.Create("ftp://ftp.example.com/file2.zip");
request2.Credentials = new NetworkCredential("username", "password");
request2.Method = WebRequestMethods.Ftp.UploadFile;

using (Stream fileStream = File.OpenRead(@"C:\path\file2.zip"))
using (Stream ftpStream = request2.GetRequestStream())
{
    fileStream.CopyTo(ftpStream);
}

If you enable .NET network logging, you will see that only one FTP control connection is opened to the server:

FtpWebRequest#45004109::.ctor(ftp://ftp.example.com/file1.zip)
FtpWebRequest#45004109::GetRequestStream(Method=STOR.)
Current OS installation type is 'Client'.
RAS supported: True
FtpControlStream#21454193 - Created connection from 127.0.0.1:60360 to 93.184.216.34:2121.
Associating FtpWebRequest#45004109 with FtpControlStream#21454193
FtpControlStream#21454193 - Received response [220 ...]
FtpControlStream#21454193 - Sending command [USER username]
FtpControlStream#21454193 - Received response [331 Password required for username]
FtpControlStream#21454193 - Sending command [PASS ********]
FtpControlStream#21454193 - Received response [230 Logged on]
FtpControlStream#21454193 - Sending command [OPTS utf8 on]
FtpControlStream#21454193 - Received response [202 UTF8 mode is always enabled. No need to send this command.]
FtpControlStream#21454193 - Sending command [PWD]
FtpControlStream#21454193 - Received response [257 "/" is current directory.]
FtpControlStream#21454193 - Sending command [TYPE I]
FtpControlStream#21454193 - Received response [200 Type set to I]
FtpControlStream#21454193 - Sending command [PASV]
FtpControlStream#21454193 - Received response [227 Entering Passive Mode (93,184,216,34,247,106)]
FtpControlStream#21454193 - Sending command [STOR file1.zip]
FtpControlStream#21454193 - Received response [150 Opening data channel for file upload to server of "/file1.zip"]
FtpControlStream#21454193 - Received response [226 Successfully transferred "/file1.zip"]
FtpWebRequest#45004109::(Releasing FTP connection#21454193.)
FtpWebRequest#58870012::.ctor(ftp://ftp.example.com/file2.zip)
FtpWebRequest#58870012::GetRequestStream(Method=STOR.)
Associating FtpWebRequest#58870012 with FtpControlStream#21454193
FtpControlStream#21454193 - Sending command [PASV]
FtpControlStream#21454193 - Received response [227 Entering Passive Mode (93,184,216,34,247,142)]
FtpControlStream#21454193 - Sending command [STOR file2.zip]
FtpControlStream#21454193 - Received response [150 Opening data channel for file upload to server of "/file2.zip"]
FtpControlStream#21454193 - Received response [226 Successfully transferred "/file2.zip"]
FtpWebRequest#58870012::(Releasing FTP connection#21454193.)

Note that this does not work in .NET Core:
Download multiple files over FTP using one connection in .NET Core

-3
votes

edtFTPnet looks promising, as did FluentFTP

However, in the end, using a DLL assembly or library for this type of project, whether with the LGPL or MIT or anything else, opens up the possibility of a malicious attack by someone replacing the DLL with one which was injected with some form of Malware. Because the source is freely available.

In the end, it is better to write your own, with the code embedded directly in the executable. The extra effort is worth the security.

So I am marking this as answered. The comments were appreciated.