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?

shA.t
  • 16,580
  • 5
  • 54
  • 111
The One
  • 4,560
  • 5
  • 36
  • 52

5 Answers5

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.

Mehrzad Chehraz
  • 5,092
  • 2
  • 17
  • 28
  • Great, I'll mark it as the correct answer, did you write the class from scratch? if not, please indicate source. – The One May 12 '15 at 16:16
  • Yes I did, but as I mentioned, It may need further improvements, but I hope it solves your problem. – Mehrzad Chehraz May 12 '15 at 16:16
  • 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... – Saurabh Mar 19 '18 at 14:42
  • @MehrzadChehraz can you plz explain the regex pattern? – ac-lap Aug 19 '19 at 17:00
  • @ac-lap It's simple if you are familiar with regex. [This sample](https://regex101.com/r/MFxU3U/1) might help. Also, check the [specs](http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html) to get more info about which input needs to get matched and how. – Mehrzad Chehraz Aug 19 '19 at 18:36
15

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
The Senator
  • 5,181
  • 2
  • 34
  • 49
4

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

using var response = await Client.SendAsync(request);
response.Content.Headers.ContentDisposition.FileName
ArDumez
  • 931
  • 6
  • 24
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
}
johnny 5
  • 19,893
  • 50
  • 121
  • 195
The One
  • 4,560
  • 5
  • 36
  • 52
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
LockTar
  • 5,364
  • 3
  • 46
  • 72