Welcome to Dream.In.Code
Getting C# Help is Easy!

Join 118,922 C# Programmers for FREE! Ask your question and get quick answers from experts. There are 1,978 online right now! We've got more than 500 tutorials and 2,000 snippets. Join and find out why Dream.In.Code is the #1 programming help community on the internet! Registration is fast and FREE... Join Now!



Create an FTP Class Library in C#

 
Reply to this topicStart new topic

> Create an FTP Class Library in C#

PsychoCoder
Group Icon



post 22 Oct, 2007 - 07:30 PM
Post #1


Welcome to my tutorial on FTP Class Library in C#. In this tutorial I will walk you through creating an FTP Class, which can be implemented into your own FTP interface, using Raw FTP Commands and the following Namespaces in the .Net Framework:

The latter of the 4, System.Runtime.Remoting.Messaging, gives us the remoting infrastructure needed to communicate with the remote FTP Server. Support for FTP in .Net 2.0 is not only a new feature, but it doesn't get nearly enough attention as it should. Before 2.0 we were delegated to relying on 3rd party libraries if we wanted to access FTP functionality. I have been asked many times how to go about creating an FTP Class Library in C#, both professionally and from friends I know in the industry, so I thought it was time to dive into the world of Sockets and remoting.

At the time of this tutorial my FTP Class Library doesn't yet have support for FTPS or recursive downloading, but I plan on implementing that in the future. The features you will have available when you complete this tutorial are:
  • Uploading
  • Recursive Uploading
  • Download
  • Resume (Download & Upload)
  • Delete (Files & Directories)
  • Rename Files
  • Create Directory

Keep in mind that this is a large class library, responsible for a lot of functionality, so this tutorial is rather lengthy. Now into the making of your class library. As with any .Net code you write you need to add your using Directives. The using Directive allows us access to the types and methods in Namespace's without having to fully qualify the Namespace as well:

CODE

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;


Next we need our Global variables. Some of these are for populating our Public Properties. Variables are required for properties as properties don't allocate any storage space on their own, and to prevent the variables from being directly modified, always declare them as private:

CODE

#region Variables
//Property Variables
private string _ftpServer = string.Empty;
private string _ftpPath = ".";
private string _ftpUsername = string.Empty;
private string _ftpPassword = string.Empty;
private int _ftpPort = 21;
private bool _isLoggedIn = false;
private bool _isBinary = false;
private int _timeOut = 10;
//Static variables
private static int BUFFER_SIZE = 512;
private static Encoding ASCII = Encoding.ASCII;
//Misc Global variables
private bool _doVerbose = false;
private string statusMessage = string.Empty;
private string result = string.Empty;
private int bytes = 0;
private int _statusCode = 0;
private Byte[] buffer = new Byte[BUFFER_SIZE];
private Socket ftpSocket = null;
#endregion


Now we need to look at the Properties for our library. All our properties are Public, making them accessible from outside the class, but we have Read/Write variables, where the value is set outside our library, and we have ReadOnly properties, where the value is set from within the library itself, and retrieve from elsewhere. In this version of out library we have properties for the following:
  • Server name
  • Working directory path
  • Username
  • Password
  • Port number
  • Is the user logged in
  • Use binary mode or Ascii
  • Timeout value

In future versions I will be adding these as storable values, to keep a list of FTP Servers I connect to, and hashed values for the username/password combination of each server. Here are our properties:

CODE

#region Class Properties
#region Read/Write Properties
/// <summary>
/// Display all communications to the debug log
/// </summary>
public bool DoVerbose
{
    get { return _doVerbose; }
    set { _doVerbose = value; }
}

/// <summary>
/// FTP Server port to use, default is usually 21
/// </summary>
public int FtpPort
{
    get { return _ftpPort; }
    set { _ftpPort = value; }
}

/// <summary>
/// Timeout waiting for a response from server, in seconds.
/// </summary>
public int TimeoutValue
{
    get { return _timeOut; }
    set { _timeOut = value; }
}

/// <summary>
/// Name of the FTP server we wish to connect to
/// </summary>
/// <returns></returns>
public string FtpServer
{
    get { return _ftpServer; }
    set { _ftpServer = value; }
}

/// <summary>
/// The remote port we wish to connect through
/// </summary>
/// <returns></returns>
public int RemotePort
{
    get { return _ftpPort; }
    set { _ftpPort = value; }
}

/// <summary>
/// The working directory
/// </summary>
public string FtpPath
{
    get { return _ftpPath; }
    set { _ftpPath = value; }

}

/// <summary>
/// Server username
/// </summary>
public string FtpUsername
{
    get { return _ftpUsername; }
    set { _ftpUsername = value; }
}

/// <summary>
/// Server password
/// </summary>
public string FtpPassword
{
    get { return _ftpPassword; }
    set { _ftpPassword = value; }
}

/// <summary>
/// If the value of mode is true, set
/// binary mode for downloads, else, Ascii mode.
/// </summary>
public bool IsBinary
{
    get { return _isBinary; }
    set
    {
        //if _isBinary already exit
        if (_isBinary == value) return;
        //check the value being passed
        //if it's true send the command
        //for binary download
        if (value)
            Execute("TYPE I");
        else
            //otherwise stay in Ascii mode
            Execute("TYPE A");
        //now check the status code, if
        //its not 200 throw an exception
        if (_statusCode != 200)
        {
            throw new FtpException(result.Substring(4));
        }
    }
}
#endregion

#region ReadOnly Properties
/// <summary>
/// determine if the user is logged in
/// </summary>
public bool IsLoggedIn
{
    get { return _isLoggedIn; }
}

/// <summary>
/// returns the status code of the command
/// </summary>
public int StatusCode
{
    get { return _statusCode; }
}
#endregion
#endregion


Notice the property StatusCode, this is a readonly property that will hold the FTP code returned from the remote server. This value is crucial when creating an FTP client. Now we move on to the methods of our class library. Before a user can do anything on an FTP Server, they first need to login to that server with the correct username and password. For our login method we create a Socket to communicate with the server through, once we have successfully connection to the host, we can execute 2 FTP commands:
  • USER: Used to send the username to the server
  • PASS: Used to send the password to the server

Then based on the returned status code we act accordingly, we either let the user know they logged on successfully, or we throw our custom exception FtpException (We'll get to that later):

CODE

#region FTPFtpLogin
/// <summary>
/// method to log in to the remote ftp server
/// </summary>
public void FtpLogin()
{
//check if the connection is currently open
if (_isLoggedIn)
{
    //its open so we need to close it
    CloseConnection();
}
//message that we're connection to the server
Debug.WriteLine("Opening connection to " + _ftpServer, "FtpClient");
//create our ip address object
IPAddress remoteAddress = null;
//create our end point object
IPEndPoint addrEndPoint = null;
try
{
    //create our ftp socket
    ftpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    //retrieve the server ip
    remoteAddress = Dns.GetHostEntry(_ftpServer).AddressList[0];
    //set the endpoint value
    addrEndPoint = new IPEndPoint(remoteAddress, _ftpPort);
    //connect to the ftp server
    ftpSocket.Connect(addrEndPoint);
}
catch (Exception ex)
{
    // since an error happened, we need to
    //close the connection and throw an exception
    if (ftpSocket != null && ftpSocket.Connected)
    {
        ftpSocket.Close();
    }
    throw new FtpException("Couldn't connect to remote server", ex);
}
//read the host response
readResponse();
//check for a status code of 220
if (_statusCode != 220)
{
    //failed so close the connection
    CloseConnection();
    //throw an exception
    throw new FtpException(result.Substring(4));
}
//execute the USER ftp command (sends the username)
Execute("USER " + _ftpUsername);
//check the returned status code
if (!(_statusCode == 331 || _statusCode == 230))
{
    //not what we were looking for so
    //logout and throw an exception
    LogOut();
    throw new FtpException(result.Substring(4));
}
//if the status code isnt 230
if (_statusCode != 230)
{
    //execute the PASS ftp command (sends the password)
    Execute("PASS " + _ftpPassword);
    //check the returned status code
    if (!(_statusCode == 230 || _statusCode == 202))
    {
        //not what we were looking for so
        //logout and throw an exception
        LogOut();
        throw new FtpException(result.Substring(4));
    }
}
//we made it this far so we're logged in
_isLoggedIn = true;
//verbose the login message
Debug.WriteLine("Connected to " + _ftpServer, "FtpClient");
//set the initial working directory
ChangeWorkingDirectory(_ftpPath);
}
#endregion


Next we will look at the CloseConnection method. This method is called mainly when we close our application. Here we check to see if the socket is still active, if so we execute the FTP command of QUIT which immediately terminates the connection tot he FTP server:

CODE

#region CloseConnection
/// <summary>
/// method to close the connection
/// </summary>
public void CloseConnection()
{
//display the closing message
Debug.WriteLine("Closing connection to " + _ftpServer, "FtpClient");
//check to see if the connection is still active
//if it is then execute the ftp quit command
//which terminates the connection
if (ftpSocket != null)
{
    Execute("QUIT");
}
//log the user out
LogOut();
}
#endregion


You will notice that in the first 2 methods we looked at, both called a method named Execute. The Execute method is what we will be using to send our FTP commands to the remote server. Here we convert the command string into a byte array, then use the Send Method of the Socket to send the converted command to the remote server. Once we send the command we wait for and read the host's response:

CODE

private void Execute(String msg)
{
    //check to see if verbose debugging is enabled
    //if so write the command to the window
    if (_doVerbose) Debug.WriteLine(msg,"FtpClient");
    //convert the command to a byte array
    Byte[] cmdBytes = Encoding.ASCII.GetBytes((msg + "\r\n").ToCharArray());
    //send the command to the host
    ftpSocket.Send(cmdBytes, cmdBytes.Length, 0);
    //read the returned response
    readResponse();
}
#endregion


You'll notice the method call to readResponse as the last line in the Execute method. It is in here we set the values of our _statusCode variable, along with the result and statusMessage variables. These values come from a method called ParseHostResponse. First the readResponse method:

CODE

#region readResponse
/// <summary>
///
/// </summary>
private void readResponse()
{
    statusMessage = "";
    result = ParseHostResponse();
    _statusCode = int.Parse(result.Substring(0,3));
}
#endregion


Now for ParseHostResponse:

CODE

#region ParseHostResponse
/// <summary>
/// Method to parse the response from the remote host
/// </summary>
/// <returns></returns>
private string ParseHostResponse()
{
while(true)
{
    //retrieve the host response and convert it to
    //a byte array
    bytes = ftpSocket.Receive(buffer,buffer.Length, 0);
    //decode the byte array and set the
    //statusMessage to its value
    statusMessage += ASCII.GetString(buffer,0,bytes);
    //check the size of the byte array
    if ( bytes < buffer.Length )
    {
        break;
    }
}
//split the host response
string[] msg = statusMessage.Split('\n');
//check the length of the response
if (statusMessage.Length > 2)
    statusMessage = msg[msg.Length - 2];
else
    statusMessage = msg[0];

//check for a space in the host response, if it exists return
//the message to the client
if (!statusMessage.Substring(3,1).Equals(" ")) return ParseHostResponse();
//check if the user selected verbose Debugging
if (_doVerbose)
{
    //loop through the message from the host
    for(int i = 0; i < msg.Length - 1; i++)
    {
        //write each line out to the window
        Debug.Write( msg[i], "FtpClient" );
    }
}
//return the message
return statusMessage;
}
#endregion


Now, when doing FTP you need to open a new socket for any transfers. To do this we use the passive approach, by letting the remote server send us the IP and port we need to connect through. We do this by sending the PASV command to the server, it then returns the IP and post in this format 227 Entering Passive Mode (192,168,8,36,8,75), so we need to parse this so we have the information for transferring the data. We do this by looping through the return message, building the IP and port number, then we attempt a connect with that information:

CODE

/// <summary>
/// when doing data transfers, we need to open another socket for it.
/// </summary>
/// <returns>Connected socket</returns>
private Socket OpenSocketForTransfer()
{
//send the PASV command (Passive command)
Execute("PASV");
//check the status code, if it
//isnt 227 (successful) then throw an exception
if (_statusCode != 227)
{
    throw new FtpException(result.Substring(4));
}
//find the index of the opening "("
//and the closing ")". The return
//message from the server, if successful, has
//the IP and port number for the client in
//enclosed in "(" & ")"
int idx1 = result.IndexOf('(');
int idx2 = result.IndexOf(')');
//now we need to get everything in the parenthesis
string ipData = result.Substring((idx1+1),(idx2-idx1)-1);
//create new integer array with size of 6
//the returning message is in 6 segments
int[] msgSegments = new int[6];
//get the length of the message
int msgLength = ipData.Length;
int partCount = 0;
string buffer = "";
//now we need to loop through the host response
for (int i = 0; i < msgLength && partCount <= 6; i++)
{
    //convert each character to a char
    char chr = char.Parse( ipData.Substring(i,1) );
    //check to see if the current character is numeric
    if (char.IsDigit(chr))
    {
        //since its a number we add it to our buffer variable
        buffer+=chr;
    }
    //now we need to check for the
    //comma seperating the digits
    else if (chr != ',')
    {
        //no comma so throw an exception
        throw new FtpException("Malformed PASV result: " + result);
    }
    else
    {
        //check to see if the current character is a comma
        //or if the counter + 1 equals the host response length
        if (chr == ',' || i + 1 == msgLength)
        {
            try
            {
                //since its one of the 2 we add it to the
                //current index of the message segments
                msgSegments[partCount++] = int.Parse(buffer);
                buffer = "";
            }
                //handle any exceptions thrown
            catch (Exception ex)
            {
                throw new FtpException("Malformed PASV result (not supported?): " + result, ex);
            }
        }
    }    
}
//now we assemble the IP address returned from the host
string ipAddress = msgSegments[0] + "."+ msgSegments[1]+ "." + msgSegments[2] + "." + msgSegments[3];
//the last 2 segments are the port we need to use
int port = (msgSegments[4] << 8) + msgSegments[5];

Socket tranferSocket = null;
IPEndPoint ipEndPoint = null;

try
{
    //create our new socket for transfering data
    tranferSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    ipEndPoint = new IPEndPoint(Dns.GetHostEntry(ipAddress).AddressList[0], port);
    tranferSocket.Connect(ipEndPoint);
}
catch(Exception ex)
{
    // doubtfull....
    if ( tranferSocket != null && tranferSocket.Connected ) tranferSocket.Close();
    //throw an FtpException
    throw new FtpException("Can't connect to remote server", ex);
}
//return the socket
return tranferSocket;
}


Now we have all the information for connecting to the remote server, for logging into the server, and for opening a data socket for the data transfer, now lets look at how we would go about uploading a file to an FTP Server. With uploading a file, we will attempt to enable resuming the download in case the connection is interrupted, but remember, all servers don't support resuming.

To do this we initially get the size of the file, then when we start the upload we look for a file with the same name, if it exists then we check its size against the file on the clients computer. If they don't match we send the REST command, which will initialize a resume, it is here when we will be informed if the FTP server supports the resume feature. If it doesn't support resume, we overwrite the file there with the one being uploaded:

CODE

#region UploadFile
/// <summary>
/// Upload a file and set the resume flag.
/// </summary>
/// <param name="fileName"></param>
/// <param name="resume"></param>
public void UploadFile(string fileName, bool resume)
{
    //make sure the user is logged in
    if (!_isLoggedIn)
    {
        //FtpLogin();
        throw new FtpException("You need to log in before you can perform this operation");
    }

    Socket dataSocket = null;
    long resumeOffset = 0;
    //if resume is true
    if (resume)
    {
        try
        {
            //set _isBinary to true
            IsBinary = true;
            //get the size of the file
            resumeOffset = GetFileSize(Path.GetFileName(fileName));
        }
        catch (Exception)
        {
            // file not exist
            resumeOffset = 0;
        }
    }

    // open stream to read file
    FileStream input = new FileStream(fileName, FileMode.Open);
    //if resume is true
    //and the size of the file read is
    //less than the initial value
    if (resume && input.Length < resumeOffset)
    {
        // different file size
        Debug.WriteLine("Overwriting " + fileName, "FtpClient");
        resumeOffset = 0;
    }
    else if (resume && input.Length == resumeOffset)
    {
        // file done
        input.Close();
        Debug.WriteLine("Skipping completed " + fileName + " - turn resume off to not detect.", "FtpClient");
        return;
    }

    //now create our socket needed for
    //the file transfer
    dataSocket = OpenSocketForTransfer();
    //if the file size is greater than 0
    if (resumeOffset > 0)
    {
        //execute the rest command, which
        //sets the point the resume will occurr
        //if the upload is interrupted
        Execute("REST " + resumeOffset);
        //check the status code, if it's not
        //350 the resume isnt supported by the server
        if (_statusCode != 350)
        {
            Debug.WriteLine("Resuming not supported", "FtpClient");
            resumeOffset = 0;
        }
    }
    //execute the store ftp command (starts the transfer of the file)
    Execute("STOR " + Path.GetFileName(fileName));
    //check the status code, we need a
    //value of 150 or 125, otherwise throw an exception
    if (_statusCode != 125 && _statusCode != 150)
    {
        throw new FtpException(result.Substring(4));
    }
    //now check the resumeOffset value,
    //if its not zero then we need to resume
    //the upload process where it ended
    if (resumeOffset != 0)
    {
        //let the user know the upload is resuming
        Debug.WriteLine("Resuming at offset " + resumeOffset, "FtpClient");
        //use the Seek method to get to where the upload ended
        input.Seek(resumeOffset, SeekOrigin.Begin);
    }
    //let the user know the uploading has begun
    Debug.WriteLine("Uploading file " + fileName + " to " + _ftpPath, "FtpClient");
    //upload the file
    while ((bytes = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        dataSocket.Send(buffer, bytes, 0);
    }
    //close our reader
    input.Close();
    //check to see if the socket is still connected
    //if it is then disconnect it
    if (dataSocket.Connected)
    {
        dataSocket.Close();
    }
    //read the host's response
    readResponse();
    //checking for a successful upload code (226 or 250)
    //if not either then throw an exception
    if (_statusCode != 226 && _statusCode != 250)
    {
        throw new FtpException(result.Substring(4));
    }
}
#endregion


You will notice we reference a method named GetFileSize when we upload the file. This is a method we use to retrieve the size of the file they're uploading (we also use it for the downloading as well). Here we sent the SIZE command to the server, and if successful it returns the file size, in decimal format, otherwise we throw an exception:

CODE

#region GetFileSize
/// <summary>
/// Method to retrieve the size of the file based
/// on the name provided
/// </summary>
/// <param name="file">Name of the file to get the size of</param>
/// <returns>The files size</returns>
public long GetFileSize(string file)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
    //FtpLogin();
    throw new FtpException("You need to log in before you can perform this operation");
}
//execute the size command, which
//returns the files size as a decimal number
Execute("SIZE " + file);
long fileSize = 0;
//check our returning status code
//if it's not 213 the command failed
if (_statusCode == 213)
{
    //set the file size
    fileSize = long.Parse(result.Substring(4));
}
else
{
    //command failed so throw an exception
    throw new FtpException(result.Substring(4));
}
//return the file size
return fileSize;
}
#endregion


For downloading a file, we also attempt to implement a resume, checking in the same fashion to see if the server supports this command. Here we require the name of the file on the server to download, the name you want the file on the clients computer, and a boolean resume value. Once we send the REST command, we then send the RETR command to retrieve the remote file. We then set a timeout value, and check that value in a loop, while calling the Receive Method of the Socket Class until we either reach the timeout value, of the number of bytes remaining is zero:

CODE

#region DownloadFile
/// <summary>
/// Download a remote file to a local file name which can include
/// a path, and set the resume flag. The local file name will be
/// created or overwritten, but the path must exist.
/// </summary>
/// <param name="ftpFile">File on the server to download</param>
/// <param name="localFile">Name of the file on the local machine</param>
/// <param name="resume"></param>
public void DownloadFile(string ftpFile, string localFile, Boolean resume)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
    //FtpLogin();
    throw new FtpException("You need to log in before you can perform this operation");
}

IsBinary = true;
//display a downloading file message
Debug.WriteLine("Downloading file " + ftpFile + " from " + _ftpServer + "/" + _ftpPath, "FtpClient");
//check if a local file name was provided
//if not then set its value to the ftp file name
if (localFile.Equals(""))
{
    localFile = ftpFile;
}
//create our filestream object
FileStream output = null;
//check to see if the local file exists
//if it doesnt then create the file
//otherwise overwrite it
if (!File.Exists(localFile))
{
    //create the new file
    output = File.Create(localFile);
}
else
{
    //overwrite the existsing file
    output = new FileStream(localFile, FileMode.Open);
}

//create our new socket for the transfer
Socket dataSocket = OpenSocketForTransfer();
//create our resume point
long resumeOffset = 0;
//if resume was set to true
if (resume)
{
    //set the value of our resume variable
    resumeOffset = output.Length;
    //check if its value is greater than 0 (zero)
    if (resumeOffset > 0)
    {
        //execute our rest command, which sets the
        //resume point for the download in case
        //the download is interrupted
        Execute("REST " + resumeOffset);
        //check the status code, if not a 350
        //code then the server doesnt support resuming
        if (_statusCode != 350)
        {
            //Server dosnt support resuming
            resumeOffset = 0;
            Debug.WriteLine("Resuming not supported:" + result.Substring(4), "FtpClient");
        }
        else
        {
            Debug.WriteLine("Resuming at offset " + resumeOffset, "FtpClient");
            //seek to the interrupted point
            output.Seek(resumeOffset, SeekOrigin.Begin);
        }
    }
}
//execute out retr command
//which starts the file transfer
Execute("RETR " + ftpFile);
//check the status code, we need 150 or 125
//otherwise the download failed
if (_statusCode != 150 && _statusCode != 125)
{
    //throw an FtpException
    throw new FtpException(result.Substring(4));
}
//set a timeout value
DateTime timeout = DateTime.Now.AddSeconds(_timeOut);
//check the timeout value against the current time
//if its less then download the file
while (timeout > DateTime.Now)
{
    //receive the binary data from the socket
    bytes = dataSocket.Receive(buffer, buffer.Length, 0);
    //write the file
    output.Write(buffer, 0, bytes);
    //make sure the file is greater than
    //zero in size, if not exit the method
    if (bytes <= 0)
    {
        break;
    }
}
//close our stream
output.Close();
//check to see if the socket is still open,
//if it is then close it
if (dataSocket.Connected)
{
    dataSocket.Close();
}
//read the host's response
readResponse();
//we're looking for a status code of 226 or 250,
//if that isnt returned the download failed
if (_statusCode != 226 && _statusCode != 250)
{
    throw new FtpException(result.Substring(4));
}
}
#endregion


How will someone know what file(s) they want to download? Thats good question, which I have a good answer for. When you plug your library into a UI for the user, you have a method to list all the files that are currently on the FTP server. We do this by executing the NLST command. This will retrieve the name and size of all files on the server, then we simply populate a string array with that information, and use it to populate the UI for the client to view:

CODE

#region ListFiles
/// <summary>
/// Return a string array containing the remote directory's file list.
/// </summary>
/// <param name="mask"></param>
/// <returns></returns>
public string[] ListFiles(string mask)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
    //FtpLogin();
    throw new FtpException("You need to log in before you can perform this operation");
}
//create new socket
Socket dataSocket = OpenSocketForTransfer();
//execute the ftp nlst command, which
//returns a list of files on the remote server
Execute("NLST " + mask);
//check the return code, we're looking for
//either 150 or 125, otherwise the command failed
if (!(_statusCode == 150 || _statusCode == 125))
{
    //failed, throw an exception
    throw new FtpException(result.Substring(4));
}
//set the message to empty
statusMessage = "";
//create a timeout value based on our timeout property
DateTime timeout = DateTime.Now.AddSeconds(_timeOut);
//loop while out timeout value is
//greater than the current time
while (timeout > DateTime.Now)
{
    //retrieve the data from the host
    int bytes = dataSocket.Receive(buffer, buffer.Length, 0);
    //convert it to Ascii format
    statusMessage += ASCII.GetString(buffer, 0, bytes);
    //exit the method is nothing is returned
    if (bytes < buffer.Length) break;
}
//throw the returned message into a string array
string[] msg = statusMessage.Replace("\r", "").Split('\n');
//close the socket connection
dataSocket.Close();
//check the return message
if (statusMessage.IndexOf("No such file or directory") != -1)
    //return an empty message
    msg = new string[] { };
//read the host's response
readResponse();
//if we didnt receive a status code of 226
//then the process failed
if (_statusCode != 226)
    //return an empty message
    msg = new string[] { };
//    throw new FtpException(result.Substring(4));

return msg;
}
#endregion


I had mentioned earlier in this tutorial that our library will offer recursive uploading to the FTP server, so lets take a look at how we offer that functionality. FOr this we require the user to provide the directory path, a boolean value for recursive, and a mask value , such as '*.*', '*.jpg*', etc. We take that directory name and check to see if the directory already exists on the server, if it doesnt we create that directory.

From there we change the working directory to the new directory, we want to be in the new directory when we start the upload, we'll look at that method next.Then we start our loop, we first upload all the files that are in that directory, then, if the user opted for recursive, we loop through all the directories in the specified directory and upload them, and their files as well, then we change the working directory back to the root directory:

CODE

#region UploadDirectory
/// <summary>
/// Upload a directory and its file contents
/// </summary>
/// <param name="dirPath">Path of the directory to upload</param>
/// <param name="recursive">Whether to recurse sub directories</param>
/// <param name="fileMask">Only upload files of the given mask(i.e;'*.*','*.jpg', ect..)</param>
public void UploadDirectory(string dirPath, bool recursive, string fileMask)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
    //FtpLogin();
    throw new FtpException("You need to log in before you can perform this operation");
}
string[] directories = dirPath.Replace("/", @"\").Split('\\');
string rootDirectory = directories[directories.Length - 1];

// make the root dir if it does not exist
if (ListFiles(rootDirectory).Length < 1)
{
    CreateDirectory(rootDirectory);
}
//make the new directory the working directory
ChangeWorkingDirectory(rootDirectory);
//loop through the files in the directory
foreach (string file in Directory.GetFiles(dirPath, fileMask))
{
    //upload each file
    UploadFile(file, true);
}
//check if recusrsive was specified
if (recursive)
{
    //since recursive is true, we loop through all the
    //directories in the directory provided
    foreach (string directory in Directory.GetDirectories(dirPath))
    {
        //upload each directory
        UploadDirectory(directory, recursive, fileMask);
    }
}
//change working directory back to root level
ChangeWorkingDirectory("..");
}
#endregion


For changing the working directory for the user all we need is the name of the directory they wish to make the working directory. We then execute the [i[CWD[/i] command, which stands for, Change Working Directory. We then check for a stus code of 250, if we dont receive that code we throw an exception, if we do we then execute the PWD command (Print Working Directory) and check for a status of 257. If we dont receive that status from the server we throw an exception, otherwise we write out the message of the new directory:

CODE

#region ChangeWorkingDirectory
/// <summary>
/// Change the current working directory on the remote FTP server.
/// </summary>
/// <param name="dirName"></param>
public void ChangeWorkingDirectory(string dirName)
{
    //check to make sure a directory name was supplied
    if (dirName == null || dirName.Equals(".") || dirName.Length == 0)
    {
        //no directory was provided so throw an exception
        //and break out of the method
        throw new FtpException("A directory name wasn't provided. Please provide one and try your request again.");
    }
    //before we can change the directory we need
    //to make sure the user is logged in
    if (!_isLoggedIn)
    {
        //FtpLogin();
        throw new FtpException("You need to log in before you can perform this operation");
    }
    //execute the CWD command = Change Working Directory
    Execute("CWD " + dirName);
    //check for a return status code of 250
    if (_statusCode != 250)
    {
        //operation failed, throw an exception
        throw new FtpException(result.Substring(4));
    }
    //execute the PWD command
    //Print Working Directory
    Execute("PWD");
    //check for a status code of 250
    if (_statusCode != 257)
    {
        //operation failed, throw an exception
        throw new FtpException(result.Substring(4));
    }
    // we made it this far so retrieve the
    //directory from the host response
    _ftpPath = statusMessage.Split('"')[1];

    Debug.WriteLine("Current directory is " + _ftpPath, "FtpClient");
}
#endregion


There are 2 more small methods I'd like to go over with you, one is for renaming a file, the other is for deleting a file. There is, of course, more in this library, but this tutorial is getting quite long as it is, so I'll be including the file with this tutorial, so you can look through the rest of the code.

For deleting a file, we send the DELE command to the remote server, then wait for a response. If we receive a status code of 250 all is good and the file was deleted, if not we throw an exception with the error message returned from the server:

CODE

#region DeleteFile
/// <summary>
/// method to delete a file from the FTP server.
/// </summary>
/// <param name="file">File to delete</param>
public void DeleteFile(string file)
{
//make sure the user is logged in
if (!_isLoggedIn)
{
    //FtpLogin();
    throw new FtpException("You need to log in before you can perform this operation");
}
//execute the delete command
Execute("DELE " + file);
//check for a status code of 250, if
//not then throw an exception
if (_statusCode != 250)
{
    throw new FtpException(result.Substring(4));
}
Debug.WriteLine("Deleted file " + file, "FtpClient");
}
#endregion


Now, to rename a file we send two commands to the server. First we send the RNFR command, which is rename from, then we send the RNTO, which is rename to. When wwe send the first command we check for a status code of 350, if successful we send the 2nd command, then look for a status of 250. If all goes well then the file is renamed, otherwise we throw an exception with the message from the FTP server:

CODE

#region RenameFile
/// <summary>
/// Rename a file on the remote FTP server.
/// </summary>
/// <param name="oldName">File to rename</param>
/// <param name="newName">New name of the file</param>
/// <param name="replace">setting to false will throw exception if it exists</param>
public void RenameFile(string oldName, string newName, bool replace)
{
    //make sure the user is logged in
    if (!_isLoggedIn)
    {
        //FtpLogin();
        throw new FtpException("You need to log in before you can perform this operation");
    }
    //execute the rename from command
    Execute("RNFR " + oldName);
    //check for a status code of 350
    if (_statusCode != 350)
    {
        throw new FtpException(result.Substring(4));
    }
    //if they didnt choose to replace the file, and a
    //file with that name already exists then throw an exception
    if (!replace && ListFiles(newName).Length > 0)
    {
        throw new FtpException("File already exists");
    }
    //execute the rename to command
    Execute("RNTO " + newName);
    //check for a status code of 250, if
    //not then throw an exception
    if (_statusCode != 250)
    {
        throw new FtpException(result.Substring(4));
    }
    //write the successful message out
    Debug.WriteLine("Renamed file " + oldName + " to " + newName, "FtpClient");
}
#endregion


One finaly method, this method is for creating a new directory on the server. To do this we send the MKD command to the server (Make Directory), then check the status code thats returned. If its not either 250 or 257 we throw an exception containing the message from the server, otherwise the operation was successful:

CODE

#region CreateDirectory
/// <summary>
/// Create a directory on the remote FTP server.
/// </summary>
/// <param name="dirName">Name of the directory to create</param>
public void CreateDirectory(string dirName)
{
    //make sure the user is logged in
    if (!_isLoggedIn)
    {
        //FtpLogin();
        throw new FtpException("You need to log in before you can perform this operation");
    }
    //check to make sure a directory name was supplied
    if (dirName == null || dirName.Equals(".") || dirName.Length == 0)
    {
        //no directory was provided so throw an exception
        //and break out of the method
        throw new FtpException("A directory name wasn't provided. Please provide one and try your request again.");
    }
    //execute the make directory command
    Execute("MKD " + dirName);
    //check for a status code of 250 or 257
    if (_statusCode != 250 && _statusCode != 257)
    {
        //operation failed, throw an exception
        throw new FtpException(result.Substring(4));
    }

    Debug.WriteLine("Created directory " + dirName, "FtpClient");
}
#endregion


There are a couple more methods in the library, but Ill let you look through them on your own, because I now want to show the short class file that makes our cutom FtpException we throw whever a command fails. In this class we inherit from the Exception Class, and it contains nothing but the 2 base contructors Microsoft suggests all custom exception classes have:

CODE

public class FtpException : Exception
{
    public FtpException(string message) : base(message)
    {
    }
    public FtpException(string message, Exception innerException) : base(message, innerException)
    {
    }
}


That's it, thats our Custom Exception class, and our FTP Class Library. Like I metioned earlier, I will be including this library with the tutorial, but it is under the GNU General Public License so the licensing information must remain in tact with the library and code at all times. So now you have a good understanding of creating your own custom FTP Class Library utilizing just the objects available in the .Net Framework 2.0.

I know this tutorial was long and indepth, but I hope you found it useful and informative, as I did when creating this library. Thank you so much for reading.

Attached File  PCFtp.zip ( 46.54k ) Number of downloads: 851


Happy Coding! smile.gif
Go to the top of the page
+Quote Post


Register to Make This Ad Go Away!

born2c0de
Group Icon



post 23 Oct, 2007 - 12:39 PM
Post #2
I needed something like that.
Awesome Tutorial!!!
Go to the top of the page
+Quote Post

orcasquall
Group Icon



post 26 Oct, 2007 - 06:26 AM
Post #3
Wow, that is some tutorial... I remember I had to write something like that to do file sending via FTP in ASP.NET. Since there wasn't any need to write a full class, I just did the STOR command (and USER, PASS, QUIT).

Given that .NET 2.0 has built-in classes for FTP too, what are the pros and cons using the class library here and the built-in classes? For example, I used this (in another program) for sending files
CODE

FtpWebRequest ftpreq = (FtpWebRequest)WebRequest.Create("ftp://1.2.3.4/remotefile.txt");
ftpreq.Method = WebRequestMethods.Ftp.UploadFile;
ftpreq.UseBinary = true;
ftpreq.Credentials = new NetworkCredential("scott", "tiger");
// the default when logged into Windows is HTTP proxy.
// This will unset the default.
ftpreq.Proxy = null;
ftpreq.UsePassive = true;

byte[] by = File.ReadAllBytes("localfile.txt");
ftpreq.ContentLength = by.Length;

Stream rs = ftpreq.GetRequestStream();
rs.Write(by, 0, by.Length);
rs.Close();

FtpWebResponse resp = (FtpWebResponse)ftpreq.GetResponse();
resp.Close();


I've never been strong in networking stuff...
Go to the top of the page
+Quote Post

jain.rulez
*



post 6 Nov, 2007 - 03:35 AM
Post #4
that was a great tutorial but i m not able to use it.
please tell me how to use it in a different program say on a button click.



Go to the top of the page
+Quote Post


Fast ReplyReply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 

Lo-Fi Version Time is now: 10/13/08 04:59AM

Live C# Help!

C# Tutorials

Reference Sheets

C# Snippets

Bye Bye Ads

Free DIC T-Shirt

T-Shirt Example

Related Sites

Monthly Drawing

Thumb Drive

Partners

Top Contributors

Top 10 Kudos This Month