I’ve played around with FTP couple of weeks back. My application is required to feed FTP folder with 1000+ files daily. I’ve been googling around and I found one of the methods is to use WebApplication so I decided to put this WebApplication method to upload while iterating every single files in the directory. After 5 files, it keeps giving me error The remote server returned an error: (503) Bad sequence of commands. Strangely the only the first 4 files always uploaded successfully, my thought was the FTP server forcely terminated the connection which is right. After googling I found that we should set KeepAlive property to false which means new connection is always created instead of maintained but unfortunately WebClient class doesn’t have KeepAlive property. Finally, I’ve come up with this code which solves my issue in uploading 1000+ files one after the other by doing Asynchronous Upload
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading;
using System.IO;
namespace Helpers
{
public class FtpHelpers
{
public class FtpState
{
private ManualResetEvent wait;
private FtpWebRequest request;
private string fileName;
private Exception operationException = null;
string status;
public FtpState()
{
wait = new ManualResetEvent(false);
}
public ManualResetEvent OperationComplete
{
get { return wait; }
}
public FtpWebRequest Request
{
get { return request; }
set { request = value; }
}
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
public Exception OperationException
{
get { return operationException; }
set { operationException = value; }
}
public string StatusDescription
{
get { return status; }
set { status = value; }
}
}
public void AsynchronousUpload(string destinationPath, string sourcePath, string userName, string password)
{
// Create a Uri instance with the specified URI string.
// If the URI is not correctly formed, the Uri constructor
// will throw an exception.
ManualResetEvent waitObject;
Uri target = new Uri(destinationPath);
string fileName = sourcePath;
FtpState state = new FtpState();
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(target);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.KeepAlive = false;
request.Proxy = null;
// This example uses anonymous logon.
// The request is anonymous by default; the credential does not have to be specified.
// The example specifies the credential only to
// control how actions are logged on the server.
if (!(string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(password)))
{
request.Credentials = new NetworkCredential(userName, password);
}
// Store the request in the object that we pass into the
// asynchronous operations.
state.Request = request;
state.FileName = fileName;
// Get the event to wait on.
waitObject = state.OperationComplete;
// Asynchronously get the stream for the file contents.
request.BeginGetRequestStream(
new AsyncCallback(EndGetStreamCallback),
state
);
// Block the current thread until all operations are complete.
waitObject.WaitOne();
// The operations either completed or threw an exception.
if (state.OperationException != null)
{
throw state.OperationException;
}
}
private static void EndGetStreamCallback(IAsyncResult ar)
{
FtpState state = (FtpState)ar.AsyncState;
Stream requestStream = null;
// End the asynchronous call to get the request stream.
try
{
requestStream = state.Request.EndGetRequestStream(ar);
// Copy the file contents to the request stream.
const int bufferLength = 2048;
byte[] buffer = new byte[bufferLength];
int count = 0;
int readBytes = 0;
FileStream stream = File.OpenRead(state.FileName);
do
{
readBytes = stream.Read(buffer, 0, bufferLength);
requestStream.Write(buffer, 0, readBytes);
count += readBytes;
}
while (readBytes != 0);
// IMPORTANT: Close the request stream before sending the request.
requestStream.Close();
// Asynchronously get the response to the upload request.
state.Request.BeginGetResponse(
new AsyncCallback(EndGetResponseCallback),
state
);
}
// Return exceptions to the main application thread.
catch (Exception e)
{
state.OperationException = e;
state.OperationComplete.Set();
return;
}
}
// The EndGetResponseCallback method
// completes a call to BeginGetResponse.
private static void EndGetResponseCallback(IAsyncResult ar)
{
FtpState state = (FtpState)ar.AsyncState;
FtpWebResponse response = null;
try
{
response = (FtpWebResponse)state.Request.EndGetResponse(ar);
response.Close();
state.StatusDescription = response.StatusDescription;
// Signal the main application thread that
// the operation is complete.
state.OperationComplete.Set();
}
// Return exceptions to the main application thread.
catch (Exception e)
{
state.OperationException = e;
state.OperationComplete.Set();
}
}
}
}
Usage:
#Region "Properties"
Private _ftpUploader As FtpHelpers = Nothing
Private ReadOnly Property FtpUploader() As FtpHelpers
Get
If (_ftpUploader Is Nothing) Then
_ftpUploader = New FtpHelpers()
End If
Return _ftpUploader
End Get
End Property
#End Region
Private Sub ProcessFilesToFTP()
' make a reference to a directory
Dim di As New IO.DirectoryInfo(ConfigurationManager.AppSettings("OutputZIPDirectory"))
Dim jobFiles As IO.FileInfo() = di.GetFiles()
Console.WriteLine(String.Format("FTP Location: {0}", ConfigurationManager.AppSettings("ftplocation")))
'list the names of all files in the specified directory
For Each jobFile As IO.FileInfo In jobFiles
'process only zip file
If (jobFile.FullName.Contains("zip")) Then
Console.WriteLine(String.Format("Upload file {0}", jobFile.Name))
FtpUploader.AsynchronousUpload(ConfigurationManager.AppSettings("ftplocation") + "/" + jobFile.Name, jobFile.FullName, _
ConfigurationManager.AppSettings("ftpuser"), _
ConfigurationManager.AppSettings("ftppassword"))
End If
Next
Console.WriteLine(String.Format("XML files has been uploaded to {0}", ConfigurationManager.AppSettings("ftplocation")))
End Sub
The drawback with this code is in the FTP connection where the connection always reinitialized for every single file.