If I had a nickel for every time I had to export data in CSV format… I would be pretty poor since as often as it does happen it is my fulltime job to get it done. One of my claims to fame longevity is an old post on the joel on software discussion boards asking about a standard for the CSV format (there is none). Anyway, here is a recent incarnation that is a bit more flexible since I can pass any IEnumerable<T> to it:
protected byte[] GenerateCSVDownload<T> ( IEnumerable<T> linqSource, string[] headers, Dictionary<int, Func<T, object>> columnData ) { Func<object, string> csvFormatter = (field) => String.Format("\"{0}\"", field); Action<IEnumerable<object>, StringBuilder> rowBuilder = (rowData, fileStringBuilder) => { fileStringBuilder.AppendFormat("{0}\n", String.Join(",", rowData.Select(r => csvFormatter(r.ToString())).ToArray())); }; StringBuilder builder = new StringBuilder(); rowBuilder(headers, builder); foreach (T entityObject in linqSource){ List<object> row = new List<object>(); for (int i = 0; i < columnData.Keys.Count; i++) { row.Add(columnData[i](entityObject)); } rowBuilder(row, builder); } return System.Text.Encoding.UTF8.GetBytes(builder.ToString()); }
Here is an example of usage:
// usage var people = new List<Person>() { new Person(){ FirstName = "Stan", LastName = "Lee"}, new Person(){ FirstName = "Jack", LastName = "Kirby"}, new Person(){ FirstName = "Alex", LastName = "Ross"}, new Person(){ FirstName = "Adi", LastName = "Granov"} }; // returns file contents var fileBytes = GenerateCSVDownload<Person>( people, new string[] { "First Name", "Last Name" }, new Dictionary<int, Func<Person, object>>() { {0, per => per.FirstName}, {1, per => per.LastName} } );
Some final things to note:
- There are a lot of tools for this sort of thing, use them if you’re not addressing things programmatically.
- If you have a really big dataset, I’d recommend building the file in chunks rather than loading it all up in memory as I’ve done above.