Search code examples
c#.netcsvanonymous-typesfilehelpers

.NET how to output csv from enumeration of anonymous type?


Using FileHelpers, I decorated a class with [DelimitedRecord(",")] and was going to output an enumeration of objects of that type as CSV. But, it didn't work because my class inherits from ActiveRecordLinqBase<T>, which caused some problems.

So, I was wondering if I could just select an enumeration of anonymous types and somehow have filehelpers generate csv from that. I don't like having to define a class just for FileHelpers to output csv.

I would be open to using another csv library, but FileHelpers is proven.

EDIT

@Foovanadil: This would be the sort of thing I am trying to do:

CreateCSV(MyCollection.Select(x=>new{
    x.Prop1,
    x.Prop2,
    x.Prop3
}));

Gives you:

Prop1,Prop2,Prop3
val1a,val2a,val3a
val1b,val2b,val3b
etc.....

Solution

  • LINQ To CSV worked great for me.

    Here's an example of how I am using it:

    protected void Page_Load(object sender, EventArgs e)
    {
        var courseId = int.Parse(Request["id"]);
        var course = Course.Queryable.Single(x => x.Id == courseId);
        Response.ContentType = "text/csv";
        Response.AddHeader("Content-Disposition", string.Format("attachment;filename=\"{0}.csv\";", course.Code));
    
        var csvContext = new LINQtoCSV.CsvContext();
        var writer = new System.IO.StreamWriter(Response.OutputStream);
        csvContext.Write(course.Registrations.Select(x => new
        {
            x.StudentId,
            x.Name,
            x.EmailAddress,
            x.MoodleUsername,
            x.Age,
            x.Is65OrOlder,
            x.CertificationAndRank,
            x.Citizenship,
            x.DateOfBirth,
            x.DepartmentName,
            x.StationNumber,        
            x.EmploymentStatus,
            x.HighestEducationLevel
        }), writer);        
    
        writer.Dispose();
    }
    

    UPDATE

    There are some downsides to the approach above:

    • The column order in the csv file is unpredictable. It doesn't follow the order of the property definitions in the anonymous type.
    • Column headers come from the property names which aren't always what is is desired.

    So, I decided I would create a class just for the CSV records, which didn't end up being any more work than the anonymous type did. I used Auto Mapper to flatten my source class and populate the property values of the CSV class. I also decided to compare FileHelpers to Linq To CSV. Linq To CSV was the obvious winner, in my situation:

    • L2CSV allowed you apply an attribute to each property in the class for defining the column order, column header title, and conversion formats.
    • FHs would only let you supply a conversion format for each field. The column order depended on the order of the properties as they are defined in the class.
    • FHs would not infer a column header from the property name nor let you supply one. You could supply a literal string as the header for the CSV file, which is no good: The delimiter is built into the literal string; the column titles' order is not synced with the order of the properties.

    I hope these findings are useful. Here is my new code:

    // CSV Class
    
    public class CsvRegistration
    {
        [CsvColumn(FieldIndex = 0)]
        public string Name { get; set; }
    
        [CsvColumn(FieldIndex = 1, Name = "Student Id")]
        public int StudentId { get; set; }
    
        [CsvColumn(FieldIndex = 2, Name = "Email Address")]
        public string EmailAddress { get; set; }
    
        [CsvColumn(FieldIndex = 3, Name = "Moodle Username")]
        public string MoodleUsername { get; set; }
    
        [CsvColumn(FieldIndex = 4, Name = "Dept. Name")]
        public string DepartmentName { get; set; }
    
        [CsvColumn(FieldIndex = 5, Name = "Station #")]
        public string StationNumber { get; set; }
    
        [CsvColumn(FieldIndex = 6, Name = "Highest Education Level")]
        public string HighestEducationLevel { get; set; }
    
        [CsvColumn(FieldIndex = 7, Name = "Certification/Rank")]
        public string CertificationAndRank { get; set; }
    
        [CsvColumn(FieldIndex = 8, Name = "Employment Status")]
        public string EmploymentStatus { get; set; }
    
        [CsvColumn(FieldIndex = 9, Name = "Registration Date")]
        public DateTime RegistrationDate { get; set; }
    
        [CsvColumn(FieldIndex = 10, Name = "Date of Birth")]
        public DateTime DateOfBirth { get; set; }
    
        [CsvColumn(FieldIndex = 11)]
        public int Age { get; set; }
    
        [CsvColumn(FieldIndex = 12)]
        public string Citizenship { get; set; }
    
        [CsvColumn(FieldIndex = 13)]
        public string Race { get; set; }
    
        [CsvColumn(FieldIndex = 14)]
        public string Ethnicity { get; set; }
    
        [CsvColumn(FieldIndex = 15, Name = "Home Address")]
        public string HomeAddressLine1 { get; set; }
    
        [CsvColumn(FieldIndex = 16, Name = "City")]
        public string HomeAddressCity { get; set; }
    
        [CsvColumn(FieldIndex = 17, Name = "State")]
        public string HomeAddressState { get; set; }
    
        [CsvColumn(FieldIndex = 18, Name = "Zip")]
        public string HomeAddressZip { get; set; }
    
        [CsvColumn(FieldIndex = 19, Name = "County")]
        public string HomeAddressCounty { get; set; }
    
        [CsvColumn(FieldIndex = 20, Name = "Home Phone")]
        public string HomePhone { get; set; }
    
        [CsvColumn(FieldIndex = 21, Name = "Work Phone")]
        public string WorkPhone { get; set; }
    }
    
    
    // ASPX page to serve csv file
    
    protected void Page_Load(object sender, EventArgs e)
    {
        var courseId = int.Parse(Request["id"]);
        var course = Course.Queryable.Single(x => x.Id == courseId);
        Response.ContentType = "text/csv";
        Response.AddHeader("Content-Disposition", string.Format("attachment;filename=\"{0}.csv\";", course.Code));
    
        using (var writer = new System.IO.StreamWriter(Response.OutputStream))
        {
            var registrations = Mapper.Map<IEnumerable<Registration>, IEnumerable<CsvRegistration>>(course.Registrations);
            var cc = new LINQtoCSV.CsvContext();
            cc.Write(registrations, writer);
        }
    }