mercredi 10 juin 2015

C# generic Excel exporter using reflection

I'm working on generic Excel exporter in C#. My point is to put a collection of any type and specify which properties of the class should be exported using lambda expressions and I have done that. The problem I'm struggling with is that when I have complex property in my class, the property value exported is "Namespace.ClassName" (e.g. "MyApp.ViewModels.MyViewModel").

Here is my code:

Excel exporter class:

    public class ExcelExporter
    {
        public void ExportToExcel<T>(IEnumerable<T> data, params Expression<Func<T, object>>[] columns)
        {
            DataTable dataTable = this.ConvertToDataTable(data, columns);              
            //Export the dataTable object to Excel using some library...
        }

        private DataTable ConvertToDataTable<T>(IEnumerable<T> data, params Expression<Func<T, object>>[] columnsFunc)
        {
            DataTable table = new DataTable();

            foreach (var column in columnsFunc)
            {
                string columnName = ReflectionUtility.GetPropertyDisplayName<T>(column);
                table.Columns.Add(columnName);
            }

            foreach (T obj in data)
            {
                DataRow row = table.NewRow();

                for (int i = 0; i < table.Columns.Count; i++)
                {
                    row[table.Columns[i].ColumnName] = ReflectionUtility.GetPropertyValue<T>(obj, columnsFunc[i]);
                }

                table.Rows.Add(row);
            }

            return table;

        }

ReflectionUtility class - provides methods to get property name and value. "GetPropertyDisplayName" method reads the [DisplayName] attribute value from the property and sets it as a header column in the Excel document (the reason is that I'd like property like 'FirstName' to be displayed 'First Name').

public static class ReflectionUtility
    {
        /// <summary>
        /// Returns the display name of a property (set by using [DisplayName] attribute).
        /// If [DisplayName] is not provided, returns the actual property name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string GetPropertyDisplayName<T>(Expression<Func<T, object>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;

            if (memberExpression == null)
            {
                memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression;
            }

            var property = memberExpression.Member as PropertyInfo;

            if (property != null)
            {
                var displayNameAttribute = property.GetCustomAttribute(typeof(DisplayNameAttribute), false) as DisplayNameAttribute;

                if (displayNameAttribute != null)
                {
                    return displayNameAttribute.DisplayName;
                }
            }

            return memberExpression.Member.Name;
        }

        public static object GetPropertyValue<T>(T obj, Expression<Func<T, object>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;

            if (memberExpression == null)
            {
                memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression;
            }

            var property = memberExpression.Member as PropertyInfo;

            if (property != null)
            {
                // Note: If we want to export complex object, the object's value is something like "Namespace.ClassName", which is
                // inappropriate for displaying. So we must specify additionally which property from the complex object should be visualized...

                var value = property.GetValue(obj);

                return value;
            }

            return null;
        }

How I consume ExcelExporter class:

ExcelExporter excelExporter = new ExcelExporter();

    excelExporter.ExportToExcel<MyViewModel>(genericListToExport,
        p => p.StringProperty1,
        p => p.StringProperty2,
        p => p.ComplexProperty.IntProperty1);

How can I pass ComplexProperty.IntProperty1 and get the value of it and handle the case when ComplexProperty is null, so I won't get a NullReferenceException.

Any help is appreciated!





Aucun commentaire:

Enregistrer un commentaire