Download CSV using ASP.NET Web API

The CsvMediaTypeFormatter on Github makes it possible to support text/csv media type to the Web API stack. However, I wanted to rewrite it using the ToCsv() extension method from my earlier post.

using App.Helpers;

namespace App.Web.MediaTypes
{
    public class CsvMediaTypeFormatter : BufferedMediaTypeFormatter
    {
        public CsvMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
        }

        public override bool CanReadType(Type type)
        {
            return false;
        }

        public override bool CanWriteType(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            // type must implement IEnumerable
            return typeof(IEnumerable).IsAssignableFrom(type);
        }

        public override void WriteToStream(
            Type type,
            object value,
            Stream writeStream,
            HttpContent content)
        {
            using (var writer = new StreamWriter(writeStream))
            {
                string csv = ((IEnumerable)value).ToCsv();
                writer.Write(csv);
            }
        }
    }
}

Add the CsvMediaTypeFormatter to the formatters collection in the start up configuration code.

using App.Web.MediaTypes;

namespace App.Web
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // add the media formatter to the pipeline
            config.Formatters.Add(new CsvMediaTypeFormatter());
        }
    }
}

With the above configuration in place, a call to the web service with the Accept request header set to text/csv will return csv formatted output.

[RoutePrefix("api")]
public class MainController : ApiController
{
    [Route("Cars")]
    public List<Car> GetCars()
    {
        var cars = new List<Car>();

        cars.Add(new Car { Make = "Chevy", Year = 2004});
        cars.Add(new Car { Make = "Ford", Year = 2005});

        return cars;
    }
}

[JsonObject]
public class Car
{
    [JsonProperty(PropertyName = "make", Order = 1)]
    public string Make { get; set; }
    
    [JsonProperty(PropertyName = "year", Order = 0)]
    public int Year { get; set; }
}

Same can be achieved if the method returns a HttpResponseMessage. Code shown is based on How Content Negotiation Works

[Route("CarsFile")]
public HttpResponseMessage GetCarsFile()
{
    var cars = new List<Car>();

    cars.Add(new Car { Make = "Chevy", Year = 2004});
    cars.Add(new Car { Make = "Ford", Year = 2005});
    
    var negotiator = this.Configuration.Services.GetContentNegotiator();

    var result = negotiator.Negotiate(typeof(List<Car>),
                   this.Request,
                   this.Configuration.Formatters);

    // no formatter found
    if (result == null)
    {
        throw new HttpResponseException(
              new HttpResponseMessage(HttpStatusCode.NotAcceptable));
    }

    var response = new HttpResponseMessage
    {
        Content = new ObjectContent<List<Car>>(
            cars,  // data
            result.Formatter, // media formatter
            result.MediaType.MediaType // MIME type
        )
    };

    // add the `content-disposition` response header
    // to display the "File Download" dialog box 
    response.Content.Headers.ContentDisposition = 
        new ContentDispositionHeaderValue("attachment")
    {
        FileName = "cars-download." + GetFileExt(result.Formatter);
    };

    return response;
}

private string GetFileExt(MediaTypeFormatter formatter)
{
    if (formatter is JsonMediaTypeFormatter)
    {
        return "json";
    }

    if (formatter is CsvMediaTypeFormatter)
    {
        return "csv";
    }

    // default to text
    return "txt";
}

Related:

http://www.vickram.me/ienumerable-to-csv-extension-method

http://www.asp.net/web-api/overview/formats-and-model-binding/content-negotiation