Thursday 24 July 2014

Export to CSV from a List of class object(s).

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 = falseint order = 0
                        string columnHeader = "")
    {
        this.allowExport = allowExport;
        this.order = order;
        this.columnHeader = columnHeader;
    }
 
    public CSVAttribute(bool allowExport = falsestring columnHeader = ""
                        int order = 0string 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).

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 { getset; }
        public CSVAttribute Attribute { getset; }
    }

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<Twhere T : class
    {
        private List<T> Data { getset; }
 
        private IEnumerable<CSVPropertyInfo> ExportProperties { getset; }
 
        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 - 11);
                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 - 11);
                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<Twhere 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

Custome Service Class to connect wcf service

  using  System; using  System.ServiceModel; namespace Custom.Service.Client {      // Borrowed from: http://old.iserviceoriented.com/blog/p...