从URL下载/流文件-asp.net


72

我需要流式传输文件,这将导致在浏览器中保存为提示。问题是,该文件所在的目录已虚拟映射,因此我无法使用Server.MapPath确定其实际位置。该目录与网站不在同一位置(甚至不在实时服务器上)。

我想要以下内容,但这将允许我传递Web URL,而不是服务器文件路径。

我可能最终不得不从config基本路径构建我的文件路径,然后追加到路径的其余部分,但是希望我可以用这种方式代替。

var filePath = Server.MapPath(DOCUMENT_PATH);

if (!File.Exists(filePath))
    return;

var fileInfo = new System.IO.FileInfo(filePath);
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", String.Format("attachment;filename=\"{0}\"", filePath));
Response.AddHeader("Content-Length", fileInfo.Length.ToString());
Response.WriteFile(filePath);
Response.End();

您能否详细说明“虚拟映射”的含义?URL可访问的虚拟IIS文件夹?
EventHorizo​​n 2011年

它使用的是VPP路径,这是EpiServer CMS的概念。我们设置虚拟路径名称(即“ / documents /”),然后指定该路径也应映射的物理路径(即“ // servername / documents”)。然后,系统在运行时创建对目录的引用。您可以通过Web URL浏览到文件而不会出现问题
mp3duck 2011年

文件名确实可以通过URL访问。我需要使用此URL来流传输文件,而不是服务器路径,因为我无法从URL来确定它(使用MapPath)
mp3duck 2011年

如果您知道URL,并且文件扩展名在浏览器中打开了另存为提示(或者这是您的问题之一?),也许您可​​以将请求重定向到要下载的文件?否则,user97970的建议似乎是可行的方法。
EventHorizo​​n 2011年

Answers:


108

您可以使用HttpWebRequest来获取文件并将其流回客户端。这使您可以获取带有URL的文件。我发现的一个示例(但不记得该在哪里归功)是

    //Create a stream for the file
    Stream stream = null;

    //This controls how many bytes to read at a time and send to the client
    int bytesToRead = 10000;

    // Buffer to read bytes in chunk size specified above
    byte[] buffer = new Byte[bytesToRead];

    // The number of bytes read
    try
    {
      //Create a WebRequest to get the file
      HttpWebRequest fileReq = (HttpWebRequest) HttpWebRequest.Create(url);

      //Create a response for this request
      HttpWebResponse fileResp = (HttpWebResponse) fileReq.GetResponse();

      if (fileReq.ContentLength > 0)
        fileResp.ContentLength = fileReq.ContentLength;

        //Get the Stream returned from the response
        stream = fileResp.GetResponseStream();

        // prepare the response to the client. resp is the client Response
        var resp = HttpContext.Current.Response;

        //Indicate the type of data being sent
        resp.ContentType = "application/octet-stream";

        //Name the file 
        resp.AddHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        resp.AddHeader("Content-Length", fileResp.ContentLength.ToString());

        int length;
        do
        {
            // Verify that the client is connected.
            if (resp.IsClientConnected)
            {
                // Read data into the buffer.
                length = stream.Read(buffer, 0, bytesToRead);

                // and write it out to the response's output stream
                resp.OutputStream.Write(buffer, 0, length);

                // Flush the data
                resp.Flush();

                //Clear the buffer
                buffer = new Byte[bytesToRead];
            }
            else
            {
                // cancel the download if client has disconnected
                length = -1;
            }
        } while (length > 0); //Repeat until no data is read
    }
    finally
    {
        if (stream != null)
        {
            //Close the input stream
            stream.Close();
        }
    }

2
无需在每次运行时刷新响应并重新创建缓冲区-这只会减慢传输速度。另外,我认为您应该重命名一些变量(fileReq-> urlRequest,fileResp-> urlResponse)。同样也没有理由创建Stream变量,一开始,您应该在使用它的位置创建它(在GetResponseStream()调用附近)
数据

@data_smith为什么不尝试您的建议?它不会编译。缓冲区的目的是降低Web服务器上的内存使用率。
狗仔队2012年

3
在此示例中未定义resp:代码无法编译。
案件

1
如@Sharon所述,此代码无法编译。您需要做的是,在显示的注释中// prepare the response to the client. resp is the client Response,添加以下代码:var resp = HttpContext.Current.Response;
Jeremy Wiggins

@达拉斯谢谢您提供的样品,这让我很开心。我在构建将mp3文件流式传输到Windows Phone 7客户端的服务时使用它,其中AudioPlayerAgent无法处理重定向302,然后需要直接获取文件流。
乔克斯(Jonx)2012年

31

将网址下载到字节并将字节转换为流:

using (var client = new WebClient())
{
    var content = client.DownloadData(url);
    using (var stream = new MemoryStream(content))
    {
        ...
    }
}   

4
尽管这看起来很简单并且可以正常工作,但是您需要记住该解决方案将文件加载到计算机内存中。因此,当1)数据太大或2)同时进行多个请求时,由于内存不足而导致异常的机会很高。
本杰明

本杰明,可以通过改用HttpClient缓解该问题。
jpaugh

13

我做了很多,并认为我可以添加一个更简单的答案。我在这里将其设置为一个简单的班级,但我每天晚上都运行此程序,以收集我关注的公司的财务数据。

class WebPage
{
    public static string Get(string uri)
    {
        string results = "N/A";

        try
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
            HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

            StreamReader sr = new StreamReader(resp.GetResponseStream());
            results = sr.ReadToEnd();
            sr.Close();
        }
        catch (Exception ex)
        {
            results = ex.Message;
        }
        return results;
    }
}

在这种情况下,我传入一个URL,它将页面作为HTML返回。如果您想对流进行其他操作,则可以轻松更改此设置。

您可以这样使用它:

string page = WebPage.Get("http://finance.yahoo.com/q?s=yhoo");

我的情况有所不同,有一个文档管理系统,并以.tif格式返回文档,其中没有以querystring发送。例子:链接,但是这种方法不起作用,任何想法都值得赞赏。谢谢
kayhanyüksel16年

@kayhanyüksel:我尝试了该链接,但是超时。抱歉。
CodeChops

6

2年后,我用了达拉斯小牛队的答案,但我不得不改变HttpWebRequestFileWebRequest,因为我是链接到直接文件。不知道是否到处都是这种情况,但我想我会添加它。另外,我删除了

var resp = Http.Current.Resonse

并仅Http.Current.Responseresp引用位置使用。


1
完善。我剥夺了达拉的答案。我要在下面发布我的版本
里卡多·阿普尔顿

4

如果您正在寻找@Dallas答案的.NET Core版本,请使用以下内容。

        Stream stream = null;

        //This controls how many bytes to read at a time and send to the client
        int bytesToRead = 10000;

        // Buffer to read bytes in chunk size specified above
        byte[] buffer = new Byte[bytesToRead];

        // The number of bytes read
        try
        {
            //Create a WebRequest to get the file
            HttpWebRequest fileReq = (HttpWebRequest)HttpWebRequest.Create(@"file url");

            //Create a response for this request
            HttpWebResponse fileResp = (HttpWebResponse)fileReq.GetResponse();

            if (fileReq.ContentLength > 0)
                fileResp.ContentLength = fileReq.ContentLength;

            //Get the Stream returned from the response
            stream = fileResp.GetResponseStream();

            // prepare the response to the client. resp is the client Response
            var resp = HttpContext.Response;

            //Indicate the type of data being sent
            resp.ContentType = "application/octet-stream";

            //Name the file 
            resp.Headers.Add("Content-Disposition", "attachment; filename=test.zip");
            resp.Headers.Add("Content-Length", fileResp.ContentLength.ToString());

            int length;
            do
            {
                // Verify that the client is connected.
                if (!HttpContext.RequestAborted.IsCancellationRequested)
                {
                    // Read data into the buffer.
                    length = stream.Read(buffer, 0, bytesToRead);

                    // and write it out to the response's output stream
                    resp.Body.Write(buffer, 0, length);


                    //Clear the buffer
                    buffer = new Byte[bytesToRead];
                }
                else
                {
                    // cancel the download if client has disconnected
                    length = -1;
                }
            } while (length > 0); //Repeat until no data is read
        }
        finally
        {
            if (stream != null)
            {
                //Close the input stream
                stream.Close();
            }
        }

0

您可以尝试使用带有IIS路径前缀的DirectoryEntry类:

using(DirectoryEntry de = new DirectoryEntry("IIS://Localhost/w3svc/1/root" + DOCUMENT_PATH))
{
    filePath = de.Properties["Path"].Value;
}

if (!File.Exists(filePath))
        return;

var fileInfo = new System.IO.FileInfo(filePath);
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", String.Format("attachment;filename=\"{0}\"", filePath));
Response.AddHeader("Content-Length", fileInfo.Length.ToString());
Response.WriteFile(filePath);
Response.End();

有人对此表示否决,但没有理由。我正在平衡那票。
杰森

0

如果我们在Citrix Netscaler上使用Load Balancer(无WAF策略),则达拉斯公认的解决方案正在为我们工作。

当文件与WAF关联时,文件下载无法通过Netscaler的LB进行,因为当前情况(内容长度不正确)违反了RFC,并且AppFW重置了连接,而WAF不会发生策略未关联。

因此缺少的是:

Response.End();

另请参阅: 尝试使用asp.net传输PDF文件会产生“损坏的文件”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.