There are numerous articles out there that address this popular feature, and i’m not duplicating their solutions, which all work just fine, although they have their limitations.
What i don’t really like in a lot of the proposed solutions, is that they iterate over the datagrid, loosing all the controls, and replacing them with a literal control (have a look at this blog from Mitchell Sellers: http://www.mitchelsellers.com/blogs/articletype/articleview/articleid/210/exporting-a-datagrid-to-excel-the-easy-way.aspx )
I provide you with my own way for doing this, which i find easy to understand, easy to program, and very versatile.
Let’s say you are binding a list of persons to a datagrid, and that you show their firstname, lastname and date of birth. In your export however, you want a whole bunch of other properties, like address, city and postalcode!
Here is my solution:
1. Define an attribute class
using System; namespace Lbk { public class Exportable : Attribute { } }
2. Create a class, PersonExport in my case. The properties that you want to export, are decorated with the attribute you just defined.
Notice that in the constructor, we get a person object. Our PersonExport object just delegates his properties to this object. You could have done this in the Person class too, and decorate your properties there, but i chose not to. Keep it tidy and simple, Person doesn’t really need to know what properties are exportable.
namespace Lbk { public class PersonExport { private Person person; public PersonExport(Person person) { this.person = person; } [Exportable] public string LastName { get { return person.LastName; } } [Exportable] public string FirstName { get { return person.FirstName; } } [Exportable] public string Street { get { return person.Street; } } [Exportable] public string PostalCode { get { return person.PostalCode; } } [Exportable] public string City { get { return person.City; } } [Exportable] public string Country { get { return person.Country; } } [Exportable] public string BirthDate { get { if (person.ExistBirthDate) { return person.BirthDate.ToString("yyyy/MM/dd"); } return string.Empty; } } } }
3. Next, you see what happens when someone clicks on the export button in my datagrid: It does something to the Http Response, and writes out a string that contains the content of the file.
private void ExportGridView() { string attachment = "attachment; filename=leden.csv"; Response.Clear(); Response.ClearContent(); Response.ClearHeaders(); Response.AddHeader("content-disposition", attachment); Response.ContentType = "application/ms-excel"; StringBuilder sb = new StringBuilder(); this.Club.AddPersonContent(sb); Response.Write(sb.ToString()); Response.End(); }
Look at this.Club.AddPersonContent() now. That method appends the content to the stringbuilder. It iterates over all persons in the Club. But before that, it does something else
Remember that we have an PersonExport class, decorated with the [Exportable] attribute on each property. First, we need a list of these properties. These are the properties you want to export, right!
Type persontype = typeof(PersonExport); PropertyInfo[] properties = persontype.GetProperties(); List<PropertyInfo> list = new List<PropertyInfo>(); foreach(PropertyInfo property in properties) { object[] exportables = property.GetCustomAttributes(typeof (Exportable), false); if(exportables != null && exportables.Length>0) { list.Add(property); } }
Now in my csv file, i want a header row with all the names of the properties, so i append that to my stringbuilder.
foreach (PropertyInfo property in list) { sb.AppendFormat("{0},", property.Name); } sb = sb.Remove(sb.Length-1, 1); sb.AppendLine();
Finally, i iterate over all persons ( == this.PersonExtent), retrieve the values for each property, and append it to the stringbuilder.
foreach(Person person in this.PersonExtent(this.AllorsSession)) { PersonExport export = new PersonExport(person); foreach (PropertyInfo property in list) { object value = property.GetValue(export, null); if(value == null) { value = string.Empty; } sb.AppendFormat("{0},", value); } sb = sb.Remove(sb.Length-1, 1); sb.AppendLine(); }
Done! Here is the complete method again:
public void AddPersonContent(StringBuilder sb) { Type persontype = typeof(PersonExport); PropertyInfo[] properties = persontype.GetProperties(); List<PropertyInfo> list = new List<PropertyInfo>(); foreach(PropertyInfo property in properties) { object[] exportables = property.GetCustomAttributes(typeof (Exportable), false); if(exportables != null && exportables.Length>0) { list.Add(property); } } foreach (PropertyInfo property in list) { sb.AppendFormat("{0},", property.Name); } sb = sb.Remove(sb.Length-1, 1); sb.AppendLine(); foreach(Person person in this.PersonExtent(this.AllorsSession)) { PersonExport export = new PersonExport(person); foreach (PropertyInfo property in list) { object value = property.GetValue(export, null); if(value == null) { value = string.Empty; } sb.AppendFormat("{0},", value); } sb = sb.Remove(sb.Length-1, 1); sb.AppendLine(); } }
That is my solution, where you can export as many properties to your csv file as you want, because you are no longer limited to what is in your datagrid.
I find this an elegant solution, so please comment!
