Get Content-Disposition parameters
Asked Answered
R

5

22

How do I get Content-Disposition parameters I returned from WebAPI controller using WebClient?

WebApi Controller

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

    }

Client

    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
    }

I'm returning a file from WebApi controller, I'm attaching the file name in the response content headers, but also I'd like to return an aditional value.

In the client I'm able to get the filename, but how do I get the aditional parameter?

Revet answered 12/5, 2015 at 14:16 Comment(0)
L
48

If you are working with .NET 4.5 or later, consider using the System.Net.Mime.ContentDisposition class:

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

Edit:

otherwise, you need to parse Content-Disposition header according to it's specification.

Here is a simple class that performs the parsing, close to the specification:

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

Then you can use it in this way:

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"  

The only thing that should be considered when using this class is it does not handle parameters (or filename) without a double quotation.

Edit 2:

It can now handle file names without quotations.

Lamelli answered 12/5, 2015 at 14:31 Comment(5)
Great, I'll mark it as the correct answer, did you write the class from scratch? if not, please indicate source.Revet
Yes I did, but as I mentioned, It may need further improvements, but I hope it solves your problem.Lamelli
this method is good but for my example "attachment;filename=download1 - Copy (2).jpg" it was failing with System.FormatException (Specified string is not in correct format); anyway for other scenarios it was working though I am living with one error...Weaken
@MehrzadChehraz can you plz explain the regex pattern?Environmentalist
@Environmentalist It's simple if you are familiar with regex. This sample might help. Also, check the specs to get more info about which input needs to get matched and how.Lamelli
E
17

You can parse out the content disposition using the following framework code:

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

Then just take the pieces off of the disposition instance.

disposition.FileName 
disposition.DispositionType
Eweneck answered 24/5, 2016 at 9:20 Comment(2)
This is how I did it (except with TryParse). It's in System.Net.Http.Headers.Continuance
This is nice because you can use TryParse and avoid a try/catch on an exception.Sanjuanitasank
W
5

With .NET Core 3.1 and more the most simple solution is:

using var response = await Client.SendAsync(request);
response.Content.Headers.ContentDisposition.FileName
Whorled answered 21/10, 2022 at 9:7 Comment(0)
R
1

The value is there I just needed to extract it:

The Content-Disposition header is returned like this:

Content-Disposition = attachment; filename="C:\team.jpg"; MyParameter=MyValue

So I just used some string manipulation to get the values:

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
}
Revet answered 12/5, 2015 at 15:37 Comment(1)
Looks good, I'd just add a Trim(). I can't use System.New.Mime.ContentDisposition due to parsing errors in that class.Windowsill
B
1

As @Mehrzad Chehraz said you can use the new ContentDisposition class.

using System.Net.Mime;

// file1 is a HttpResponseMessage
FileName = new ContentDisposition(file1.Content.Headers.ContentDisposition.ToString()).FileName
Borges answered 10/11, 2019 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.