Scope
The purpose of this article is to show a solution for a file exporter based in a list of items regardless of type.
Introduction
Sometime ago the sample Export To CSV was published for Windows Store apps, this solution is generic, is dynamic because it uses reflection and allows export of a list of items to a CSV file. Windows Store apps uses Windows Runtime and reflection is a bit different from reflection in WPF, ASP.NET, and so on.
In this article we will create a sample for a common .Net project.
Description
For help in the sample, let's create a specific class Person.
-
-
-
- public class Person
- {
-
-
-
-
- public string Name { get; set; }
-
-
-
-
-
- public int Age { get; set; }
-
-
-
-
-
- public DateTime Birthday { get; set; }
-
-
-
-
-
- public string Country { get; set; }
- }
In the class diagram we will have:
We will then create the Export class that will have a generic method, that uses a generic type T. Something such as the following:
-
-
-
- public static class Export
- {
-
-
-
-
-
-
-
-
- public static string Exporter<T>(bool includeHeaderLine, string separator, IEnumerable<T> items) where T : class
- {
- var sb = new StringBuilder();
-
-
- var properties = typeof(T).GetProperties();
-
- if (includeHeaderLine)
- {
-
- foreach (var property in properties)
- {
- sb.Append(property.Name).Append(separator);
- }
- sb.Remove(sb.Length - 1, 1).AppendLine();
- }
-
-
- foreach (T item in items)
- {
- foreach (var property in properties)
- {
- sb.Append(MakeValueFriendly(property.GetValue(item, null))).Append(separator);
- }
- sb.Remove(sb.Length - 1, 1).AppendLine();
- }
-
- return sb.ToString();
- }
-
-
-
-
-
-
- private static string MakeValueFriendly(object value)
- {
- if (value == null)
- {
- return string.Empty;
- }
-
- if (value is DateTime)
- {
- if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
- {
- return ((DateTime)value).ToString("yyyy-MM-dd");
- }
- return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
- }
- var output = value.ToString();
-
- if (output.Contains(",") || output.Contains("""))
- {
- output = '"' + output.Replace(""", """") + '"';
- }
-
- return output;
- }
- }
The method Exporter<T> will allow including a header with the properties name and allow to define the separador among each field value. It is very interesting because I can use the ListSeparator ( , or ; ), |, + or another we define.
The method MakeValueFriendly is only useful for format specific type like DateTime.
Now we can create a list of people and write it to a file, let's see how!
-
-
-
- public class Program
- {
-
-
-
-
- static void Main(string[] args)
- {
- var people = GetPeople();
- var basePath = Directory.GetCurrentDirectory();
-
- var fullPath = Path.Combine(basePath, "mycsvfile_includeHeader.csv");
-
-
- File.WriteAllText(fullPath, Export.Exporter(true, System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator, people));
-
- fullPath = Path.Combine(basePath, "myfile_includeHeader.txt");
-
-
- File.WriteAllText(fullPath, Export.Exporter(true, "+", people));
-
-
- fullPath = Path.Combine(basePath, "mycsvfile.csv");
-
-
- File.WriteAllText(fullPath, Export.Exporter(true, System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator, people));
-
- fullPath = Path.Combine(basePath, "myfile.txt");
-
-
- File.WriteAllText(fullPath, Export.Exporter(true, "|", people));
- }
-
-
-
-
-
- private static IEnumerable<Person> GetPeople()
- {
- return new List<Person>
- {
- new Person { Name = "Mary", Age = 22, Birthday = new DateTime(1992, 03, 24), Country = "Ireland" },
- new Person { Name = "Peter", Age = 10, Birthday = new DateTime(2004, 01, 05), Country = "Portugal" },
- new Person { Name = "Anne", Age = 50, Birthday = new DateTime(1964, 11, 03), Country = "Spain" }
- };
- }
- }
In the sample provided, we get the people and then write it in a CSV file and a TXT file, where we selected both cases (with header and without).
The output:
Running the console application we will get the following output.
The CSV file with header:
The CSV file without header:
The txt file with header:
The txt file without header: