public
Last active

Set FileHelper's FileHelperEngine.HeaderText via reflection

  • Download Gist
FileHelpersTypeExtensions.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
 
// see http://stackoverflow.com/questions/3975741/column-headers-in-csv-using-filehelpers-library/8258420#8258420
 
// ReSharper disable CheckNamespace
namespace FileHelpers
// ReSharper restore CheckNamespace
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class FieldTitleAttribute : Attribute
{
public FieldTitleAttribute(string name)
{
if (name == null) throw new ArgumentNullException("name");
Name = name;
}
 
public string Name { get; private set; }
}
 
public static class FileHelpersTypeExtensions
{
public static IEnumerable<string> GetFieldTitles(this Type type)
{
var fields = from field in type.GetFields(
BindingFlags.GetField |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
where field.IsFileHelpersField()
select field;
 
return from field in fields
let attrs = field.GetCustomAttributes(true)
let order = attrs.OfType<FieldOrderAttribute>().Single().GetOrder()
let title = attrs.OfType<FieldTitleAttribute>().Single().Name
orderby order
select title;
}
 
public static string GetCsvHeader(this Type type)
{
return String.Join(",", type.GetFieldTitles());
}
 
static bool IsFileHelpersField(this FieldInfo field)
{
return field.GetCustomAttributes(true)
.OfType<FieldOrderAttribute>()
.Any();
}
 
static int GetOrder(this FieldOrderAttribute attribute)
{
// Hack cos FieldOrderAttribute.Order is internal (why?)
var pi = typeof(FieldOrderAttribute)
.GetProperty("Order",
BindingFlags.GetProperty |
BindingFlags.Instance |
BindingFlags.NonPublic);
 
return (int)pi.GetValue(attribute, null);
}
}
}
Usage.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
[DelimitedRecord(","), IgnoreFirst(1)]
public class Person
{
// Must specify FieldOrder too
[FieldOrder(1), FieldTitle("Name")]
string name;
 
[FieldOrder(2), FieldTitle("Age")]
int age;
}
 
...
 
var engine = new FileHelperEngine<Person>
{
HeaderText = typeof(Person).GetCsvHeader()
};
 
...
 
engine.WriteFile(@"C:\people.csv", people);

Thanks for this gist Richard!

I found the "FieldTitle" attribute to be a bit redundant, so I just use the field name instead:

public static class FileHelperUtils {
    public static IEnumerable<string> GetFieldTitles(this Type type) 
    {
        var fields = from field in type.GetFields(
            BindingFlags.GetField |
            BindingFlags.Public |
            BindingFlags.NonPublic |
            BindingFlags.Instance)
                     where field.IsFileHelpersField()
                     select field;

        return from field in fields
               let attrs = field.GetCustomAttributes(true)
               let order = attrs.OfType<FieldOrderAttribute>().Single().GetOrder()
               let title = field.Name
               orderby order
               select title;
    }

    public static string GetCsvHeader(this Type type)
    {
        return String.Join(",", type.GetFieldTitles());
    }

    static bool IsFileHelpersField(this FieldInfo field)
    {
        return field.GetCustomAttributes(true)
            .OfType<FieldOrderAttribute>()
            .Any();
    }

    static int GetOrder(this FieldOrderAttribute attribute)
    {
        // Hack cos FieldOrderAttribute.Order is internal (why?)
        var pi = typeof(FieldOrderAttribute)
            .GetProperty("Order",
                BindingFlags.GetProperty |
                BindingFlags.Instance |
                BindingFlags.NonPublic);

        return (int)pi.GetValue(attribute, null);
    }
}

Np. We had to use FieldTitle because we were using property-backed fields and wanted uppercase column names in the CSV (the version of FileHelpers didn't support properties... not sure how this is possible in 2011)

Additional option: use a delimiter as seperator. Too bad the original delimiter is internal, so I had to create a custom class; Joiner.

Code:

    public static string GetCsvHeader(this Type type) {
        return String.Join(type.GetCustomAttributes(true).OfType<Joiner>().Single().Delimiter, type.GetFieldTitles());
    }

[AttributeUsage(AttributeTargets.Class)]
public sealed class Joiner : Attribute {
    public string Delimiter;

    /// <summary>Indicates that this class represents a delimited record. </summary>
    /// <param name="delimiter">The separator string used to split the fields of the record.</param>
    public Joiner(string delimiter) {
            this.Delimiter = delimiter;
    }
}

@FWest98
1. This is covered by the engine: use engine.GetFileHeader()
see https://github.com/MarcosMeli/FileHelpers/blob/master/FileHelpers/Engines/EngineBase.cs
2. DelimitedRecord has (now) a public accessible property Separator https://github.com/MarcosMeli/FileHelpers/commit/f46a39af4ff22c63e265bce8156bd304c2b878e4
=> type.GetCustomAttributes(true).OfType<DelimitedRecordAttribute>()
3. in older versions, you should be able to access this via DelimitedFileEngine(type).Options.Delimiter

so, no need for an additional attribute ;)

overall:
engine.GetFileHeader() seems to use field.FriendlyName from FieldBase, but I can't see how or if this can be invoked? Default is fieldName, else a part of the reflective field ... I don't understand that code atm. So perhaps @MarcosMeli or someone else might point it out ;) Perhaps it's much easier to add custom fieldTitles?!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.