Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net Web API - How to set the filename for a custom CSV Media Type Formatter

I have successfully implemented a Csv Media Type Formatter in my ASP.Net Web API project. I am able to get results back in Csv format. However the resulting filename is "parts" without an extension.

Ideally, I want to be able to set this filename in the controller, but being able to add the extension globally would be a minimum.

Below are the examples I have found

Override OnGetResponseHeaders - I don't see that as an option in the current version. http://forums.asp.net/t/1782973.aspx/1?Setting+response+and+content+headers+esp+ContentDisposition+inside+a+MediaTypeFormatter

According to that article this should work

public override IEnumerable<KeyValuePair<string, string>> OnGetResponseHeaders(Type objectType, string mediaType, HttpResponseMessage responseMessage)
{
       return new[] { new KeyValuePair<string, string>("Content-Disposition", "attachment; filename=testing.csv") };
}

However Visual Studio says "There is no suitable method for override" and won't compile when I add that to my custom Csv Formatter.

Return HttpMessageResponse from controller - How to set downloading file name in ASP.NET Web API However this appears to just be pushing existing server files, which would take the Csv serialization out of the mix. Below is an attempt to make this approach work:

public HttpResponseMessage Get(string id)
{
    var response = new HttpResponseMessage();

    if (id == "test")
    {
        var data = GetTestData();
        response.StatusCode = HttpStatusCode.OK;
        response.Content = new StreamContent(data);
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        response.Content.Headers.ContentDisposition.FileName = "testorama.csv";
        return response;
    }

    return null;
}

The issue here is that new StreamContent() is expecting a stream - is there a way to get the current stream that Custom Csv Formatter created?

Bottom line, how can I set the filename for a resultset that is first serialized to Csv format?

Solution

Thanks Claudio - that got me going in the right direction. A couple changes from what you posted:

  1. I was deriving from BufferedMediaTypeFormatter for my custom Csv Formatter and to use SetDefaultContentHeaders I had to instead derive from MediaTypeFormatter.

  2. SetDefaultContentHeaders accepts the mediaType parameter with a type of MediaTypeHeaderValue and not string.

Here is the final code:

public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
    base.SetDefaultContentHeaders(type, headers, mediaType);
    headers.Add("Content-Disposition", "attachment; filename=testorama.csv");
}
like image 587
Luke Jenkins Avatar asked Sep 07 '12 21:09

Luke Jenkins


2 Answers

Override the method SetDefaultContentHeaders on your MediaTypeFormatter

public override void SetDefaultContentHeaders(
    Type type, HttpContentHeaders headers, string mediaType)
{
    base.SetDefaultContentHeaders(type, headers, mediaType);
    headers.Add("Content-Disposition", "attachment; filename=yourname.csv");
}
like image 134
Claudio Redi Avatar answered Nov 02 '22 01:11

Claudio Redi


In order to provide a custom name based on the data (at least in Web API 2), you will want to override GetPerRequestFormatterInstance. From there you can get access to the HttpRequestMessage.

private string FileName { get; set; }

public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
    base.SetDefaultContentHeaders(type, headers, mediaType);
    if (!string.IsNullOrEmpty(FileName))
        headers.Add("Content-Disposition", string.Format("attachment; filename={0}", FileName));
}

public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType)
{
    // We aren't setting the name, so just return the current instance.
    if (!request.Properties.ContainsKey("filename")) return this;

    var formatter = new CsvFormatter();
    var fileName = request.Properties["filename"] as string;
    formatter.FileName = string.Format("{0}.csv", fileName);
    return formatter;
}

Then, in your controller action, you can set the filename like this...

if (!Request.Properties.ContainsKey("filename"))
    Request.Properties.Add("filename", "MyFile.{ext}");

I use {ext} in case I add additional formatters that require a filename. In theory, only the extension should have to change to support the other formats.

like image 30
Brian Avatar answered Nov 02 '22 02:11

Brian