My last post dealt with emitting CSV files from an IEnumerable<T>. I thought I’d extend the idea a bit by using an extension method on IEnumerable<T> as a means of making the functionality portable without the use of a “helper method.”
Here is the extension method:
public static byte[] GenerateCSVDownload<T>(this IEnumerable<T> data, 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 data) { 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()); }
And 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"} }; var bytes = people.GenerateCSVDownload( new string[] { "First Name", "Last Name" }, new Dictionary<int, Func<Person, object>>() { {0, per => per.FirstName}, {1, per => per.LastName} });
I will reiterate from my previous post: this sketch is just about the technique. For large amounts of data you'd be better off chunking the data out. You could also abstract the method a bit to deal with different kinds of output (string, file, etc) by just accepting or returning a stream.