I'm trying to get my head around how to download a file from a secure FTP server from my AngularJS application using REST.
Thing is, that for security reasons, I can't just append an iframe or set window.location to ftp://myip:81/myfolder/myfile.pdf, so I have to find a way to trigger the download without this. My initial thought was to create a Generic handler which takes the filename and the folder name as parameters and then serve the file to the user through the context.Response somehow.
What I have so far is this:
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(path);
request.UsePassive = false;
request.Credentials = new NetworkCredential(ftpHelper.Username, ftpHelper.Password);
request.Method = WebRequestMethods.File.DownloadFile;
using (var response = (FtpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
// stuck here ...
}
}
request.Abort();
I've got a feeling that this isn't possible, though ;-) Can anyone confirm/disprove? And if it can be done, I'd love a small example/hint on this :-)
Thanks!
Related
I am going through the example exam questions for Microsoft exam 70-483 "Programming in C#".
There is one question the answer to which I don't understand and couldn't find anything about the topic on the Internet.
The question is:
You are implementing a method named ProcessFile that retrieves data files from web servers and FTP servers. The ProcessFile() method has the following method signature:
Public void ProcessFile(Guid dataField, string dataFileUri)
Each time the ProcessFile() method is called, it must retrieve a unique data file and then save the data file to disk.
You need to complete the implementation of the
ProcessFile() method. Which code segment should you use?
FileWebRequest request = FileWebRequest.Create(dataFileUri) as FileWebRequest;
using (FileWebResponse response = request.GetResponse() as FileWebResponse)
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
using (StreamWriter writer = new StreamWriter(dataFieldId + ".dat"))
{
writer.Write(reader.ReadToEnd());
}
-or-
WebRequest request = WebRequest.Create(dataFileUri);
using (WebResponse response = request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
using (StreamWriter writer = new StreamWriter(dataFieldId + ".dat"))
{
writer.Write(reader.ReadToEnd());
}
According to the question-making people, the latter snippet, using "WebRequest" is the correct one. But I cannot figure out why the "FileWebRequest" one isn't.
Keep in mind the questions I am doing have been wrong a lot in the past, so maybe this isn't correct, either?
Some hours ago I also have met this question. Early I have not work with this, but by searching info in Google I concluded followings:
the main words in question is from web servers and FTP servers,
that means that dataFileUri may be like http://mywebserver or ftp://myftpserver
when you try to get file from ftp server, for example:
//from answer
var request1 = WebRequest.Create("ftp://myftpserver");
//from answer
var request2 = FileWebRequest.Create("ftp://myftpserver") as FileWebRequest;
var request3 = WebRequest.Create("ftp://myftpserver") as FtpWebRequest;
request1, request3 will have request value with type SystemNet.FtpWebRequest. request2 will be null.
The similar behavior will be when you try to use http://mywebserver: request2, request3 will be null.
when you use WebRequest the type of the request will be automatically detected by transfer protocol
So you don't need to think about whether file stored on web server/file server/ftp server
I'm trying to make a simple method to download a file from an FTP using FtpWebRequest with the method WebRequestMethods.Ftp.DownloadFile. The problem is that I wan't to display the progress of downloading and thus need to know the file size ahead to be able to calculate the percentage transfered. But when I call GetResponse in FtpWebRequest the ContentLength member is -1.
OK - so I get the size of the file in advance using the method WebRequestMethods.Ftp.GetFileSize. No problem. Then after getting the size I download the file.
This is where the problem in question appears...
After getting the size I try to reuse the FtpWebRequest and resets the method to WebRequestMethods.Ftp.DownloadFile. This causes an System.InvalidOperationException saying something like "Can't perform this action after sending the request." (may not be the exact formulation - translated from the one I get in Swedish).
I've found elsewhere that as long as I set the KeepAlive property to true, it doesn't matter, the connection is kept active. This is what I don't understand... The only object I've created is my FtpWebRequest object. And if I create another one, how can it know what connection to use? And what credentials?
Pseudo code:
Create FtpWebRequest
Set Method property to GetFileSize
Set KeepAlive property to true
Set Credentials property to new NetworkCredential(...)
Get FtpWebResponse from the request
Read and store ContentLength
Now I got the file size. So it's time to download the file. Setting Method now causes the exception mentioned above. So do I create a new FtpWebRequest? Or is there anyway to reset the request to be reused? (Closing the response made no difference.)
I don't understand how to move forward without re-creating the object. I could do that, but it just doesn't feel right. So i'm posting here in hope to find the correct way of doing this.
Here's the (non working) code (Inputs are sURI, sDiskName, sUser and sPwd.) :
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(sURI);
request.Method = WebRequestMethods.Ftp.GetFileSize;
request.Credentials = new NetworkCredential(sUser, sPwd);
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;
FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
int contLen = (int)resp.ContentLength;
resp.Close();
request.Method = WebRequestMethods.Ftp.DownloadFile;
resp = (FtpWebResponse)request.GetResponse();
Stream inStr = resp.GetResponseStream();
byte[] buff = new byte[16384];
sDiskName = Environment.ExpandEnvironmentVariables(sDiskName);
FileStream file = File.Create(sDiskName);
int readBytesCount;
int readTotal=0;
while ((readBytesCount = inStr.Read(buff, 0, buff.Length)) > 0)
{
readTotal += readBytesCount;
toolStripProgressBar1.Value = 100*readTotal/contLen;
Application.DoEvents();
file.Write(buff, 0, readBytesCount);
}
file.Close();
I hope someone can explain how this is supposed to work. Thanks in advance.
I don't think this will be answered so I'm "closing it" by telling you how I solved it.
Well, I didn't really solve it. I did however test the download by recreating the FtpWebRequest and noticed that on the FTP server it behaved as I wanted i.e. only one log on and then sequentially executing my requests.
This is how the code getting the file size and starting the download ended up:
// Start by fetching the file size
FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(sURI);
request.Method = WebRequestMethods.Ftp.GetFileSize;
NetworkCredential nc = new NetworkCredential(sUser, sPwd);
request.Credentials = nc;
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;
// Get the result (size)
FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
Int64 contLen = resp.ContentLength;
// and now download the file
request = (FtpWebRequest)FtpWebRequest.Create(sURI);
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.Credentials = nc;
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;
resp = (FtpWebResponse)request.GetResponse();
So no answer on if it's possible to reset the FtpWebRequest for re-use. But at least I know there's no redundant information being transferred.
Thanks to everybody who took an interest and spent time thinking of an answer.
FtpWebRequest can be used for only 1 request, like getting the file size or to download the file, but not both. You have to create 2 FtpWebRequests. Behind the scene, FtpWebRequest notices that it is the same URL and credentials and will reuse the same ftp connection without closing it, as long IsKeepAlieve is true, which is the default setting.
This is a sad example of bad design by Microsoft. Instead of letting us explicitly open and close a connection, they want to do it automatically for us and confuse everyone.
You're going to probably want to use the Async method. Here's the link to the MSDN doc.
http://msdn.microsoft.com/en-us/library/system.net.ftpwebrequest.aspx
GetResponseAsync()
That will keep your application from locking up too, so you won't have to use
Application.DoEvents();
You could also look at possibly using an alternative ftp library. imo the FtpWebRequest is not exactly the best ftp class. A quick search turned up this library. Ftp isn't stateless like HTTP. I prefer libraries that let you create a client, open a connect, and keep the connection alive.
http://sanity-free.org/dist/NullFX.Net-binary.zip
Here's the code exacmple I found
FtpClient client =
new FtpClient(
new IPEndPoint( IPAddress.Loopback, 21 ),
new NetworkCredential( "test", "testing#localdomain" )
);
client.Connect();
client.Download("testfile.zip", #"C:\downloads\testfile.zip");
The source is there too, so you would be able to possibly attach some events to the read process for your download progress tracking.
I need some guidance. I need to develop a customizable FTP in C# that should be configured using App.Config file. Also, the FTP should push the data to any server from any client again depends on config file.
I will appreciate if someone can guide, if there is any API or any other useful suggestion, or move me in the right direction.
You can use FtpWebRequest; however, this is fairly low level. There is a higher-level class WebClient, which requires much less code for many scenarios; however, it doesn't support FTP/SSL by default. Fortunately, you can make WebClient work with FTP/SSL by registering your own prefix:
private void RegisterFtps()
{
WebRequest.RegisterPrefix("ftps", new FtpsWebRequestCreator());
}
private sealed class FtpsWebRequestCreator : IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
FtpWebRequest webRequest = (FtpWebRequest)WebRequest.Create(uri.AbsoluteUri.Remove(3, 1)); // Removes the "s" in "ftps://".
webRequest.EnableSsl = true;
return webRequest;
}
}
Once you do this, you can use WebClient almost like normal, except that your URIs start with "ftps://" instead of "ftp://". The one caveat is that you have to specify the method parameter, since there won't be a default one. E.g.
using (var webClient = new WebClient()) {
// Note here that the second parameter can't be null.
webClient.UploadFileAsync(uploadUri, WebRequestMethods.Ftp.UploadFile, fileName, state);
}
The accepted answer works, indeed. But I find it too cumbersome to register a prefix, implement an interface, and all that stuff, particularly, if you need it just for one transfer.
FtpWebRequest is not that difficult to use. So I believe that for one-time use, it's better to go this way:
FtpWebRequest request =
(FtpWebRequest)WebRequest.Create("ftp://ftp.example.com/remote/path/file.zip");
request.Credentials = new NetworkCredential("username", "password");
request.EnableSsl = true;
request.Method = WebRequestMethods.Ftp.UploadFile;
using (Stream fileStream = File.OpenRead(#"C:\local\path\file.zip"))
using (Stream ftpStream = request.GetRequestStream())
{
fileStream.CopyTo(ftpStream);
}
The key is the EnableSsl property.
For other scenarios, see:
Upload and download a binary file to/from FTP server in C#/.NET
We use edtFTPnet with good results.
I have a file at a URI that I would like to read using StreamReader. Obviously, this causes a problem since File.OpenText does not support URI paths. The file is a txt file with a bunch of html in it. I have multiple web pages that use this same piece of html, so I have put it in a txt file, and am reading it into the page when the page loads (I can get it to work when I put the file on the file system, but need to put it in a document repository online so that a business user can get to it). I am trying to avoid using an iframe. Is there a way to use StreamReader with URI formats? If not, what other options are there using C# to read in the txt file of html? If this is not optimal, can someone suggest a better approach?
Is there a specific requirement to use StreamReader? Unless there is, you can use the WebClient class:
var webClient = new WebClient();
string readHtml = webClient.DownloadString("your_file_path_url");
You could try using the HttpWebRequestClass, or WebClient. Here's the slightly complicated web request example. It's advantage over WebClient is it gives you more control over how the request is made:
HttpWebRequest httpRequest = (HttpWebRequest) WebRequest.Create(lcUrl);
httpRequest.Timeout = 10000; // 10 secs
httpRequest.UserAgent = "Code Sample Web Client";
HttpWebResponse webResponse = (HttpWebResponse) httpRequest.GetResponse();
StreamReader responseStream = new StreamReader(webResponse.GetResponseStream());
string content = responseStream.ReadToEnd();
If you are behind a proxy don't forget to set your credentials:
WebRequest request=WebRequest.Create(url);
request.Timeout=30*60*1000;
request.UseDefaultCredentials=true;
request.Proxy.Credentials=request.Credentials;
WebResponse response=(WebResponse)request.GetResponse();
using (Stream s=response.GetResponseStream())
...
How can I check a File exits in a web location in ASP.Net(in a different web application, but same web server), currently I doing like this. Is there any better way of doing this?
using (WebClient client = new WebClient())
{
try
{
Stream stream = client.OpenRead("http://localhost/images/myimage.jpg");
if (stream != null)
{
//exists
}
}
catch
{
//Not exists
}
}
Remember that you are never going to get a 100% definitive response on the existence of a file, but the way I do it would be pretty similar to yours...
bool remoteFileExists(string addressOfFile)
{
try
{
HttpWebRequest request = WebRequest.Create(addressOfFile) as HttpWebRequest;
request.Method = "HEAD";
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
var response = request.GetResponse() as HttpWebResponse;
return (response.StatusCode == HttpStatusCode.OK);
}
catch(WebException wex)
{
return false;
}
}
EDIT :: looking at the edit by Anton Gogolev above (How can one check to see if a remote file exists using C#) I should have cast the response to a HttpWebResponse object and checked the status code. Edited the code to reflect that
If a file is accessible via HTTP, you can issue a HTTP HEAD requrest for that particular URL using HttpWebRequest. If HttpWebResponse.StatusCode will be 200, than file is there.
EDIT: See this on why GetResponse throws stupid exceptions when it actually should not do that.
You can use Server.MapPath to get the directory and then check if file exist using IO standard methods like File.Exists
The 404 or Not Found error message is a HTTP standard response code indicating that the client was able to communicate with the server but the server could not find what was requested. A 404 error indicates that the requested resource may be available in the future.
You can use a HEAD request (HttpWebRequest.Method = "HEAD")