2

我希望将 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();

就像我说的,这个代码片段在开发机器和 UAT 环境中运行良好。将打开一个对话框并要求保存、查看或取消 Testme.doc。但仅在生产中使用 SSL 时,IE 6 和 IE7 不使用附件名称。相反,它使用发送流的页面名称 testheader.apx,然后引发错误。

IE 确实提供了高级设置“不要将加密的页面保存到磁盘”。

我怀疑这是问题的一部分,服务器告诉浏览器不要缓存文件,而 IE 启用了“不要将加密的页面保存到磁盘”。

是的,我知道对于较大的文件,上面的代码片段将是内存的主要拖累,并且这种实现将是有问题的。因此,真正的最终解决方案不会将整个文件打开为单个字节数组,而是将文件作为流打开,然后将文件以一口大小的块(例如大约 10K 大小)发送到客户端。

其他任何人都有类似的通过 ssl“流式传输”二进制文件的经验吗?有什么建议或建议吗?

4

2 回答 2

4

这可能非常简单,信不信由你我今天编写的代码完全相同,我认为问题可能是内容配置没有告诉浏览器它是一个附件,因此可以保存。


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);
  }
}

于 2008-09-09T22:49:19.647 回答
3

好的,我解决了这个问题,这里有几个因素在起作用。

首先,此支持 Microsoft 文章是有益的: Internet Explorer 无法从 SSL 网站打开 Office 文档

为了让 Internet Explorer 在 Office(或任何进程外的 ActiveX 文档服务器)中打开文档,Internet Explorer 必须将文件保存到本地缓存目录并要求关联的应用程序使用 IPersistFile::Load 加载文件. 如果文件未存储到磁盘,则此操作失败。

当 Internet Explorer 通过 SSL 与安全网站通信时,Internet Explorer 会强制执行任何无缓存请求。如果存在一个或多个标头,Internet Explorer 不会缓存该文件。因此,Office 无法打开该文件。

其次,页面处理的早期部分导致“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,感谢您也发现了代码中缺少的细节。

于 2008-09-16T22:30:28.403 回答