Click here to Skip to main content
Click here to Skip to main content

Using FTP with .NET including sending server commands

, 19 May 2004
Rate this:
Please Sign up or sign in to vote.
How to send and receive files, enumerate directories and subdirectories remotely on demand, send server commands through FTP control port.

Introduction

This article describes the usage of the CFTPConnection class wrapped in a managed C++ assembly. The following functions are used and discussed:

  1. Sending / receiving files from FTP server.
  2. Enumerating directory contents.
  3. Sending server commands, i.e., NLST to the FTP server using CFTPConnection::Command method and parsing the server's response synchronously. The FTP data (20) and control (21) ports.
  4. Using these functions from a simple UI.

Body

Recently, I had a need to develop an FTP client as a sub-function of a large C# Windows Form application. I quickly discovered that FTP support was not part of the FCL. I decided that the simplest way to address my needs was to develop a wrapper around the CFTPConnection class MFC WinInet wrapper (yes a wrapper of a wrapper -- Smile | :) ).

I started out by just creating a purely unmanaged C++ .dll and just exporting a couple of functions, and then using PInvoke to call the functions from C#. I found this to be both inelegant and largely infeasible when it came to enumerating directory contents and returning these contents to a C# application. The solution I finally settled on was to create a managed C++ assembly that would interact with CFTPConnection and would package things up into nice managed classes for the C# clients.

Sending / Receiving files

Sending and receiving files is supported right out of the box by CFTPConnection. My wrapper around these functions is thin at best. Here's a simple example of sending a file with the wrapper:

bSent = FTP_Wrapper.SendFTPFile(Mode, Convert.ToInt32(szFTPServerPort), 
                     szFTPServerName, szFTPUserName, 
                     szFTPUserPassword, szFTPFile, 
                     szFTPDestFile, 
                     ref szErrorMsg);
if (!bSent)
    Console.WriteLine(szErrorMsg);

Enumerating Directory Contents

Getting the contents of an FTP directory is pretty straightforward using CFtpFileFind. I wanted to use this class and return a collection of files and subfolders to my managed clients. One thing to watch out for when enumerating directory contents of a server is that you don't try to pull back an entire server's contents before showing them in your GUI. For a larger server, this reading of the entire directory structure could take quite a while. A better approach (and one that is shown in the sample application) is to just enumerate the root directory, show the results, and then when a user wants to go down into a subdirectory, enumerate that subdirectory.

Enter the DirectoryContents class:

namespace FTP_Wrap
{
  public __gc class DirectoryContents;
  public __gc class DirectoryItem 
  {
    public:
      System::String* m_Name;
      System::String* m_PathName;
      int m_Type; // 0 = file, 1 = subdirectory;
      long m_Size; // file size in bytes
      bool Equals(System::Object* pObj); 
  };

  public __gc class DirectoryContents
  {
    public:
      DirectoryContents();
      virtual ~DirectoryContents() {}; 
      bool LoadList(int SourceType, System::String* pServerNm, 
         int ServerPort, System::String* pUserNm, 
         System::String* pPwd, System::String* pRemoteDir, 
         System::String* pMask, System::String*& pErrorMsg);
      System::Collections::ArrayList* m_DirectoryList; 
  };
}

Using the DirectoryContents class from C#:

static void Main(string[] args)
{
  //
  // TODO: Add code to start application here
  // 

  //Do the FTP dir command and recurse all sub folders:
  ShowDirContents("/");
}

static void ShowDirContents(String Dir)
{
  FTP_Wrap.DirectoryContents dc = new DirectoryContents();
  String ErrorMsg = "";
  bool Loaded = dc.LoadList("localhost", 21, "USERID", "PASSWORD", 
                Dir, "*", ref ErrorMsg);

  if (!Loaded)
  {
    Console.WriteLine(ErrorMsg);
    return;
  } 
  foreach (DirectoryItem item in dc.m_DirectoryList)
  {
    if (item.m_Type == 0) //files
    {
      Console.WriteLine("File: " + item.m_PathName + " Size: " + item.m_Size);
    }
    else //sub directories
      ShowDirContents(item.m_PathName);
  }
}

CFTPConnection::Command

One additional challenge I faced in implementing my client functionality was that one of the FTP machine types that would typically be interfaced with did not support truly standard FTP. As a result, using CFTPFileFind did not work with the server to enumerate directories. Trying to use CFTPFileFind would return a bunch of garbage that was barely if at all interpretable as a list of files. I needed a better way to get the file lists from the server.

After digging around a little more, I noticed that there is a set of 'server' commands that can be sent to an FTP server using the Command method of CFTPConnection. The commands are sent on port 21 (the FTP control port) but the responses are sent back on port 20 (the data port). Luckily (or I'd have more code to deal with), CFTPConnection handles these communications.

In order to get the directory contents using a server command, I issued a NLST command to the server and parsed the response.

Here's what happens at the TCP/IP level:

Send NLST through port 21 to the server:

Sample screenshot

Get response to NLST from server to my local port 20:

Sample screenshot

Code from directorycontents.cpp:

// Issue the command to the server:
CString dir(pRemoteDir);
InternetFile* pFile = NULL;
System::String* pRes = NULL;
BOOL bChg = pFtpConn->SetCurrentDirectory(dir);
CString strDir;
pFtpConn->GetCurrentDirectory(strDir);
File = pFtpConn->Command(_T("NLST"), CFtpConnection::CmdRespRead, 
FTP_TRANSFER_TYPE_BINARY);

//Process the response (the file list):
ULONGLONG a = pFile->GetLength();
char readBuf[256];
unsigned int rd = 0;
System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
do 
{
  rd = pFile->Read(readBuf, 256);
  if (rd > 0)
  {
    System::String* pStr = new System::String(readBuf);
    pSb->Append(pStr, 0, rd);
  }
} while (rd > 0);

pRes = pSb->ToString();

// ok, parse the response:
System::String* pDelim = "\r\n";
System::Text::RegularExpressions::Regex* pRegex = 
  new System::Text::RegularExpressions::Regex(pDelim);
System::String* parts[] = pRegex->Split(pRes);

for (int n = 0; n < parts->Length; n++)
{
  String* pItemNm = parts[n];
  if (pItemNm->Length == 0)
    continue;

  DirectoryItem* pItem = new DirectoryItem; 
  if (pItemNm->EndsWith("/"))
  {
    pItem->m_Type = 1; // a directory
    String* pTemp = pItemNm->Substring(0, pItemNm->Length - 1);
    int nNameBegin = pTemp->LastIndexOf("/");
    pItem->m_Name = pTemp->Substring(nNameBegin + 1);
    System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
    pSb->Append(pRemoteDir);
    pSb->Append(pItem->m_Name);
    pSb->Append("/");
    pItem->m_PathName = pSb->ToString();
  }
  else
  {
    pItem->m_Type = 0; // a file
    int nNameBegin = pItemNm->LastIndexOf("/");
    pItem->m_Name = pItemNm->Substring(nNameBegin + 1);
    System::Text::StringBuilder* pSb = new System::Text::StringBuilder();
    pSb->Append(pRemoteDir);
    pSb->Append(pItem->m_Name);
    pItem->m_PathName = pSb->ToString();
  }
  pItem->m_Size = 0;
  m_DirectoryList->Add(pItem);
}

The big thing to note is that the Command method returns a CInternetFile. To get the actual response data, you just read that file till EOF.

The sample application

I've included a very simple stripped down sample application to show the functionality of the classes. In order to play with the DirectoryContents enumerations, select the '...' button beside 'file to get' in the get file form, or the '...' button beside the 'FTP Destination File' in the send file form. Go ahead and step through these to get a better idea if I've left something out.

The sample app will save your FTP settings (port, server name, most recent file names, etc.) to the current user registry under the software key.

Conclusion

These classes solved what I was trying to do. I hope they can help someone here.

References

I used numerous resources for figuring out how to use the CFTPConnection class. Plenty of good information on it is in MSDN. Also, I used the ethereal packet capture utility not only for this but for a lot of network troubleshooting. It can be downloaded from here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Tim Kohler
Chief Technology Officer
United States United States
Working to keep a technology company up to date. Wondering when Microsoft will hire a fresh, innovative guy to run the company.

Comments and Discussions

 
GeneralMy vote of 2 Pinmemberhadree25-Jan-12 23:22 
GeneralCompling error Pinmembervijayr8-Apr-10 2:04 
QuestionDependencies? Pinmemberjess_le_chat22-Jul-09 8:53 
QuestionHow to run FTP commands Pinmember12kaunas7-Feb-07 7:13 
QuestionHow to use your FTP_Wrap? [modified] Pinmemberfnjcr4-Dec-06 16:08 
GeneralQuick question PinmemberTom Wright8-Jun-05 7:12 
GeneralRe: Quick question PinmemberTim Kohler8-Jun-05 9:49 
GeneralAnother free, open-source .NET FTP component Pinmemberh_c_a_andersen12-Nov-04 16:08 
GeneralLINK : fatal error LNK1256 PinmemberSeong Hyun3-Aug-04 17:38 
GeneralRe: LINK : fatal error LNK1256 Pinmembervijayr8-Apr-10 2:07 
QuestionWhy FTP_Wrap is not by C#? Pinmembergamebaby9-Jun-04 2:35 
???
 
???
AnswerRe: Why FTP_Wrap is not by C#? PinmemberTim Kohler14-Jun-04 11:45 
QuestionWhy FTP_Wrap is not by C#? Pinmembergamebaby9-Jun-04 2:35 
GeneralBroken link... PinmemberKarby20-May-04 13:31 
GeneralRe: Broken link... PinmemberTim Kohler21-May-04 6:19 
GeneralRe: Broken link... PinmemberMagadass3-Feb-06 21:22 
GeneralWow... Pinmemberbryansp20-May-04 7:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 20 May 2004
Article Copyright 2004 by Tim Kohler
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid