在下载文件时获取文件名

28
我们提供的文件保存在数据库中,唯一检索它们的方式是使用它们的 id,比如:www.AwesomeURL.com/AwesomeSite.aspx?requestedFileId=23。我正在使用 WebClient 类,一切都工作得很好。
但是,我面临的唯一问题是,如何获取真实的文件名?目前我的代码如下:
WebClient client = new WebClient ();

string url = "www.AwesomeURL.com/AwesomeSite.aspx?requestedFileId=23";

client.DownloadFile(url, "IDontKnowHowToGetTheRealFileNameHere.txt");

我所知道的只有 id

当我尝试从浏览器访问url时,它可以得到正确的名称 => DownloadedFile.xls,但在其他情况下却不行。

获取正确响应的正确方法是什么?


你找到解决方案了吗?如果这里的任何答案都没有帮助到你,而你已经找到了解决方案,请与我们分享。 :) - Shadow The Spring Wizard
我实际上还没有找到任何解决方案... 把代码藏起来,等待一些随机的灵感 ;) - Faizan S.
当你尝试我的答案中提供的代码时会发生什么?你从未在那里留下评论。 - Shadow The Spring Wizard
我该如何获取文件名的编码?如果我使用HttpWebResponse,我可以使用StreamReader sr = new StreamReader(s, Encoding.GetEncoding(response.CharacterSet)); - Nime Cloud
8个回答

26

我曾经遇到过同样的问题,后来发现了这个类:System.Net.Mime.ContentDisposition

using (WebClient client = new WebClient()){
    client.OpenRead(url);

    string header_contentDisposition = client.ResponseHeaders["content-disposition"];
    string filename = new ContentDisposition(header_contentDisposition).FileName;

    ...do stuff...
}

这个类的文档显示它是用于电子邮件附件的,但在我用来测试的服务器上它运行良好,而且避免了解析,非常方便。


我该如何获取文件名的编码?如果我使用HttpWebResponse,我可以使用StreamReader sr = new StreamReader(s, Encoding.GetEncoding(response.CharacterSet)); - Nime Cloud
@NimeCloud 我相信 WebClient 会尝试读取头信息并自动设置编码。如果头信息中没有或者是损坏的,你可以提前使用 WebClient.Encoding 来设置它。 - wst

24

以下是完整的代码,假设服务器应用了content-disposition头部:

using (WebClient client = new WebClient())
{
    using (Stream rawStream = client.OpenRead(url))
    {
        string fileName = string.Empty;
        string contentDisposition = client.ResponseHeaders["content-disposition"];
        if (!string.IsNullOrEmpty(contentDisposition))
        {
            string lookFor = "filename=";
            int index = contentDisposition.IndexOf(lookFor, StringComparison.CurrentCultureIgnoreCase);
            if (index >= 0)
                fileName = contentDisposition.Substring(index + lookFor.Length);
        }
        if (fileName.Length > 0)
        {
            using (StreamReader reader = new StreamReader(rawStream))
            {
                File.WriteAllText(Server.MapPath(fileName), reader.ReadToEnd());
                reader.Close();
            }
        }
        rawStream.Close();
    }
}
如果服务器没有设置这个标头,请尝试调试并查看您拥有的ResponseHeaders,其中一个可能包含您想要的名称。如果浏览器显示了名称,那么它必须来自某个地方.. :)

1
如果我没记错的话,您忘记从文件名中删除周围的引号了。即 "file.txt" 而不是 file.txt - yellowblood
1
也许只是我使用的服务器有问题。另外需要注意的是,这个方法只适用于文本文件,在大多数情况下,使用字节而不是文本可能更好。你可以将StreamReader替换为MemoryStream,然后使用rawStream.CopyTo(memStream)File.WriteAllBytes(path, memStream.ToArray())(仅适用于.NET 4)。 - yellowblood
1
@Shadow 谢谢,我会尝试使用Chrome开发者工具!在.NET 4中[不确定其他版本],可以通过实例化从client.ResponseHeaders["content-disposition"]返回的字符串来使用System.Net.Mime.ContentDisposition[我一直在使用WebResponse,所以我使用了Response.Headers["Content-Disposition"]],文件名包含在FileName属性中,我们还可以检查Content-Disposition是否为“Inline”,这将决定我们是应该使用Url还是Content-Disposition来获取文件名。 - Writwick
您不必手动解析Content-Disposition头,.NET有一个类可以完成此操作(System.Net.Mime.ContentDisposition)。 - Daniel Lo Nigro
@DanielLoNigro 感谢,我之前不知道这个。等有时间的时候,我会尽快简化我的代码。 - Shadow The Spring Wizard
显示剩余5条评论

10

您需要查看content-disposition头,方法如下:

string disposition = client.ResponseHeaders["content-disposition"];
一个典型的例子是:
"attachment; filename=IDontKnowHowToGetTheRealFileNameHere.txt"

我认为他并不担心服务器端。 - Daniel A. White
@Daniel - 谁说了服务器的事情?客户端 负责 读取 这个头部... - Marc Gravell
谢谢您添加这个 - 我没有看到 WebClient 可以访问响应头。 - Daniel A. White
有趣,我在下载后得到了content-disposition,但代码抛出了NullReferenceException异常 | 还有其他地方可以放置它吗? - Faizan S.
@Shaharyar - 通过 Fiddler 运行它。如果它没有发送该标头,那么坦白地说,您只能编造一个名称。 - Marc Gravell
糟糕,我刚刚在FireBug运行它,没有任何响应。 - Faizan S.

4
我使用wst代码实现了这个功能。
以下是将URL文件下载到c:\temp文件夹的完整代码。
public static void DownloadFile(string url)
    {
        using (WebClient client = new WebClient())
        {
            client.OpenRead(url);

            string header_contentDisposition = client.ResponseHeaders["content-disposition"];
            string filename = new ContentDisposition(header_contentDisposition).FileName;


            //Start the download and copy the file to the destinationFolder
            client.DownloadFile(new Uri(url), @"c:\temp\" + filename);
        }

    }

3

您可以使用HTTP content-disposition头来建议提供的内容的文件名:

Content-Disposition: attachment; filename=downloadedfile.xls;

因此,在您的AwesomeSite.aspx脚本中,您将设置content-disposition头。在您的WebClient类中,您将检索该标头以按照AwesomeSite网站建议保存文件。


1
虽然Shadow Wizard提出的解决方案对于文本文件很有效,但是我需要支持在我的应用程序中下载二进制文件,例如图片和可执行文件。

这里是一个小扩展WebClient,可以完成此操作。下载是异步的。还需要文件名的默认值,因为我们不知道服务器是否会发送所有正确的标头。

static class WebClientExtensions
{
    public static async Task<string> DownloadFileToDirectory(this WebClient client, string address, string directory, string defaultFileName)
    {
        if (!Directory.Exists(directory))
            throw new DirectoryNotFoundException("Downloads directory must exist");

        string filePath = null;

        using (var stream = await client.OpenReadTaskAsync(address))
        {
            var fileName = TryGetFileNameFromHeaders(client);
            if (string.IsNullOrWhiteSpace(fileName))
                fileName = defaultFileName;

            filePath = Path.Combine(directory, fileName);
            await WriteStreamToFile(stream, filePath);
        }

        return filePath;
    }

    private static string TryGetFileNameFromHeaders(WebClient client)
    {
        // content-disposition might contain the suggested file name, typically same as origiinal name on the server
        // Originally content-disposition is for email attachments, but web servers also use it.
        string contentDisposition = client.ResponseHeaders["content-disposition"];
        return string.IsNullOrWhiteSpace(contentDisposition) ?
            null :
            new ContentDisposition(contentDisposition).FileName;
    }

    private static async Task WriteStreamToFile(Stream stream, string filePath)
    {
        // Code below will throw generously, e. g. when we don't have write access, or run out of disk space
        using (var outStream = new FileStream(filePath, FileMode.CreateNew))
        {
            var buffer = new byte[8192];
            while (true)
            {
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0)
                    break;
                // Could use async variant here as well. Probably helpful when downloading to a slow network share or tape. Not my use case.
                outStream.Write(buffer, 0, bytesRead);
            }
        }
    }
}

0

好的,轮到我了。

当我尝试“下载文件”时,我有几个想法:

  1. 仅使用HttpClient。我有一些扩展方法,创建其他扩展WebClient并不理想。
  2. 对我来说,获取文件名也是必须的。
  3. 我必须将结果写入MemoryStream,但不是FileStream

解决方案

所以,对于我来说,解决方案是这段代码:

// assuming that httpClient created already (including the Authentication cumbersome)
var response = await httpClient.GetAsync(absoluteURL);  // call the external API

// reading file name from HTTP headers
var fileName = response.Content.Headers.ContentDisposition.FileNameStar; // also available to read from ".FileName"

// reading file as a byte array
var fileBiteArr = await response.Content
                        .ReadAsByteArrayAsync()
                        .ConfigureAwait(false); 

var memoryStream = new MemoryStream(fileBiteArr); // memory streamed file
            

测试

为了测试Stream是否包含我们需要的内容,可以将其转换为文件并进行检查:

// getting the "Downloads" folder location, can be anything else
string pathUser = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string downloadPath = Path.Combine(pathUser, "Downloads\\"); 

using (FileStream file =
                 new FileStream(
                     $"{downloadPath}/file.pdf", 
                     FileMode.Create, 
                     FileAccess.Write))
{
     byte[] bytes = new byte[memoryStream .Length];
     memoryStream.Read(bytes, 0, (int)memoryStream.Length);

     file.Write(bytes, 0, bytes.Length);
     memoryStream.Close();
}


0
这是我的工作代码:
using (var client = new HttpClient())
   using (var result = await client.GetAsync(downloadUrl))
       if (result.IsSuccessStatusCode)
       {
        string fileName = string.Empty;
        if (result.Content.Headers.ContentDisposition != null)
        {
          fileName = System.Net.WebUtility.UrlDecode(result.Content.Headers.ContentDisposition.FileName).Replace("\"", "");
        }
        ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接