SharePoint stream file for preview - asp.net

I am looking to stream a file housed in a SharePoint 2003 document library down to the browser. Basically the idea is to open the file as a stream and then to "write" the file stream to the reponse, specifying the content type and content disposition headers. Content disposition is used to preserve the file name, content type of course to clue the browser about what app to open to view the file.
This works all good and fine in a development environment and UAT environment. However, in the production environment, things do not always work as expected,however only with IE6/IE7. FF works great in all environments.
Note that in the production environment SSL is enabled and generally used. (When SSL is not used in the production environment, file streams, is named as expected, and properly dislays.)
Here is a code snippet:
System.IO.FileStream fs = new System.IO.FileStream(Server.MapPath(".") + "\\" + "test.doc", System.IO.FileMode.Open);
long byteNum = fs.Length;
byte[] pdfBytes = new byte[byteNum];
fs.Read(pdfBytes, 0, (int)byteNum);
Response.AppendHeader("Content-disposition", "filename=Testme.doc");
Response.CacheControl = "no-cache";
Response.ContentType = "application/msword; charset=utf-8";
Response.Expires = -1;
Response.OutputStream.Write(pdfBytes, 0, pdfBytes.Length);
Response.Flush();
Response.Close();
fs.Close();
Like I said, this code snippet works fine on the dev machine and in the UAT environment. A dialog box opens and asks to save, view or cancel Testme.doc. But in production onnly when using SSL, IE 6 & IE7 don't use the name of the attachment. Instead it uses the name of the page that is sending the stream, testheader.apx and then an error is thrown.
IE does provide an advanced setting "Do not save encrypted pages to disk".
I suspect this is part of the problem, the server tells the browser not to cache the file, while IE has the "Do not save encrypted pages to disk" enabled.
Yes I am aware that for larger files, the code snippet above will be a major drag on memory and this implimentation will be problematic. So the real final solution will not open the entire file into a single byte array, but rather will open the file as a stream, and then send the file down to the client in bite size chunks (e.g. perhaps roughly 10K in size).
Anyone else have similar experience "streaming" binary files over ssl? Any suggestions or recommendations?

It might be something really simple, believe it or not I coded exactly the same thing today, i think the issue might be that the content disposition doesnt tell the browser its an attachment and therefore able to be saved.
Response.AddHeader("Content-Disposition", "attachment;filename=myfile.doc");
failing that i've included my code below as I know that works over https://
private void ReadFile(string URL)
{
try
{
string uristring = URL;
WebRequest myReq = WebRequest.Create(uristring);
NetworkCredential netCredential = new NetworkCredential(ConfigurationManager.AppSettings["Username"].ToString(),
ConfigurationManager.AppSettings["Password"].ToString(),
ConfigurationManager.AppSettings["Domain"].ToString());
myReq.Credentials = netCredential;
StringBuilder strSource = new StringBuilder("");
//get the stream of data
string contentType = "";
MemoryStream ms;
// Send a request to download the pdf document and then get the response
using (HttpWebResponse response = (HttpWebResponse)myReq.GetResponse())
{
contentType = response.ContentType;
// Get the stream from the server
using (Stream stream = response.GetResponseStream())
{
// Use the ReadFully method from the link above:
byte[] data = ReadFully(stream, response.ContentLength);
// Return the memory stream.
ms = new MemoryStream(data);
}
}
Response.Clear();
Response.ContentType = contentType;
Response.AddHeader("Content-Disposition", "attachment;");
// Write the memory stream containing the pdf file directly to the Response object that gets sent to the client
ms.WriteTo(Response.OutputStream);
}
catch (Exception ex)
{
throw new Exception("Error in ReadFile", ex);
}
}

Ok, I resolved the problem, several factors at play here.
Firstly this support Microsoft article was beneficial:
Internet Explorer is unable to open Office documents from an SSL Web site.
In order for Internet Explorer to open documents in Office (or any out-of-process, ActiveX document server), Internet Explorer must save the file to the local cache directory and ask the associated application to load the file by using IPersistFile::Load. If the file is not stored to disk, this operation fails.
When Internet Explorer communicates with a secure Web site through SSL, Internet Explorer enforces any no-cache request. If the header or headers are present, Internet Explorer does not cache the file. Consequently, Office cannot open the file.
Secondly, something earlier in the page processing was causing the "no-cache" header to get written. So Response.ClearHeaders needed to be added, this cleared out the no-cache header, and the output of the page needs to allow caching.
Thirdly for good measure, also added on Response.End, so that no other processing futher on in the request lifetime attempts to clear the headers I've set and re-add the no-cache header.
Fourthly, discovered that content expiration had been enabled in IIS. I've left it enabled at the web site level, but since this one aspx page will serve as a gateway for downloading the files, I disabled it at the download page level.
So here is the code snippet that works (there are a couple other minor changes which I believe are inconsequential):
System.IO.FileStream fs = new System.IO.FileStream(Server.MapPath(".") + "\\" + "TestMe.doc", System.IO.FileMode.Open);
long byteNum = fs.Length;
byte[] fileBytes = new byte[byteNum];
fs.Read(fileBytes, 0, (int)byteNum);
Response.ClearContent();
Response.ClearHeaders();
Response.AppendHeader("Content-disposition", "attachment; filename=Testme.doc");
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.ContentType = "application/octet-stream";
Response.OutputStream.Write(fileBytes, 0, fileBytes.Length);
Response.Flush();
Response.Close();
fs.Close();
Response.End();
Keep in mind too, this is just for illustration. The real production code will include exception handling and likely read the file a chunk at a time (perhaps 10K).
Mauro, thanks for catching a detail that was missing from the code as well.

Related

ASP.NET 4.0, Browser binary render of PDF on IIS6

I have an ASP.Net 4.0 web application that renders on client's browser a PDF created on the fly using CrystalReports inside an iFrame via binary write. The webapp works well on my dev station (Visual Studio 2012 Web Developer), but when I tested to our production server (Win2003, IIS6) the part where the user clicks on a button to stream the PDF it just shows a part of the document (the page header logo only, the rest of the PDF is blank). I made a test: instead of streaming the binary data to the client's browser I saved to a local directory on the server's virtual path, I could access the PDF which integrity was OK, all data appeared on the saved doc. I've been Googling for 3 days, tried all kind of code snippets, IIS configurations (log files doesn't show any error), server permissions, HTML headers, web.config tags, etc., with no luck. I suspect that it has something to be with IIS, but I can't find the solution. Here is the code that renders the PDF on client's browser:
//reportObject is a CrystalReport document object (.rpt)
Dim s As System.IO.MemoryStream = reportObj.ExportToStream(ExportFormatType.PortableDocFormat)
s.Seek(0, SeekOrigin.Begin)
With HttpContext.Current.Response
.ClearContent()
.ClearHeaders()
.ContentType = "application/pdf"
.AddHeader("Content-Disposition", "inline; filename=" & fileName)
.BinaryWrite(s.ToArray)
.Flush()
.End()
End With
I also tried these, all worked on my dev machine, but not on the server:
With HttpContext.Current.Response
.Clear()
.ClearHeaders()
.Buffer = True
.AddHeader("Content-Disposition", "inline; filename=doc.pdf")
.AddHeader("Content-Length", Int32.Parse(s.Length))
.AddHeader("Content-transfer-encoding", "8bit")
.AddHeader("Cache-Control", "private")
.AddHeader("Pragma", "cache")
.AddHeader("Expires", "0")
.AddHeader("X-UA-Compatible", "IE=EmulateIE7")
.ContentType = "application/pdf"
.BinaryWrite(s.ToArray)
.Flush()
.Close()
HttpContext.Current.ApplicationInstance.CompleteRequest()
End With
And also these lines:
.AppendHeader("Accept-Ranges", "none")
.OutputStream.Write(s.ToArray(), 0, Convert.ToInt32(s.Length))
Nothing seems to help.
I had to implement another approach described on this thread:
Response.TransmitFile and delete it after transmission
I hope it helps someone.

Viewing file using asp.net

In our application, we allow user to upload documents which can be PDF, Doc, XLS, TXT. Uploaded documents will be saved on web server. We need to display link for each document user uploaded and when user click on that link, it should open relevant document. it is expected to have required software to open relevant documents.
To upload document, we use saveAs method of FileUpload control and it works absolutely fine.
Now, how to view it?
I believe, i need to copy/download file to local user machine and need to open it using Process.Start.
For that i need to find user local temp directory. if i put path.GetTempPath(), it gives me web server directory and copy file there.
File.Copy(
sPath + dataReader["url"].ToString(),
Path.GetTempPath() + dataReader["url"].ToString(),
true);
Please advise.
You can't write to the user's drive from the webserver.
What you can do is just provide a link that will download the file to the client.
Set the Content-Disposition header to "attachment" to have a "save as" dialog come up, or to "inline" to let it display in the browser using the registered program from the client.
You can create a LinkButton with a server side handler that contains code like this:
byte[] data = ...; // get the data from database or whatever
Response.Clear(); // no contents of the aspx file needed
Response.CacheControl = "private";
Response.ContentType = "application/pdf"; // or whatever the mimetype of your file is
Response.AppendHeader("Content-Disposition", "attachment;filename=statistic.pdf");
Response.AppendHeader("Content-Length", data.Length.ToString(CultureInfo.InvariantCulture));
Response.BinaryWrite(data);
Response.End(); // no further processing of the page needed
Can you not just put a link on the page pointing to the directory that the files are in?
e.g.
<a href=downloadedfiles/filename.pdf> click here </a>
Once you've provided the link, your job is done. Mostly. The client's browser will handle loading the file when the link is clicked, if it can handle the file type based on the file extension.
I prefer to use a http handler for referencing file links on a web page. This will be important on the day when you need to implement security for uploaded file access; otherwise, any user could access any file.
You don't have to download the file to user machine.
// For pdf documents
Response.Clear();
string filePath = "File path on the web server";
Response.ContentType = "application/pdf"; // for pdf
Response.WriteFile(filePath);
// For word documents
Response.Clear();
string filePath = "File path on the web server";
Response.ContentType = "application/msword";
Response.WriteFile(filePath);
// similarly other file types

Asp.net sending a dotx(word template) to client side

In my program, I need to send a dotx to client side. Now I process it as:
generate the temp dotx in server side which contains some data from database.
send it to client side. The code is:
Response.Clear();
Response.Buffer = true;
Response.Charset = "unicode";
Response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
Response.AddHeader("content-disposition", "attachment; filename=**.dotx");
Response.AddHeader("Content-Length", dataWord.Length.ToString());
Response.ContentEncoding = System.Text.Encoding.Unicode;
Response.BinaryWrite(dataWord);
Response.Flush();
Response.Close();
dataWord is the dotx.
delete the temp dotx.
This process should be correct. But sometimes I only get a blank document in client side. I am sure the dotx generated in server side is not blank. What may cause that problem? Does error happen during sending data to client-side with Response, or is my code not good?
I have found doing numerous different versions of this that writing an actual file to the server and then sending via Response.TransmitFile to be far far more robust.
By doing so you also have an extra debug step you can use for now by checking the files manually on the server to see if they are blank and possibly spotting other issues.
Think you must have a valid filename and not **.dotx

File download fails for files over 64MB

I have a website which allows secure (ssl) file uploads and download. The site runs on a Window 2003 server with IIS 6.0; asp.net 2.
When using this code:
protected void StartDownLoad(string filename)
{
Response.Clear();
if(filename.EndsWith("zip"))
Response.ContentType = "application/zip";
else
Response.ContentType = "application/msword";
string path = "C:\\Inetpub\\sites\\testsite\\secureDocs\\" + filename;
Response.WriteFile(path);
string headDesc = "inline;filename=" + filename;
Response.AddHeader("Content-Disposition", headDesc);
Response.End();
}
In my tests a 62MB file downloads without any problem -- a 65MB appear to start the download and then immediately stop. The http error logs have four entries each showing "Connection_Dropped". If I remove permissions from the folder and directly access the file through an https url I am able to download files that exceed 65MB so it doesn't seem like it's an IIS issue. Is there an asp.net setting that restricts the response write? Is it an IIS issue? Has anyone run into this before? Any solutions?
You can try using
Response.TransmitFile(path)
instead of
Response.WriteFile(path)
TransmitFile() doesn't buffer the file.
Bye.

When downloading a file using FileStream, why does page error message refers to aspx page name, not file name?

After building a filepath (path, below) in a string (I am aware of Path in System.IO, but am using someone else's code and do not have the opportunity to refactor it to use Path). I am using a FileStream to deliver the file to the user (see below):
FileStream myStream = new FileStream(path, FileMode.Open, FileAccess.Read);
long fileSize = myStream.Length;
byte[] Buffer = new byte[(int)fileSize + 1];
myStream.Read(Buffer, 0, (int)myStream.Length);
myStream.Close();
Response.ContentType = "application/csv";
Response.AddHeader("content-disposition", "attachment; filename=" + filename);
Response.BinaryWrite(Buffer);
Response.Flush();
Response.End();
I have seen from: ASP.NET How To Stream File To User reasons to avoid use of Response.End() and Response.Close().
I have also seen several articles about different ways to transmit files and have diagnosed and found a solution to the problem (https and http headers) with a colleague.
However, the error message that was being displayed was not about access to the file at path, but the aspx file.
Edit: Error message is:
Internet Explorer cannot download MyPage.aspx from server.domain.tld
Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found. Please try again later.
(page name and address anonymised)
Why is this? Is it due to the contents of the file coming from the HTTP response .Flush() method rather than a file being accessed at its address?
Even though you are sending a file, it is the "page" that contains the header information that describes the file you are sending. The browser still has to download that page, then sees the "attachment; filename=" and gives you the file instead.
So if there is an error, it will be page that is shown as the problem. It's a bit like getting a corrupted email with an attachment, you seen the problem in the email not the attachment itself.
Don't call Response.End();

Resources