c# / More effective way to upload file with ftp - c#

I'm uploading a .txt file with c# and:
client.Credentials = new NetworkCredential(ftpU, ftpP);
client.UploadFile("here ftp server", "STOR", lfilepath);
And sometimes it just throws error like "System Error"
This .txt is just log in information, content of this txt is like User: Name At: 2015/12/12 08:43 AM
Is there any option to eliminate this error? Make ftp upload more effective? Or any idea to save log on informations in Internet.

Try to use FtpWebRequest and WebRequestMethods.Ftp.UploadFile. Here is the piece of code which we use for uplaoding files from ZipArchive to FTP (so there is also an option showing creating a directories if you need it too). From all methods which I've tested it was the most efficient one.
//// Get the object used to communicate with the server.
var request =
(FtpWebRequest)
WebRequest.Create("ftp://" + ftpServer + #"/" + remotePath + #"/" +
entry.FullName.TrimEnd('/'));
//// Determine if we are transferring file or directory
if (string.IsNullOrWhiteSpace(entry.Name) && !string.IsNullOrWhiteSpace(entry.FullName))
request.Method = WebRequestMethods.Ftp.MakeDirectory;
else
request.Method = WebRequestMethods.Ftp.UploadFile;
//// Try to transfer file
try
{
//// This example assumes the FTP site uses anonymous logon.
request.Credentials = new NetworkCredential(user, password);
switch (request.Method)
{
case WebRequestMethods.Ftp.MakeDirectory:
break;
case WebRequestMethods.Ftp.UploadFile:
var buffer = new byte[8192];
using (var rs = request.GetRequestStream())
{
StreamUtils.Copy(entry.Open(), rs, buffer);
}
break;
}
}
catch (Exception ex)
{
//// Handle it!
LogHelper.Error<FtpHelper>("Could not extract file from package.", ex);
}
finally
{
//// Get the response from the FTP server.
var response = (FtpWebResponse) request.GetResponse();
//// Close the connection = Happy a FTP server.
response.Close();
}

Related

Receiving errors when retrieving a file via FTP and delivering to multiple servers

To start with, my goal is to use FTP to retrieve a file and put it on multiple servers. This is intended as a backup procedure where we're taking a file and putting it onto two different backup servers then removing it from the FTP server.
In case this is relevant, the program runs as a service on a server.
I have tried two different methods and gotten 3 different errors so I will detail those. I am open to trying different libraries, whole different methods, and that's why I'm focusing on the overall goal instead of the specific code and errors.
So the first method worked successfully when I tested it locally on my dev laptop; I was able to do everything I wanted. It's worth noting that I debugged as the same domain account that the service runs on in the environments. When I deployed to test I received the error "The underlying connection was closed: The server committed a protocol violation."
I have (of course) trimmed this code down to what I believe are the relevant parts.
This gets called for each path that's being delivered to. They read similar to:
first case: \\fqdn\directory
second case: \\192.168.123.123\directory$
private bool CopyFTPToPath(string file, string path)
{
try
{
string filePath = "ftp://" + Settings["Host"].Value + "/" + Settings["Path"].Value + "/" + file;
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(filePath);
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.Credentials = new NetworkCredential("user", "pass");
using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(responseStream))
{
using (StreamWriter writer = new StreamWriter(path + "/" + file, false))
{
writer.Write(reader.ReadToEnd());
}
}
}
}
return File.Exists(path + "/" + file);
catch(Exception ex)
{
Utility.LogError(ErrorType.Alert, ex);
}
}
Again this is successful when I run it locally, it fails when run on the server with the same credentials, with the above protocol violation error. The error occurs when it attempts to create the StreamWriter.
So I tried using a library to handle the connection and grabbed the WinSCP library because I've seen it recommended on here frequently. The code I wrote for that is this:
private bool CopyFTPToPath(string file, string path)
{
try
{
string filePath = "ftp://" + Settings["Host"].Value + "/" + Settings["Path"].Value + "/" + file;
SessionOptions sessionOptions = new SessionOptions() {
Protocol = Protocol.Ftp,
HostName = Settings.Settings["Host"].Value,
UserName = "user",
Password = "pass"
};
using(Session session = new Session())
{
session.Open(sessionOptions);
TransferOptions transferOptions = new TransferOptions();
transferOptions.TransferMode = TransferMode.Binary;
TransferEventArgs result = session.GetFileToDirectory(filePath, path,false,transferOptions);
}
return File.Exists(path + "/" + file);
catch(Exception ex)
{
Utility.LogError(ErrorType.Alert, ex);
}
}
Now this block of code fails at the session.GetFileToDirectory call, throwing a System.IO.DirectoryNotFoundException on the path. In this case I'm wondering if WinSCP is unable to handle a network path as the local directory path.
Here is a sanitized stacktrace from the error, for what that's worth. If the local directory parameter is actually local it seems to work, so I think that's what's going on there.
{System.IO.DirectoryNotFoundException: \\192.***.***.***\Path\Path
at WinSCP.Session.DoGetFilesToDirectory(String remoteDirectory, String localDirectory, String filemask, Boolean remove, TransferOptions options, String additionalParams)
at WinSCP.Session.GetEntryToDirectory(String remoteFilePath, String localDirectory, Boolean remove, TransferOptions options, String additionalParams)
at WinSCP.Session.GetFileToDirectory(String remoteFilePath, String localDirectory, Boolean remove, TransferOptions options)
at Program.RoutineTasks.Backup.CopyFTPToPath(String file, String path) in C:\Projects\Program\RoutineTasks\Backup.cs:line 114}
FTP is my only option to access this file. The file is 130gb, I don't have disk space on the server that runs this to copy it local to hand out. Both of those are outside of my control.
EDIT: I have found a solution that will definitely work if I can figure out how to manage the streams better, because the files are huge so I need to break them up to prevent running out of memory.
That code is this, in place of the using Session block above
using(Session session = new Session())
{
session.Open(sessionOptions);
TransferOptions transferOptions = new TransferOptions();
transferOptions.TransferMode = TransferMode.Binary;
using (StreamReader reader = new StreamReader(session.GetFile(filePath, transferOptions)))
{
using (StreamWriter writer = new StreamWriter(path + "/" + file, false))
{
while (!reader.EndOfStream)
{
writer.Write(reader.Read());
}
}
}
}
In the end this wound up being related to permissions on the account. When I presented to the sysadmin that the service account's access to the path was behaving inconsistently between my laptop and the test environment here is what he said:
"you have to configure all non windows shares through the mmc snapin"
The paths I am delivering to are non-windows. He made a change there and the SA was then able to access the paths from the test server. This was not something that could have been solved simply by code.

How to Determine if FTP Directory Exists

I wrote the following code to detect if a directory exists. The DirectoryExists method accepts either a fully qualified or relative path.
public bool DirectoryExists(string directory)
{
try
{
FtpWebRequest request = GetRequest(directory);
request.Method = WebRequestMethods.Ftp.ListDirectory;
using (FtpWebResponse response = request.GetResponse() as FtpWebResponse)
using (StreamReader sr = new StreamReader(response.GetResponseStream(), System.Text.Encoding.ASCII))
{
sr.ReadToEnd();
}
return true;
}
catch { }
return false;
}
protected FtpWebRequest GetRequest(string filename = "")
{
FtpWebRequest request = WebRequest.Create(_host.GetUrl(filename)) as FtpWebRequest;
request.Credentials = new NetworkCredential(Username, Password);
request.Proxy = null;
request.KeepAlive = false;
return request;
}
Note: _host.GetUrl(filename) returns the fully qualified path of the specified directory or filename. In my case, this is ftp://www.mydomain.com/Articles/controls/precisely-defining-kilobytes-megabytes-and-gigabytes.
This code has worked for many months. But all of a sudden it stopped working. Now, there is no exception raised in DirectoryExists when the directory does not exist. sr.ReadToEnd() simply returns an empty string.
I posted a similar question and it was suggested that I should always append / to the end of my path. I tried that, and thought I got an exception once, but it's not raising an exception now.
I don't know if the behavior changed on the FTP server I'm communicating with or what. Again, this worked fine when I wrote it and now it doesn't.
How can I determine whether or not an FTP directory exists?
EDIT:
Inspecting the response after calling ReadToEnd(), I see:
BannerMessage="220 Microsoft FTP Service\r\n"
ContentLength=-1
ExitMessage="221 Goodbye.\r\n"
StatusCode=ClosingData
StatusDescription="226 Transfer complete.\r\n"
WelcomeMessage="230 User logged in.\r\n"
UPDATE:
Ultimately, I see that most people have been recommending variations of what I was doing originally. This has lent weight to Hans Passant's suggestion that the issue lies with the server. I am attempting to get them to look at this but they seem a little baffled by the entire discussion. I know they are using a Microsoft server, and I'm skeptical I will be able to get a resolution from them.
If all else fails, I think the answer is to do a listing of the parent directory, which does require some extra work to handle cases such as when the directory in question is the root, and also cases when the parent doesn't exist.
FTP servers reply with a 550 message when you ask for a non existing directory/file. This 550 is translated to an exception in .NET.
Regarding the code you presented I don't see the reason to use a StreamReader in production. A simple modification is the following:
public bool DirectoryExists(string directory)
{
try
{
FtpWebRequest request = GetRequest(directory);
request.Method = WebRequestMethods.Ftp.ListDirectory;
return request.GetResponse() != null;
}
catch
{
return false;
}
}
I kept the exception handling as is (missing) but I suggest you to work on it as there are many and different cases to catch that are not related to ftp responses.
request.GetResponse() will generate an exception if the directory does not exist.
I tried it with the following paths for true returns:
ftp://ftp.mozilla.org/
ftp://ftp.mozilla.org/pub/
ftp://ftp.mozilla.org/pub/data/
ftp://ftp.mozilla.org/pub/data/bloat-reports/
The last one is an existing but empty directory
ftp://ftp.mozilla.org/pub/data/bloat-reports2/
returns false
There is a big but regarding trailing / in paths.
mozilla.org is a zero byte length file under pub directory
ftp://ftp.mozilla.org/pub/mozilla.org returns true
ftp://ftp.mozilla.org/pub/mozilla.org/ returned false and then true ?????
This behavior looks like with what you described. You can easily reproduce it on a browser. I suggest you to do the same with your own paths and check the contents of the directories. In case you do have empty files, remove them or replace their contents with a space, and finally check your code to ensure that you will not create new files or recreate them with zero byte length.
I hope that this helps.
UPDATE
No news, bad news! I assume that nothing of the above helped you. Maybe by going one level up in your path will solve the problem. The idea is to get a list of the parent directory and check it for the child name. If the parent path is valid it should always return non empty string. The code is the following:
static bool DirectoryExists(string directory)
{
try
{
directory = GetRequest(directory);
string
parent = directory.Substring(0, directory.TrimEnd('/').LastIndexOf('/') + 1),
child = directory.Substring(directory.TrimEnd('/').LastIndexOf('/') + 1).TrimEnd('/');
FtpWebRequest request = GetRequest(parent);
request.Method = WebRequestMethods.Ftp.ListDirectory;
using (FtpWebResponse response = request.GetResponse() as FtpWebResponse)
{
if (response == null)
return false;
string data = new StreamReader(response.GetResponseStream(), true).ReadToEnd();
return data.IndexOf(child, StringComparison.InvariantCultureIgnoreCase) >= 0;
}
}
catch
{
return false;
}
}
As you can see there is no need to worry about trailing slashes.
Warning: The code will not cover the case of a subdirectory and a filename with same names under the same path.
This might happen because FTP server software installed on your server.
Otherwise, you may want to take a look at response.StatusCode to see if there is any hints.
http://msdn.microsoft.com/en-us/library/system.net.ftpstatuscode(v=vs.110).aspx
Finally, if all of that doesn't work, try to write a dumb text file on that un-existed directory.
I had similar code that would deploy items via FTP to an IIS 6/Windows 2003 Server. This worked fine until I pointed it at an IIS7/Windows 2008 server. I started seeing the exact same behavior you did. I debugged it and ultimately changed my code to the following. This is a direct snippet from my code. I can include the source from the methods it calls if you like, but I think you understand those operations.
try
{
//Get a recursive FTP Listing
items = GetFtpListing(target, true);
}
catch(WebException e)
{
if (string.Equals(e.Message, "The remote server returned an error: (550) File unavailable (e.g., file not found, no access)."))
{
CreateRemoteDirectory(target);
items = GetFtpListing(target, true);
}
else
{
throw e;
}
}
relevant section of GetFtpListing
List<string> ftpRawList = new List<string>();
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(path);
request.Timeout = 60000;
request.ReadWriteTimeout = 60000;
request.Credentials = this.credentials;
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
{
Stream data = response.GetResponseStream();
using (StreamReader reader = new StreamReader(data))
{
string responseString = reader.ReadToEnd();
ftpRawList.AddRange(responseString.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
}
I use the below to check if a ftp connection credentials are valid. You may use the same to check if directory exists. Returns true if url, username, and password are correct.
URL: you specify your directory path in here.
user: ftp username
password: ftp password
public static bool isValidConnection(string url, string user, string password, ref string errorMessage = "")
{
try {
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Method = WebRequestMethods.Ftp.ListDirectory;
request.Credentials = new NetworkCredential(user, password);
request.KeepAlive = false;
request.UsePassive = true;
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
response.Close();
} catch (WebException ex) {
errorMessage = ex.Message;
return false;
}
return true;
}
One way is to list the directory content as present in many solutions, but in my case i also have to read data content so the server checks the directory. If the directory is not present, an webException is throw that should be interpreted to filter from unexpected errors.
Here is my solution:
bool directoryExists(string path)
{
bool? exists = null;
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(path);
request.Method = WebRequestMethods.Ftp.ListDirectory;
request.Credentials = new NetworkCredential("username", "*****");
FtpWebResponse response = null;
try
{
response = (FtpWebResponse)request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
string str = sr.ReadLine(); //Just needs to read one line to fire check on server
sr.Close();
return true;
}
}
catch (WebException ex)
{
var r = (FtpWebResponse)ex.Response;
if (r.StatusCode ==
FtpStatusCode.ActionNotTakenFileUnavailable)
{
//Does not exist
return false;
}
throw; //if error is not related to directory existence then the exception needs to be treated above.
}
finally
{
if (response != null)
response.Close();
}
return false;
}
The FTP server i am targeting i know to be a windows server but i don't have access to the version information.

FTP download not writing the data in the file

I have written a program that downloads a file from a remote ftp site and saves it to the local c drive then uploads that file to a separate server. The problem now is that when the file gets downloaded, there is no data inside the text file it creates on the local C and I can't figure out why that is. Here is the code I'm using
// Download File
public void download(string remoteFile, string localFile)
{
try
{
// Create an FTP Request
ftpRequest = (FtpWebRequest)FtpWebRequest.Create(downhost + "/" + remoteFile);
// Log in to the FTP Server with the User Name and Password Provided
ftpRequest.Credentials = new NetworkCredential(downuser, downpass);
// When in doubt, use these options
ftpRequest.UseBinary = true;
ftpRequest.UsePassive = true;
ftpRequest.KeepAlive = true;
/* Set HTTP Proxy to Null to avoid The Requested FTP Command Is Not Supported When Using HTTP Proxy error */
ftpRequest.Proxy = null;
// Specify the Type of FTP Request
ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;
// Establish Return Communication with the FTP Server
ftpResponse = (FtpWebResponse)ftpRequest.GetResponse();
// Get the FTP Server's Response Stream
ftpStream = ftpResponse.GetResponseStream();
// Open a File Stream to Write the Downloaded File
FileStream localFileStream = new FileStream(localFile, FileMode.Create);
// Buffer for the Downloaded Data
byte[] byteBuffer = new byte[bufferSize];
int bytesRead = ftpStream.Read(byteBuffer, 0, bufferSize);
// Download the File by Writing the Buffered Data Until the Transfer is Complete
try
{
while (bytesRead > 0)
{
localFileStream.Write(byteBuffer, 0, bytesRead);
bytesRead = ftpStream.Read(byteBuffer, 0, bufferSize);
}
}
catch (Exception ex) { Console.WriteLine(ex.ToString()); }
// Resource Cleanup
localFileStream.Close();
ftpStream.Close();
ftpResponse.Close();
ftpRequest = null;
}
catch (Exception ex) { Console.WriteLine(ex.ToString()); }
return;
}
I used the code found at http://www.codeproject.com/Tips/443588/Simple-Csharp-FTP-Class as the basis to build my program off of and I've done Google searches on how other people have written their ftp download scripts but can't figure out any reason why the data isn't being written.
Any help is appreciated.
Flush the file. Call localFileStream.Flush(); before you close it.
I realise that this is a bit late, but i was having the same issue trying to use that example, i managed to get a FTP download working with an example from here:
http://www.techrepublic.com/blog/howdoi/how-do-i-use-c-to-upload-and-download-files-from-an-ftp-server/165
I'm having issues with the same method. I did find a way to get the system to work but I had to copy the ftpstream in your code to another stream like this: (in vb.net, I know, I'm sorry)
ftpStream.copyTo(MySecondStream)
ftpStream.close()
'do whatever you want with the copy now
Return MySecondStream
Maybe it has something to do with how the initial stream is handled and how long it stays open. I posted my question here: Why do I have to copy an FTP stream to another variable to return it to the calling method?
I had the same problem on an upload. 0 bytes written. Code was working on one FTP server, but not on another. Just had to do the following:
client.FtpStream.Flush();
client.FtpStream.Close();

Checking if multiple files exists on FTP server (C#)

I need a way to check if multiple files exist in a given FTP path. I will have a list, which lists all the file names to check, and I will need to check if all these exist on the server, and return errors for ones that don't. How easy is this?
Thanks
The safest approach would be to retrieve a list of files/directories per directory and parse that list.
// Get the object used to communicate with the server.
var request = WebRequest.Create(url);
request.Credentials = new NetworkCredential(username, password);
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
try
{
using(var response = request.GetResponse())
{
using(var stream = response.GetResponseStream())
{
using(var reader = new StreamReader(stream))
{
while(reader.Peek() >= 0)
{
var line = reader.ReadLine();
// check if this is a file or directory, filter list etc..
}
}
}
}
}
catch
{
}
Another- easier - option would be to try to retrieve the files DateTimestamp and catch the exception if the file doesn't exist. You should check the exception since one could be thrown for a different reason.
var request = WebRequest.Create(url);
request.Credentials = new NetworkCredential(username, password);
request.Method = WebRequestMethods.Ftp.GetDateTimestamp;
try
{
using(var response = (FtpWebResponse)request.GetResponse())
{
// file exists
}
}
catch(WebException e)
{
// file probably doesn't exits
}
Well, if you have access to the server, you could write your script there and then just request that script and thus only have to make one server request. Otherwise, you'll just need to check each file, one by one.

How to check if a file exists on a server using c# and the WebClient class

In my application I use the WebClient class to download files from a Webserver by simply calling the DownloadFile method. Now I need to check whether a certain file exists prior to downloading it (or in case I just want to make sure that it exists). I've got two questions with that:
What is the best way to check whether a file exists on a server without transfering to much data across the wire? (It's quite a huge number of files I need to check)
Is there a way to get the size of a given remote file without downloading it?
Thanks in advance!
WebClient is fairly limited; if you switch to using WebRequest, then you gain the ability to send an HTTP HEAD request. When you issue the request, you should either get an error (if the file is missing), or a WebResponse with a valid ContentLength property.
Edit: Example code:
WebRequest request = WebRequest.Create(new Uri("http://www.example.com/"));
request.Method = "HEAD";
using(WebResponse response = request.GetResponse()) {
Console.WriteLine("{0} {1}", response.ContentLength, response.ContentType);
}
When you request file using the WebClient Class, the 404 Error (File Not Found) will lead to an exception. Best way is to handle that exception and use a flag which can be set to see if the file exists or not.
The example code goes as follows:
System.Net.HttpWebRequest request = null;
System.Net.HttpWebResponse response = null;
request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create("www.example.com/somepath");
request.Timeout = 30000;
try
{
response = (System.Net.HttpWebResponse)request.GetResponse();
flag = 1;
}
catch
{
flag = -1;
}
if (flag==1)
{
Console.WriteLine("File Found!!!");
}
else
{
Console.WriteLine("File Not Found!!!");
}
You can put your code in respective if blocks.
Hope it helps!
What is the best way to check whether a file exists on a server
without transfering to much data across the wire?
You can test with WebClient.OpenRead to open the file stream without reading all the file bytes:
using (var client = new WebClient())
{
Stream stream = client.OpenRead(url);
// ^ throws System.Net.WebException: 'Could not find file...' if file is not present
stream.Close();
}
This will indicate if the file exists at the remote location or not.
To fully read the file stream, you would do:
using (var client = new WebClient())
{
Stream stream = client.OpenRead(url);
StreamReader sr = new StreamReader(stream);
Console.WriteLine(sr.ReadToEnd());
stream.Close();
}
In case anyone stuck with ssl certificate issue
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback
(
delegate { return true; }
);
WebRequest request = WebRequest.Create(new Uri("http://.com/flower.zip"));
request.Method = "HEAD";
using (WebResponse response = request.GetResponse())
{
Console.WriteLine("{0} {1}", response.ContentLength, response.ContentType);
}

Categories