SharePoint流文件进行预览
我希望将SharePoint 2003文档库中的文件流式传输到浏览器。基本上,想法是打开文件作为流,然后将文件流"写入"到响应中,并指定内容类型和内容处置头。内容配置用于保留文件名,内容类型当然是为了使浏览器了解要打开哪个应用程序以查看文件。
在开发环境和UAT环境中,这一切都很好。但是,在生产环境中,事情并非总是能按预期运行,但是只能使用IE6 / IE7. FF在所有环境中都适用。
请注意,在生产环境中,启用并通常使用SSL。 (当在生产环境中未使用SSL时,文件流将按预期命名,并进行适当的分配。)
这是一个代码片段:
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();
就像我说的那样,此代码段在dev机器和UAT环境中都可以正常工作。将打开一个对话框,询问我们是否保存,查看或者取消Testme.doc。但是在生产中仅使用SSL时,IE 6和IE7不会使用附件的名称。而是使用发送流的页面的名称testheader.apx,然后引发错误。
IE确实提供了高级设置"不要将加密的页面保存到磁盘"。
我怀疑这是问题的一部分,服务器告诉浏览器不要缓存文件,而IE启用了"不将加密页面保存到磁盘"功能。
是的,我知道,对于较大的文件,上面的代码片段将对内存造成很大的拖累,这种实现方式将是有问题的。因此,真正的最终解决方案不会将整个文件打开为一个字节数组,而是以流的形式打开文件,然后以一口大小的块(例如大约10K的大小)将文件发送给客户端。
其他人也有类似的经验通过ssl"流式处理"二进制文件吗?有什么建议或者建议吗?
解决方案
回答
可能这确实很简单,不管我们信不信,我今天编写的代码都完全相同,我认为问题可能是内容处置不告诉浏览器其附件,因此可以保存。
Response.AddHeader("Content-Disposition", "attachment;filename=myfile.doc");
失败,我在下面包含了我的代码,因为我知道它可以在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); } }
回答
好的,我解决了这个问题,这里有几个因素在起作用。
首先,此支持Microsoft文章是有益的:
Internet Explorer无法从SSL网站打开Office文档。
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.
其次,页面处理过程中较早的某个事件导致" no-cache"标头被写入。因此,需要添加Response.ClearHeaders,这清除了no-cache头,并且页面的输出需要允许缓存。
第三,出于很好的考虑,它也添加在Response.End上,以便在请求生存期中没有其他处理尝试清除我设置的头并重新添加无缓存头。
第四,发现在IIS中启用了内容过期。我已经在网站级别启用了该功能,但是由于这个aspx页面将用作下载文件的网关,因此我在下载页面级别将其禁用。
因此,这是有效的代码段(还有一些其他较小的更改,我认为这是无关紧要的):
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();
也请记住,这仅用于说明。实际的生产代码将包括异常处理,并且可能一次(也许10K)一次读取文件。
Mauro,感谢我们也捕获了代码中缺少的细节。