Export to CSV from a List of class object(s).
Today I got an assignment to transfer/export data into CSV file from a
List of class object so I thought to make it generic so that I can use it in
multiple modules and projects too, but how with the following challenges?
Exclude Class Property.
Custom Header.
No Header.
Column Order while exporting to CSV.
Should be Generic.
The solution came to mind is to use C# Attribute to handle the challenges.
Attribute (MSDN):Attributes provide a powerful
method of associating metadata, or declarative information, with code
(assemblies, types, methods, properties, and so forth).
Reflection (MSDN): Reflection provides objects (of
type Type) that describe assemblies, modules and types. You can use reflection
to dynamically create an instance of a type, bind the type to an existing
object, or get the type from an existing object and invoke its methods or
access its fields and properties. If you are using attributes in your code,
reflection enables you to access them. Using reflection I can extract the property
information or custom attribute provided for CSV.
Generics (MSDN):Generics introduce to the .NET
Framework the concept of type parameters, which make it possible to design
classes and methods that defer the specification of one or more types until the
class or method is declared and instantiated by client code.Generics used to
export any List of class object without re-writing code again and again.
Let’s start with designing Attribute.
using System;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public class CSVAttribute : Attribute { private readonly bool allowExport; private readonly int order; private readonly string columnHeader; private readonly string format; public CSVAttribute(bool allowExport = false, int order = 0,
string columnHeader = "") { this.allowExport = allowExport; this.order = order; this.columnHeader = columnHeader; } public CSVAttribute(bool allowExport = false, string columnHeader = "",
int order = 0, string format = "") { this.allowExport = allowExport; this.order = order; this.columnHeader = columnHeader; this.format = format; } public string ColumnHeader { get { return columnHeader; } } public bool AllowExport { get { return allowExport; } } public int Order { get { return order; } } public string Format { get { return format; } } }
So we have defined CSVAttribute which will be applied at property level,
multiple definitions on same property is not allowed and it is not inherited.
This attribute has constructor which take three values
1. AllowExport (whether you want to export to CSV default is false).
2. ColumnHeader (Column Header you want to write while exporting).
3. Order (Order in which you want column to be printed).
1. AllowExport (whether you want to export to CSV default is false).
2. ColumnHeader (Column Header you want to write while exporting).
3. Order (Order in which you want column to be printed).
Let’s now define the Custom CSVPropertyInfo Class which we will use
during export to CSV option.
using System.Reflection; public class CSVPropertyInfo { public PropertyInfo Property { get; set; } public CSVAttribute Attribute { get; set; } }
Now time to define the CSVFileWriter
class who is responsible for writing the header and data in CSV for provided
property or propertied only.
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text;
public class CSVFileWriter<T> : ICSVFileWriter<T> where T : class { private List<T> Data { get; set; } private IEnumerable<CSVPropertyInfo> ExportProperties { get; set; } private string Headers { get { var headerName = new StringBuilder(); foreach (CSVPropertyInfo property in ExportProperties) { headerName.Append(string.Format("\"{0}\",",
property.Attribute.ColumnHeader)); } headerName.Remove(headerName.Length - 1, 1); return headerName.ToString(); } } public void Write(string fullFileName, List<T> data, bool writeHeader = true) { if (data == null || !data.Any()) { throw new Exception("Please provide the data"); } Data = data; SetExportProperties(); var fileData = new StringBuilder(); if (writeHeader) { fileData.AppendLine(Headers); } fileData.Append(GetRowData()); WriteFile(fullFileName, fileData.ToString()); } private void SetExportProperties() { var properties = new List<CSVPropertyInfo>(); foreach (PropertyInfo property in Data.First().GetType().GetProperties()) { object[] attrs = property.GetCustomAttributes(true); foreach (CSVAttribute attr in attrs
.Where(x => x.GetType() == typeof(CSVAttribute))) { if (attr != null && attr.AllowExport) { properties.Add(new CSVPropertyInfo {
Attribute = attr,
Property = property }); } } } ExportProperties = properties.OrderBy(x => x.Attribute.Order).ToList(); } private string GetRowData() { var sb = new StringBuilder(); foreach (T data in Data) { var row = new StringBuilder(); foreach (var property in ExportProperties) { if (property.Attribute.Format.Length > 0) { var propType = property.Property.PropertyType; if (propType == typeof(DateTime)) { row.Append(string.Format("\"{0}\",",
Convert.ToDateTime(property.Property.GetValue(data, null))
.ToString(property.Attribute.Format))); } else if (propType == typeof(double) || propType == typeof(float)) { row.Append(string.Format("\"{0}\",",
Convert.ToDouble(property.Property.GetValue(data, null))
.ToString(property.Attribute.Format))); } else { row.Append(string.Format("\"{0}\",",
Convert.ToDouble(property.Property.GetValue(data, null)))); } } else { row.Append(string.Format("\"{0}\",",
Convert.ToString(property.Property.GetValue(data, null)))); } } row.Remove(row.Length - 1, 1); sb.AppendLine(row.ToString()); } return sb.ToString(); } private void WriteFile(string fullFileName, string data) { using (var streamWriter = new StreamWriter(fullFileName, false)) { streamWriter.Write(data); streamWriter.Close(); } } } public interface ICSVFileWriter<T> where T : class { void Write(string fullFileName, List<T> data, bool writeHeader = true); }
So our CSV writer is ready now to get export we need to implement
attribute in our class.
For example we have Employee class as follow.
publicclassEmployee
{
[CSV(true, "Date of Birth", 3)]
publicDateTime DOB { get; set;
}
[CSV(true, "First Name",1)]
publicstringFirstName { get; set; }
[CSV(true, "Last Name", 2)]
publicstringLastName { get; set; }
publicstringUserId { get; set; }
}
Once the attributeis defines it is ready for export. In above class we
have not places CSV attribute on UserId so this field will not be
considered while exporting data. The order for column we have defined which is
different than the property order in class. The Second parameter in CSV is
Column Header for DOB property we will use column Header as a Date of
Birth. Now let’s execute the actual export.
varemployeeList = newList<Employee>
{
newEmployee
{
DOB = DateTime.Now.AddDays(-365*28),
FirstName = "K",
LastName = "P"
},
newEmployee
{
DOB = DateTime.Now.AddDays(-365*28),
FirstName = "P",
LastName = "D"
},
newEmployee
{
DOB = DateTime.Now.AddDays(-365*29),
FirstName = "M",
LastName = "G"
}
};
varcsvFileWriter = newCSVFileWriter<Employee>();
csvFileWriter.Write("c:\\Test\\Employee.csv",
employeeList);
Happy Coding.J