I am working on a cool reporting MVC app using Highcharts and Datatable.net. The app has export feature. Users may want to get an extract of the data in some form that they can use. There are multiple ways of doing this on client side but users may be seeing only a part of the data on the client. We decided to serve a comma separated value(CSV) from the server. I thought I’d share the experience of wiring it up that may be helpful.

In order to serve any type of file from the server, the controller action must return a result of the type FileContentResult as below:

public FileContentResult ExportConversion(MyFilters filters)
{
  try
  {
    var Data =
        IService.SomeMethod(“filters”).ToList();

    var bytes = GenericHelper.GetBytesForCSVFile(Data, DataPreparer.FormatCSV,
                                                   "TotalItemsPerday, SpecialItemsPerDay, SpecialItemsPercentage");

    return File(bytes, "text/csv", "FileName");

 }
 catch (Exception exception)
 {
    _logger.LogError(exception);
    throw;
 }
}

In the controller action, I am getting a list of objects from the service. Then I am passing it along to a GetBytesForCSVFIle static method in it my GenericHelper class. This method takes a converter function for an object in this case Item and applies it to a collection and gives a byte array. The object looks like below:

public class Item
{
    public int Id { get; set; }
    public decimal TotalItemsPerDay { get; set; }
    public decimal SpecialItemsPerDay { get; set; }
    public decimal SpecialItemsPercentage { get; set;
}

The GetBytesForCSVFile looks like below:

Public static class GenericHelper
{
   public static byte[] GetBytesForCSVFile<T>(List<T> Data, Converter<T, string> converter,string headerrow="")
    {
        if (Data.Any())
        {
            var convertAll = Data.ConvertAll(converter);

            convertAll.Insert(0, headerrow + Environment.NewLine);

            string completeString = String.Concat(convertAll);

            return new UTF8Encoding().GetBytes(completeString);
        }
        else
        {
            return new byte[]{};
        }
    }
}

The converter looks like below:

Public static class DataPreparer
{
    public static string FormatCSV(Item item)
    {
        return TotalItemsPerDay.ToString() + 
                      "," + SpecialItemsPerday.ToString() + 
                      "," + SpecialItemsPercentage.ToString() +"%" + Environment.NewLine;
    }
}

Creating a formatter in such a way gives us a flexibility to handle any complex object and format the way we want it. If something in the format changes, the change is isolated to this formatter. I think this is clean. I didn’t think it was great use of time to come with up universal formatter function and maintain it. It is a sort of inversion of control because we are asking a client “Hey Client, what kind of format you are looking for?” Client Says “FormatCSV type”. Our client injects that into our little helper method GetBytesForCSVFile. The method just passes the converter along to a ConvertAll method and then adds the header as a first row if we have one. It then creates one string and creates a byte array out of it.

A little tangent here… If you are interested to know when to use string builder and when to use string concatenation read this post by Jon Skeet

On the view, I added a jQuery function to call this as below:

$('#Export').click(function () {
    if ($("#myFormName").validate().form()) {
        var filters = {
            StartDate: $("#StartDate").val(),
            EndDate: $("#EndDate").val()
        };
        var url = "Export?StartDate=" + filters.StartDate + "&&EndDate=" + filters.EndDate;
       document.location = url;
    }
    else {
        return;
    }
});

That’s it! On the click of the html button with Id Export, the controller action gets called. It gets the data, formats it and sends it over the wire to user as a csv file. IMHO, this is easy,clean and maintainable. If you are using .net 4.5 , you can sprinkle some async love around. :)