获取Content-Disposition参数

22

我如何使用WebClient获取从WebAPI控制器返回的Content-Disposition参数?

WebAPI控制器

    [Route("api/mycontroller/GetFile/{fileId}")]
    public HttpResponseMessage GetFile(int fileId)
    {
        try
        {
                var file = GetSomeFile(fileId)

                HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StreamContent(new MemoryStream(file));
                response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
                response.Content.Headers.ContentDisposition.FileName = file.FileOriginalName;

                /********* Parameter *************/
                response.Content.Headers.ContentDisposition.Parameters.Add(new NameValueHeaderValue("MyParameter", "MyValue"));

                return response;

        }
        catch(Exception ex)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
        }

    }

客户端

    void DownloadFile()
    {
        WebClient wc = new WebClient();
        wc.DownloadDataCompleted += wc_DownloadDataCompleted;
        wc.DownloadDataAsync(new Uri("api/mycontroller/GetFile/18"));
    }

    void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
    {
        WebClient wc=sender as WebClient;

        // Try to extract the filename from the Content-Disposition header
        if (!String.IsNullOrEmpty(wc.ResponseHeaders["Content-Disposition"]))
        {
           string fileName = wc.ResponseHeaders["Content-Disposition"].Substring(wc.ResponseHeaders["Content-Disposition"].IndexOf("filename=") + 10).Replace("\"", ""); //FileName ok

        /******   How do I get "MyParameter"?   **********/

        }
        var data = e.Result; //File OK
    }

我从WebApi控制器返回一个文件,我在响应内容头中附加了文件名称,但是我还想返回额外的值。

在客户端,我可以获取文件名,但是如何获取附加参数?

5个回答

48

如果您正在使用.NET 4.5或更高版本,请考虑使用System.Net.Mime.ContentDisposition类:

string cpString = wc.ResponseHeaders["Content-Disposition"];
ContentDisposition contentDisposition = new ContentDisposition(cpString);
string filename = contentDisposition.FileName;
StringDictionary parameters = contentDisposition.Parameters;
// You have got parameters now

编辑:

否则,您需要根据其规范解析Content-Disposition头。

以下是一个执行解析的简单类,与规范接近:

class ContentDisposition {
    private static readonly Regex regex = new Regex(
        "^([^;]+);(?:\\s*([^=]+)=((?<q>\"?)[^\"]*\\k<q>);?)*$",
        RegexOptions.Compiled
    );

    private readonly string fileName;
    private readonly StringDictionary parameters;
    private readonly string type;

    public ContentDisposition(string s) {
        if (string.IsNullOrEmpty(s)) {
            throw new ArgumentNullException("s");
        }
        Match match = regex.Match(s);
        if (!match.Success) {
            throw new FormatException("input is not a valid content-disposition string.");
        }
        var typeGroup = match.Groups[1];
        var nameGroup = match.Groups[2];
        var valueGroup = match.Groups[3];

        int groupCount = match.Groups.Count;
        int paramCount = nameGroup.Captures.Count;

        this.type = typeGroup.Value;
        this.parameters = new StringDictionary();

        for (int i = 0; i < paramCount; i++ ) {
            string name = nameGroup.Captures[i].Value;
            string value = valueGroup.Captures[i].Value;

            if (name.Equals("filename", StringComparison.InvariantCultureIgnoreCase)) {
                this.fileName = value;
            }
            else {
                this.parameters.Add(name, value);
            }
        }
    }
    public string FileName {
        get {
            return this.fileName;
        }
    }
    public StringDictionary Parameters {
        get {
            return this.parameters;
        }
    }
    public string Type {
        get {
            return this.type;
        }
    }
} 

然后您可以这样使用它:

static void Main() {        
    string text = "attachment; filename=\"fname.ext\"; param1=\"A\"; param2=\"A\";";

    var cp = new ContentDisposition(text);       
    Console.WriteLine("FileName:" + cp.FileName);        
    foreach (DictionaryEntry param in cp.Parameters) {
        Console.WriteLine("{0} = {1}", param.Key, param.Value);
    }        
}
// Output:
// FileName:"fname.ext" 
// param1 = "A" 
// param2 = "A"  

在使用这个类时唯一需要考虑的是,如果没有双引号,它无法处理参数(或文件名)。

编辑2:

现在它可以处理没有引号的文件名了。


好的,我会将其标记为正确答案,你是从零开始编写这个类的吗?如果不是,请注明来源。 - The One
是的,我做了,但正如我所提到的,它可能需要进一步改进,但我希望它能解决你的问题。 - Mehrzad Chehraz
这个方法很好,但对于我的示例“attachment;filename=download1 - Copy (2).jpg”,它会失败并显示System.FormatException(指定的字符串格式不正确);不过在其他情况下它是可以工作的,尽管我只能容忍一个错误... - Saurabh
@MehrzadChehraz,你能解释一下正则表达式模式吗? - ac-lap
如果您熟悉正则表达式,这很简单。这个示例可能会有所帮助。此外,请查看规范以获取有关需要匹配哪些输入以及如何匹配的更多信息。 - Mehrzad Chehraz

15

您可以使用以下框架代码解析内容的配置:

var content = "attachment; filename=myfile.csv";
var disposition = ContentDispositionHeaderValue.Parse(content);

然后只需要从布置实例中取出这些部分即可。

disposition.FileName 
disposition.DispositionType

1
这是我完成它的方式(除了 TryParse)。它在 System.Net.Http.Headers 中。 - Subjective Reality
这很好,因为您可以使用TryParse并避免在异常上使用try/catch。 - Joshcodes

5

使用.NET Core 3.1及以上版本的最简单解决方案如下:

using var response = await Client.SendAsync(request);
response.Content.Headers.ContentDisposition.FileName

1
价值已经存在,我只需要提取它:
Content-Disposition头像这样返回:
Content-Disposition = attachment; filename="C:\team.jpg"; MyParameter=MyValue

所以我只是使用了一些字符串操作来获取这些值:

void wc_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
    WebClient wc=sender as WebClient;

    // Try to extract the filename from the Content-Disposition header
    if (!String.IsNullOrEmpty(wc.ResponseHeaders["Content-Disposition"]))
    {
        string[] values = wc.ResponseHeaders["Content-Disposition"].Split(';');
        string fileName = values.Single(v => v.Contains("filename"))
                                .Replace("filename=","")
                                .Replace("\"","");

        /**********  HERE IS THE PARAMETER   ********/
        string myParameter = values.Single(v => v.Contains("MyParameter"))
                                   .Replace("MyParameter=", "")
                                   .Replace("\"", "");

    }
    var data = e.Result; //File ok
}

看起来不错,我只会添加一个Trim()。由于该类中存在解析错误,我无法使用System.New.Mime.ContentDisposition。 - Chris Klepeis

1
正如 @Mehrzad Chehraz 所说,您可以使用新的 ContentDisposition 类。
using System.Net.Mime;

// file1 is a HttpResponseMessage
FileName = new ContentDisposition(file1.Content.Headers.ContentDisposition.ToString()).FileName

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