Click here to Skip to main content
Click here to Skip to main content

Fluent CSV/XML Exporter for List

By , 5 Apr 2011
 

Introduction

I don't know about you, but I work with lots and lots of lists at work and from time to time, I have the requirement to export some List<T> to a CSV file, and I used to just write a little helper method that use a bit of Reflection that got out all the public properties of the T objects stored in my list and then get the names of the properties and use those as headers, and then go through the List<T> and get the values out using some Reflection over the properties.

This works fine, but it is just not that generic a solution, and only really worked with that particular type of T that I was storing in the list, and all the code to decide what columns to use and what the column headers should be was hidden away from the users of the method that did the export.

So I had a think about this and thought there must be a better way, so this article represents a generic solution to this problem where I can do the following:

  1. Could be used as an extension method to any IEnumerable<T>
  2. Let the using code specify what columns should be exported
  3. Use a Fluent API (as they are all the rage these days)
  4. Allow auto header named to be obtained using Expression trees
  5. Allow the the user to supply custom header names
  6. Allow the user to also supply custom format strings for the data being exported
  7. Allow the data to be exported to a CSV file or allow the data to be exported to a CSV string
  8. Allow the exporter to be able to deal with NULL values
  9. Allow the user to specify a custom separator (if none is supplied, a comma "," is used)

What can be done with it

If we assume we have a default object to use as T (which can be any class that has properties) that is like this:

public class Person
{
    public int Age { get; set; }
    public String Name { get; set; }

    public Person(int age, String name)
    {
        this.Age = age;
        this.Name = name;
    }
}

And we have a List<Person> object configured as follows:

List<Person> people = new List<Person>();
people.Add(new Person(1, "sam"));
people.Add(new Person(2, "john"));
people.Add(new Person(3, "paul"));

Then using my little exporter, we can carry out the following functions, where the code snippets below are actually working examples of how to use it for the scenarios outlined below.

Export to CSV string

We can export the data of a List<Person> to a CSV string as follows:

Use default headers and use no formatting on the exported data

Note: In this example, I supply a custom separator of ":" to use.

//Get it as a String result, using default Headers, and default Columns, 
//and custom seperator
using (StringWriter writer = new StringWriter())
{
    people.GetExporter(":")
        .AddExportableColumn((x) => x.Age)
        .AddExportableColumn((x) => x.Name)
        .AsCSVString(writer);

    String resultsWithDefaultHeadersAndDefaultColumns = writer.ToString();
}

Which produces the following output:

Age:Name
  1:sam
  2:john
  3:paul

Use default headers and use formatting on the exported data

//Get it as a String result, using automatic Headers, but formatted Columns, 
//and standard "," seperator
using (StringWriter writer = new StringWriter())
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age, customFormatString: "The Person Age Is {0}")
        .AddExportableColumn((x) => x.Name, customFormatString: "The Persons Name {0}")
        .AsCSVString(writer);

    String resultsWithDefaultHeadersAndFormattedColumns = writer.ToString();
}

Which produces the following output:

Age,Name
  The Person Is 1,The Person Name Is sam
  The Person Is 2,The Person Name Is john
  The Person Is 3,The Person Name Is paul

Use custom headers and use no formatting on the exported data

//Get it as a String result, using custom Headers, but default Columns
//and standard "," seperator
using (StringWriter writer = new StringWriter())
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age, headerString: "AgeColumn")
        .AddExportableColumn((x) => x.Name, headerString: "NameColumn")
        .AsCSVString(writer);

    String resultsWithCustomHeadersAndDefaultColumns = writer.ToString();
}

Which produces the following output:

AgeColumn,NameColumn
  1,sam
  2,john
  3,paul

Export to CSV file

We can also choose to export to a CSV file using the little helper class, where we can do things like this:

Use default headers and use no formatting on the exported data

//Get it as a CSV file, using default Headers, and default Columns, 
//and standard "," seperator
using (StreamWriter writer = 
     new StreamWriter(@"c:\temp\exportedWithDefaultHeadersAndDefaultColumns.csv"))
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age)
        .AddExportableColumn((x) => x.Name)
        .AsCSVString(writer);
}

Use default headers and use formatting on the exported data, which produces the following

//Get it as a CSV file, using automatic Headers, but formatted Columns, 
//and standard "," seperator
using (StreamWriter writer = 
	new StreamWriter(@"c:\temp\exportedWithDefaultHeadersAndFormattedColumns.csv"))
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age, customFormatString: "The Person Age Is {0}")
        .AddExportableColumn((x) => x.Name, customFormatString: "The Persons Name {0}")
        .AsCSVString(writer);
}

Use custom headers and use no formatting on the exported data

//Get it as a CSV file, using custom Headers, but default Columns
//and standard "," seperator
using (StreamWriter writer = 
	new StreamWriter(@"c:\temp\erxportedWithCustomHeadersAndDefaultColumns.csv"))
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age, headerString: "AgeColumn")
        .AddExportableColumn((x) => x.Name, headerString: "NameColumn")
        .AsCSVString(writer);
}

Export to XML file

We can also export to an XML file, thanks to some extra effort of one of the readers who posted to this article's forum. We can do something like this:

using (XmlTextWriter sw = new XmlTextWriter("LastResults.xml", 
	System.Text.Encoding.UTF8))
{
    people.GetExporter()
        .AddExportableColumn((x) => x.Age, headerString: "AgeColumn")
        .AddExportableColumn((x) => x.Name, headerString: "NameColumn")
        .ToXML(sw);
}

Note: We must have the following XSL file in the same folder as the binaries. The downloadable code includes this file for you.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" version="4.0" 
     encoding="ISO-8859-1" indent="no" omit-xml-declaration="yes"/>

  <xsl:template match="dump">
    <head>
      <meta NAME="ROBOTS" CONTENT="NOINDEX,NOFOLLOW"/>
      <meta HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE"/>
      <title>
        Dump (<xsl:value-of select="count(/dump/item)"/> items)
      </title>
      <style media="screen" type="text/css">
        body {
        background-color: #F1F0EB;
        font-family: Verdana, Tahoma, Helvetica, sans-serif;
        font-size: .8em;
        margin: 10, 10, 10, 10;
        }
        h1, h2, h3 {
        font-family: Tahoma, Arial, sans-serif;
        font-weight: bolder;
        }
        h2 {
        font-size: 2em;
        color: #A0A0A0;
        }
        table {
        border-width: 1px;
        border-spacing: 2px;
        border-style: solid;
        border-color: gray;
        border-collapse: separate;
        background-color: #F1F0EB;
        }
        table th {
        border-width: 0px;
        font-size: .9em;
        line-height: 1.2em;
        padding: 1px;
        border-style: none;
        border-color: gray;
        background-color: #F1F0EB;
        -moz-border-radius: ;
        }
        table td {
        border-width: 0px;
        padding: 1px;
        font-size: .9em;
        line-height: 1.2em;
        border-style: none;
        border-color: gray;
        background-color: white;
        -moz-border-radius: ;
        }
      </style>
    </head>
    <html>
      <body>
        <h2>List dump</h2>
        <p>
          Date: <xsl:value-of select="/dump/@date"/>
        </p>
        <table border="1">
          <thead>
            <tr bgcolor="#9acd32">
              <xsl:apply-templates select="item[1]/*" mode="th" />
            </tr>
          </thead>
          <tbody>
            <xsl:apply-templates select="item" />
          </tbody>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="item">
    <tr>
      <xsl:apply-templates select="*" mode="td" />
    </tr>

  </xsl:template>
  <xsl:template match="item/*" mode="th">
    <th>
      <xsl:value-of select="local-name()" />
    </th>
  </xsl:template>

  <xsl:template match="item/*" mode="td">
    <td>
      <xsl:value-of select="." />
    </td>
  </xsl:template>
</xsl:stylesheet>

Which when examined in a browser will look like this:

How does it work

Well, as some of you may have already guessed, the secret lies in using Expression trees/optional parameters and named parameters. But before we get into that, one thing of note is that by using this sort of technique, the control of what to export is firmly in the hands of the consumer of the export helper class contained in this article, which as I said is important, as by doing this, it is totally generic, and allows the user to specify what columns they want to export and also the order in which they are exported. And as can also be seen from the above examples, the user can choose to use custom column headers/format strings, or have them inferred (via expression tree parsing).

One reader suggested re-factoring it to allow the exporter as an extension method on IEnumerable<T>, so I added that. Thanks for that, David Sehnal.

The export also uses a Fluent interface which I talked about a bit in one of my older articles, which you can read here: http://www.codeproject.com/KB/WPF/fluentAPI.aspx.

Anyway without further ado, here is the complete code for the exporter code presented in this article:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.IO;
using System.Xml;

namespace FluentListExporterColumns
{

    public static class IEnumerableExtensions
    {
        /// <summary>
        /// Exporter extension method for all IEnumerableOfT
        /// </summary>
        public static FluentExporter<T> GetExporter<T>(
            this IEnumerable<T> source, String seperator = ",") where T : class
        {
            return new FluentExporter<T>(source, seperator);
        }
    }

    /// <summary>
    /// Represents custom exportable column with a expression for the property name
    /// and a custom format string
    /// </summary>
    public class ExportableColumn<T>
    {
        public Expression<Func<T, Object>> Func { get; private set; }
        public String HeaderString { get; private set; }
        public String CustomFormatString { get; private set; }

        public ExportableColumn(
            Expression<Func<T, Object>> func, 
            String headerString = "", 
            String customFormatString = "")
        {
            this.Func = func;
            this.HeaderString = headerString;
            this.CustomFormatString = customFormatString;
        }
    }

    /// <summary>
    /// Exporter that uses Expression tree parsing to work out what values to export for 
    /// columns, and will use additional data as specified in the List of ExportableColumn
    /// which defines whethere to use custom headers, or formatted output
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class FluentExporter<T> where T : class
    {
        private List<ExportableColumn<T>> columns = 
          new List<ExportableColumn<T>>();
        private Dictionary<Expression<Func<T, Object>>, Func<T, Object>> 
          compiledFuncLookup = 
          new Dictionary<Expression<Func<T, Object>>, Func<T, Object>>();
        private List<String> headers = new List<String>();
        private IEnumerable<T> sourceList;
        private String seperator;
        private bool doneHeaders;


        public FluentExporter(IEnumerable<T> sourceList, String seperator = ",")
        {
            this.sourceList = sourceList;
            this.seperator = seperator;
        }

        public FluentExporter<T> AddExportableColumn(
            Expression<Func<T, Object>> func,
            String headerString = "",
            String customFormatString = "")
        {
            columns.Add(new ExportableColumn<T>(
                             func,headerString,customFormatString));
            return this;
        }

        /// <summary>
        /// Export all specified columns as a string, 
        /// using seperator and column data provided
        /// where we may use custom or default headers 
        /// (depending on whether a custom header string was supplied)
        /// where we may use custom fomatted column data or default data 
        /// (depending on whether a custom format string was supplied)
        /// </summary>
        public void AsCSVString(TextWriter writer)
        {
            if (columns.Count == 0)
                throw new InvalidOperationException(
                    "You need to specify at least one column to export value");

            int i = 0;
            foreach (T item in sourceList)
            {
                List<String> values = new List<String>();
                foreach (ExportableColumn<T> exportableColumn in columns)
                {
                    if (!doneHeaders)
                    {
                        if (String.IsNullOrEmpty(exportableColumn.HeaderString))
                        {
                            headers.Add(GetPropertyName(exportableColumn.Func));
                        }
                        else
                        {
                            headers.Add(exportableColumn.HeaderString);
                        }

                        Func<T, Object> func = exportableColumn.Func.Compile();
                        compiledFuncLookup.Add(exportableColumn.Func, func);
                        if (!String.IsNullOrEmpty(exportableColumn.CustomFormatString))
                        {
                            var value = func(item);
                            values.Add(value != null ?
                                String.Format(exportableColumn.CustomFormatString, 
                                value.ToString()) : "");

                        }
                        else
                        {
                            var value = func(item);
                            values.Add(value != null ? value.ToString() : "");
                        }
                    }
                    else
                    {
                        if (!String.IsNullOrEmpty(exportableColumn.CustomFormatString))
                        {
                            var value = compiledFuncLookup[exportableColumn.Func](item);
                            values.Add(value != null ?
                                String.Format(exportableColumn.CustomFormatString, 
                                value.ToString()) : "");
                        }
                        else
                        {
                            var value = compiledFuncLookup[exportableColumn.Func](item);
                            values.Add(value != null ? value.ToString() : "");
                        }
                    }
                }
                if (!doneHeaders)
                {
                    writer.WriteLine(headers.Aggregate(
                          (start, end) => start + seperator + end));
                    doneHeaders = true;
                }

                writer.WriteLine(values.Aggregate(
                       (start, end) => start + seperator + end));
            }

        }

        // <summary>
        /// Export all specified columns as a XML string, using column data provided
        /// and use custom headers depending on whether a custom header string was supplied.
        /// Use custom formatted column data or default data depending
        /// on whether a custom format string was supplied.
        /// </summary>
        public void ToXML(XmlTextWriter writer)
        {
            if (columns.Count == 0)
                throw new InvalidOperationException(
                   "You need to specify at least one element to export value");

            foreach (T item in sourceList)
            {
                List<String> values = new List<String>();
                foreach (ExportableColumn<T> exportableColumn in columns)
                {
                    if (!doneHeaders)
                    {
                        if (String.IsNullOrEmpty(exportableColumn.HeaderString))
                        {
                            headers.Add(MakeXMLNameLegal(
                                    GetPropertyName(exportableColumn.Func)));
                        }
                        else
                        {
                            headers.Add(MakeXMLNameLegal(exportableColumn.HeaderString));
                        }

                        Func<T, Object> func = exportableColumn.Func.Compile();
                        compiledFuncLookup.Add(exportableColumn.Func, func);
                        if (!String.IsNullOrEmpty(exportableColumn.CustomFormatString))
                        {
                            var value = func(item);
                            values.Add(value != null ?
                                String.Format(exportableColumn.CustomFormatString, 
                                              value.ToString()) : "");

                        }
                        else
                        {
                            var value = func(item);
                            values.Add(value != null ? value.ToString() : "");
                        }
                    }
                    else
                    {
                        if (!String.IsNullOrEmpty(exportableColumn.CustomFormatString))
                        {
                            var value = compiledFuncLookup[exportableColumn.Func](item);
                            values.Add(value != null ?
                                String.Format(exportableColumn.CustomFormatString, 
                                              value.ToString()) : "");
                        }
                        else
                        {
                            var value = compiledFuncLookup[exportableColumn.Func](item);
                            values.Add(value != null ? value.ToString() : "");
                        }
                    }
                }
                if (!doneHeaders)
                {
                    writer.Formatting = Formatting.Indented;
                    writer.WriteStartDocument(true);
                    writer.WriteProcessingInstruction("xml-stylesheet", 
                           "type='text/xsl' href='dump.xsl'");
                    writer.WriteComment("List Exporter dump");

                    // Write main document node and document properties
                    writer.WriteStartElement("dump");
                    writer.WriteAttributeString("date", DateTime.Now.ToString());

                    doneHeaders = true;
                }

                writer.WriteStartElement("item");
                for (int i = 0; i < values.Count; i++)
                {
                    writer.WriteStartElement(headers[i]);
                    writer.WriteString(values[i]);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.Flush();
        }

        /// <summary>
        /// Export to file, using the AsCSVString() method to supply the exportable data
        /// </summary>
        public void WhichIsExportedToFileLocation(StreamWriter fileWriter)
        {
            AsCSVString(fileWriter);
        }

        /// <summary>
        /// Gets a Name from an expression tree that is assumed to be a
        /// MemberExpression
        /// </summary>
        private static string GetPropertyName<T>(
            Expression<Func<T, Object>> propertyExpression)
        {
            var lambda = propertyExpression as LambdaExpression;
            MemberExpression memberExpression;
            if (lambda.Body is UnaryExpression)
            {
                var unaryExpression = lambda.Body as UnaryExpression;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }
            else
            {
                memberExpression = lambda.Body as MemberExpression;
            }

            var propertyInfo = memberExpression.Member as PropertyInfo;

            return propertyInfo.Name;
        }

        private string MakeXMLNameLegal(string aString)
        {
            StringBuilder newName = new StringBuilder();

            if (!char.IsLetter(aString[0]))
                newName.Append("_");

            // Must start with a letter or underscore.
            for (int i = 0; i <= aString.Length - 1; i++)
            {
                if (char.IsLetter(aString[i]) || char.IsNumber(aString[i]))
                {
                    newName.Append(aString[i]);
                }
                else
                {
                    newName.Append("_");
                }
            }
            return newName.ToString();
        }
    }
}

The most important part of this to understand is that we use some neat .NET 4 optional properties, so we can assume default headers are wanted unless we are told otherwise. The only other thing of note is that we can actually obtain a Func delegate by compiling an Expression, and then call that and obtain a value for the property.

The only other significant part is how we obtain a property name from an expression tree, that is done by the GetPropertyName() method, and it is that, that is used to obtain a header name if we are using default headers.

I think the code is fairly self-explanatory, but if you get lost, just let me know, and I can try and flesh this article out a bit more if needs be.

That's all

I know this is a very small article, but I think it is actually quite a useful little utility that is quite flexible, and pretty easy to use. So if you think it may help you, please give it a vote.

History

  1. 28/03/2011 : Initial API.
  2. 29/03/2011 : Refactored to use Extension Methods on IEnumerable.
  3. 01/04/2011 : Refactored to accept TextWriter based classes for storage to save writing data several times.
  4. 05/04/2011 : Refactored to include AsXML() support that one reader posted on the forum. Thanks. Corridorwarrior.
  5. 06/04/2011 : Fixed typos for second string example output produced.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
Member
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
SuggestionSuggestionsmemberAndrew Rissing25 Jul '12 - 17:38 
You seem to have a plethora of suggestions...but for a man who has so many, what does he get? One more.
 
Another route you might take instead of compiling each one individually, would be to take all the information you have and generate an Expression of the form:
 
(T obj, TextWriter tw) =>
{
  tw.Write(obj.PropertyA);
  tw.Write(obj.PropertyB);
  ...
}
 
The idea is that you're using typed expression (which you can determine from your class type) and you're able to tie yourself to the correct type within the calls to TextWriter. Granted, you'll have to do something special for XmlTextWriter since it doesn't derive from TextWriter. The other special aspects of how you expect the data could be handled as well, but it would just require more code.
 
I know this is probably wayyyy down on your list of things to do. But it is just an observation of a way to improve the code.
GeneralRe: SuggestionsmvpSacha Barber25 Jul '12 - 21:36 
Seems sensible enough, if I find then time, I'll have a look at it
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2012
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

Questionsuggestion: .AddAllColumns()memberBJ L13 Sep '11 - 10:02 
I am using this, and its great!
I did run into a scenario where I have >20 properties in my class. I need each one of these properties exported into csv.
 
having to type .AddExportableColumn((x) => x.Age) for every property can become a hassle, esp when maintaining properties that could change(development).
What I have been trying to do, unsuccessfully, is to add a method .AddAllColumns that would loop through each property in a class, and add them to the columns list.
 
if someone could point me in the right direction, it would be appreciated.
 

here is my latest attempt
    public FluentExporter<T> AddAllColumns()
        {
            foreach (PropertyInfo prop in typeof(T).GetProperties())
            {
                string propName = prop.Name;
                object o = typeof(T).GetProperty(propName);
                Expression<Func<T, Object>> exCol = (System.Object x) => o;
                columns.Add(new ExportableColumn<T>(exCol, string.Empty, string.Empty));
            }
            return this;
        }

AnswerRe: suggestion: .AddAllColumns()mvpSacha Barber14 Sep '11 - 1:59 
I don't have too much time right now, but if I find some I'll try have a look. I have lots on though, so it may not happen, I think if you carry on you will get it working, you are on right track.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

AnswerRe: suggestion: .AddAllColumns()mvpSacha Barber14 Sep '11 - 2:04 
Actually thinking about what you have above I think it needs to be more like (in pseudo code'ish)
 
I think how you were dealing with the Property get was wrong, you will need to do that via reflection (which will make it slow)
 
public FluentExporter<T> AddAllColumns()
        {
            foreach (PropertyInfo prop in typeof(T).GetProperties())
            {
                string propName = prop.Name;
                PropertyInfo prop = typeof(T).GetProperty(propName);
                Expression<Func<T, Object>> exCol = (x) => prop.GetValue(x,null);
                columns.Add(new ExportableColumn<T>(exCol, string.Empty, string.Empty));
            }
            return this;
        }
 

Also by doing typeof(T).GetProperties() you will end up with loads of properties you may not want to export, ones that were inherited from other objects say.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralRe: suggestion: .AddAllColumns()memberThomas Guilbault19 Sep '11 - 16:05 
I was doing something very close a while ago and came up with the code so it doesn't use reflection to do the retrieval. It still needs reflection when initializing, but you can create the expression tree programmatically. This gets compiled so it has the same performance as the expressions you explicitly code.
 
public FluentExporter<T> addAllColumns()
{
    foreach (PropertyInfo prop in typeof(T).GetProperties())
    {
        var input = Expression.Parameter(typeof(T), "x");
        var propExpression = Expression.Property(input, prop);
        var converter = Expression.Convert(propExpression, typeof(Object));
 
        Expression<Func<T, object>> ex = Expression.Lambda<Func<T, object>>(converter, new ParameterExpression[] { input });
        columns.Add(new ExportableColumn<T>(ex, string.Empty, string.Empty));
    }
    return this;
}

GeneralRe: suggestion: .AddAllColumns()mvpSacha Barber19 Sep '11 - 19:59 
Yes that would do it too.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralMy vote of 5memberrahulrcn8 Jun '11 - 23:22 
My Vote of 5
GeneralGood stuff Sacha, slight modification for you.memberMember 456543310 Apr '11 - 10:28 
I had something like this working a while ago based on Comma Delimited String namespace.
but I never got it fluently working, it was a real pain to use.
Thanks for this one Sacha, nice and neat. I’ve just used it in my current project with a slight modification, as follows
 
e.g. property name in a class = UsersFirstName will be translated to Users First Name (in Excel) for the column title (header)
 
Alter your method adding a bool...
 
public void AsCSVString(TextWriter writer, bool readableFlag)
 

then in the code I've added
 
if (!doneHeaders)
{
if (String.IsNullOrEmpty(exportableColumn.HeaderString))
{
if (readableFlag)
headers.Add(ReadableString.ConvertString(GetPropertyName(exportableColumn.Func)));
else
headers.Add(GetPropertyName(exportableColumn.Func));
}
else
{
headers.Add(exportableColumn.HeaderString);
}
 

 
here's the static helper
 
public static class ReadableString
{
//given "ManchesterRoad" returns "Manchester Road"
public static string ConvertString(string source)
{
return System.Text.RegularExpressions.Regex.Replace(source, "((?<=[a-z])[A-Z]|[A-Z](?=[a-z]))", " $1").Trim();
}
}
 

Good to hear about your little one destined to become a skiier, nice one mate
GeneralRe: Good stuff Sacha, slight modification for you.mvpSacha Barber10 Apr '11 - 19:57 
Nice....Seems people are liking this one, I think its short but sweet. Cool.
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 6 Apr 2011
Article Copyright 2011 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid