Comment créer un fichier CSV Excel C #? [fermé]

132

Je recherche une classe pour créer des fichiers Excel CSV.

Caractéristiques attendues:

  • Extrêmement simple à utiliser
  • Échappe les virgules et les guillemets pour exceller les gère bien
  • Exporte la date et les heures de données au format à l'épreuve du fuseau horaire

Connaissez-vous une classe capable de cela?

Chris
la source
12
mieux vaut poser la question dans la partie QUESTION, puis publier votre propre réponse dans la partie RÉPONSE. Assurez-vous d'ajouter des balises et des mots-clés dans la question pour la rendre consultable.
Cheeso
IMPORTANT: vous devez également ajouter des guillemets lorsqu'il y a des RETOURS CARRIAGE dans la "valeur".
Alex
Merci @Chris, une suggestion si je peux, ce code peut lancer une KeyNotFoundException, s'il vous plaît voir ma réponse.
Joseph
Son meilleur exemple ... mais comment puis-je ajouter deux tables dans un seul fichier, signifie que j'ai une table de deux lignes et une autre table est de 10 lignes et que les deux ont un nom de colonne unique.Je veux ajouter une table de deux lignes en haut et après écart de deux lignes je veux ajouter une deuxième table.
Floki

Réponses:

92

Version légèrement différente que j'ai écrite en utilisant la réflexion pour mes besoins. J'ai dû exporter une liste d'objets vers csv. Au cas où quelqu'un voudrait l'utiliser pour l'avenir.

public class CsvExport<T> where T: class
    {
        public List<T> Objects;

        public CsvExport(List<T> objects)
        {
            Objects = objects;
        }

        public string Export()
        {
            return Export(true);
        }

        public string Export(bool includeHeaderLine)
        {

            StringBuilder sb = new StringBuilder();
            //Get properties using reflection.
            IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

            if (includeHeaderLine)
            {
                //add header line.
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(propertyInfo.Name).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            //add value for each property.
            foreach (T obj in Objects)
            {               
                foreach (PropertyInfo propertyInfo in propertyInfos)
                {
                    sb.Append(MakeValueCsvFriendly(propertyInfo.GetValue(obj, null))).Append(",");
                }
                sb.Remove(sb.Length - 1, 1).AppendLine();
            }

            return sb.ToString();
        }

        //export to a file.
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        //export as binary data.
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());
        }

        //get the csv value for field.
        private string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is Nullable && ((INullable)value).IsNull) return "";

            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");
            }
            string output = value.ToString();

            if (output.Contains(",") || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';

            return output;

        }
    }

Exemple d'utilisation: (mis à jour par commentaire)

CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList());
Response.Write(csv.Export());
3 tours
la source
5
C'était plus comme ceci: List <BusinessObject> x = new List <BusinessObject> (); CsvExport <BusinessObject> x = nouveau CsvExport <BusinessObject> (MUsers);
masqué le
5
D'où vient votre interface INullable?
Kilhoffer du
Son meilleur exemple ... mais comment puis-je ajouter deux tables dans un seul fichier, signifie que j'ai une table de deux lignes et une autre table est de 10 lignes et que les deux ont un nom de colonne unique.Je veux ajouter une table de deux lignes en haut et après écart de deux lignes je veux ajouter une deuxième table.
Floki
2
Je sais que le message original datait de 2011, donc je ne suis pas sûr que c'était possible dans la version .NET qui était utilisée à l'époque. Mais pourquoi ne pas supprimer la public string Export()méthode et changer l'autre méthode en public string Export(bool includeHeaderLiner = true)(avec une valeur de paramètre par défaut). Encore une fois, je ne suis pas sûr si les paramètres par défaut étaient disponibles en 2011, mais le code actuel me semble juste orthodoxe.
Kevin Cruijssen
19

s'il vous plaît, pardonnez-moi

Mais je pense qu'un dépôt public open-source est un meilleur moyen de partager du code et d'apporter des contributions, des corrections et des ajouts tels que "J'ai corrigé ça, j'ai corrigé ça"

J'ai donc créé un simple git-repository à partir du code du topic-starter et de tous les ajouts:

https://github.com/jitbit/CsvExport

J'ai également ajouté moi-même quelques correctifs utiles. Tout le monde pourrait ajouter des suggestions, le bifurquer pour contribuer, etc. etc. etc. Envoyez-moi vos fourchettes pour que je les fusionne dans le repo.

PS. J'ai posté tous les avis de droits d'auteur de Chris. @Chris si vous êtes contre cette idée - faites-moi savoir, je vais la tuer.

jitbit
la source
11

Filehelpers (open source) est une autre bonne solution pour lire et écrire des fichiers CSV .

Jelle
la source
NB: la prise en charge d'Excel est uniquement pour les scénarios de base : La prise en charge d'Excel actuellement implémentée est uniquement pour les scénarios de base. Si vous avez besoin d'un formatage personnalisé, de graphiques, etc., vous devez opter pour un code personnalisé. Il est fortement recommandé d'utiliser directement la bibliothèque NPOI
AK
6

Que diriez-vous d'utiliser string.Join au lieu de toutes les boucles foreach?

Hinek
la source
String.Join ne fonctionne que sur string [], alors que j'utilise certaines des fonctionnalités de List <string>.
Chris
12
String.Join("," , List<string>)fonctionne aussi.
Dementic
6

Si quelqu'un souhaite que je convertisse cela en une méthode d'extension sur IEnumerable:

public static class ListExtensions
{
    public static string ExportAsCSV<T>(this IEnumerable<T> listToExport, bool includeHeaderLine, string delimeter)
    {
        StringBuilder sb = new StringBuilder();

        IList<PropertyInfo> propertyInfos = typeof(T).GetProperties();

        if (includeHeaderLine)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                sb.Append(propertyInfo.Name).Append(",");
            }
            sb.Remove(sb.Length - 1, 1).AppendLine();
        }

        foreach (T obj in listToExport)
        {
            T localObject = obj;

            var line = String.Join(delimeter, propertyInfos.Select(x => SanitizeValuesForCSV(x.GetValue(localObject, null), delimeter)));

            sb.AppendLine(line);
        }

        return sb.ToString();
    }

    private static string SanitizeValuesForCSV(object value, string delimeter)
    {
        string output;

        if (value == null) return "";

        if (value is DateTime)
        {
            output = ((DateTime)value).ToLongDateString();
        }
        else
        {
            output = value.ToString();                
        }

        if (output.Contains(delimeter) || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';

        output = output.Replace("\n", " ");
        output = output.Replace("\r", "");

        return output;
    }
}
ClavierCowboy
la source
5

excellent travail sur cette classe. Simple et facile à utiliser. J'ai modifié la classe pour inclure un titre dans la première ligne de l'exportation; pensé que je partagerais:

utilisation:

CsvExport myExport = new CsvExport();
myExport.addTitle = String.Format("Name: {0},{1}", lastName, firstName));

classe:

public class CsvExport
{
    List<string> fields = new List<string>();

    public string addTitle { get; set; } // string for the first row of the export

    List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
    Dictionary<string, object> currentRow
    {
        get
        {
            return rows[rows.Count - 1];
        }
    }

    public object this[string field]
    {
        set
        {
            if (!fields.Contains(field)) fields.Add(field);
            currentRow[field] = value;
        }
    }

    public void AddRow()
    {
        rows.Add(new Dictionary<string, object>());
    }

    string MakeValueCsvFriendly(object value)
    {
        if (value == null) return "";
        if (value is Nullable && ((INullable)value).IsNull) return "";
        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");
        }
        string output = value.ToString();
        if (output.Contains(",") || output.Contains("\""))
            output = '"' + output.Replace("\"", "\"\"") + '"';
        return output;

    }

    public string Export()
    {
        StringBuilder sb = new StringBuilder();

        // if there is a title
        if (!string.IsNullOrEmpty(addTitle))
        {
            // escape chars that would otherwise break the row / export
            char[] csvTokens = new[] { '\"', ',', '\n', '\r' };

            if (addTitle.IndexOfAny(csvTokens) >= 0)
            {
                addTitle = "\"" + addTitle.Replace("\"", "\"\"") + "\"";
            }
            sb.Append(addTitle).Append(",");
            sb.AppendLine();
        }


        // The header
        foreach (string field in fields)
        sb.Append(field).Append(",");
        sb.AppendLine();

        // The rows
        foreach (Dictionary<string, object> row in rows)
        {
            foreach (string field in fields)
                sb.Append(MakeValueCsvFriendly(row[field])).Append(",");
            sb.AppendLine();
        }

        return sb.ToString();
    }

    public void ExportToFile(string path)
    {
        File.WriteAllText(path, Export());
    }

    public byte[] ExportToBytes()
    {
        return Encoding.UTF8.GetBytes(Export());
    }
}
gnome
la source
3

J'ai ajouté ExportToStream pour que le csv n'ait pas à enregistrer d'abord sur le disque dur.

public Stream ExportToStream()
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(Export(true));
    writer.Flush();
    stream.Position = 0;
    return stream;
}
Jay Greene
la source
3

J'ai ajouté

public void ExportToFile(string path, DataTable tabela)
{

     DataColumnCollection colunas = tabela.Columns;

     foreach (DataRow linha in tabela.Rows)
     {

           this.AddRow();

           foreach (DataColumn coluna in colunas)

           {

               this[coluna.ColumnName] = linha[coluna];

           }

      }
      this.ExportToFile(path);

}

Le code précédent ne fonctionne pas avec les anciennes versions .NET. Pour la version 3.5 du framework, utilisez cette autre version:

        public void ExportToFile(string path)
    {
        bool abort = false;
        bool exists = false;
        do
        {
            exists = File.Exists(path);
            if (!exists)
            {
                if( !Convert.ToBoolean( File.CreateText(path) ) )
                        abort = true;
            }
        } while (!exists || abort);

        if (!abort)
        {
            //File.OpenWrite(path);
            using (StreamWriter w = File.AppendText(path))
            {
                w.WriteLine("hello");
            }

        }

        //File.WriteAllText(path, Export());
    }
Rodrigo Araujo
la source
2

Merci beaucoup pour ça! J'ai modifié la classe pour:

  • utiliser un délimiteur de variable au lieu d'être codé en dur dans le code
  • remplacement de toutes les nouvelles lignes (\ n \ r \ n \ r) dans MakeValueCsvFriendly

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

    public class CsvExport
    {

        public char delim = ';';
        /// <summary>
        /// To keep the ordered list of column names
        /// </summary>
        List<string> fields = new List<string>();

        /// <summary>
        /// The list of rows
        /// </summary>
        List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();

        /// <summary>
        /// The current row
        /// </summary>
        Dictionary<string, object> currentRow { get { return rows[rows.Count - 1]; } }

        /// <summary>
        /// Set a value on this column
        /// </summary>
        public object this[string field]
        {
            set
            {
                // Keep track of the field names, because the dictionary loses the ordering
                if (!fields.Contains(field)) fields.Add(field);
                currentRow[field] = value;
            }
        }

        /// <summary>
        /// Call this before setting any fields on a row
        /// </summary>
        public void AddRow()
        {
            rows.Add(new Dictionary<string, object>());
        }

        /// <summary>
        /// Converts a value to how it should output in a csv file
        /// If it has a comma, it needs surrounding with double quotes
        /// Eg Sydney, Australia -> "Sydney, Australia"
        /// Also if it contains any double quotes ("), then they need to be replaced with quad quotes[sic] ("")
        /// Eg "Dangerous Dan" McGrew -> """Dangerous Dan"" McGrew"
        /// </summary>
        string MakeValueCsvFriendly(object value)
        {
            if (value == null) return "";
            if (value is INullable && ((INullable)value).IsNull) return "";
            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");
            }
            string output = value.ToString();
            if (output.Contains(delim) || output.Contains("\""))
                output = '"' + output.Replace("\"", "\"\"") + '"';
            if (Regex.IsMatch(output,  @"(?:\r\n|\n|\r)"))
                output = string.Join(" ", Regex.Split(output, @"(?:\r\n|\n|\r)"));
            return output;
        }

        /// <summary>
        /// Output all rows as a CSV returning a string
        /// </summary>
        public string Export()
        {
            StringBuilder sb = new StringBuilder();

            // The header
            foreach (string field in fields)
                sb.Append(field).Append(delim);
            sb.AppendLine();

            // The rows
            foreach (Dictionary<string, object> row in rows)
            {
                foreach (string field in fields)
                    sb.Append(MakeValueCsvFriendly(row[field])).Append(delim);
                sb.AppendLine();
            }

            return sb.ToString();
        }

        /// <summary>
        /// Exports to a file
        /// </summary>
        public void ExportToFile(string path)
        {
            File.WriteAllText(path, Export());
        }

        /// <summary>
        /// Exports as raw UTF8 bytes
        /// </summary>
        public byte[] ExportToBytes()
        {
            return Encoding.UTF8.GetBytes(Export());

        }

    }
Grignotines
la source
1

La classe d'origine a un problème, et c'est si vous souhaitez ajouter une nouvelle colonne, vous recevrez KeyNotFoundException sur la méthode d'exportation. Par exemple:

static void Main(string[] args)
{
    var export = new CsvExport();

    export.AddRow();
    export["Region"] = "New York, USA";
    export["Sales"] = 100000;
    export["Date Opened"] = new DateTime(2003, 12, 31);

    export.AddRow();
    export["Region"] = "Sydney \"in\" Australia";
    export["Sales"] = 50000;
    export["Date Opened"] = new DateTime(2005, 1, 1, 9, 30, 0);
    export["Balance"] = 3.45f;  //Exception is throwed for this new column

    export.ExportToFile("Somefile.csv");
}

Pour résoudre ce problème, et en utilisant l'idée @KeyboardCowboy d'utiliser la réflexion, j'ai modifié le code pour permettre d'ajouter des lignes qui n'ont pas les mêmes colonnes. Vous pouvez utiliser des instances de classes anonymes. Par exemple:

static void Main(string[] args)
{
    var export = new CsvExporter();

    export.AddRow(new {A = 12, B = "Empty"});
    export.AddRow(new {A = 34.5f, D = false});

    export.ExportToFile("File.csv");
}

Vous pouvez télécharger le code source ici CsvExporter . N'hésitez pas à utiliser et à modifier.

Maintenant, si toutes les lignes que vous souhaitez écrire sont de la même classe, j'ai créé la classe générique CsvWriter.cs , qui a une meilleure utilisation de la RAM de performance et idéale pour écrire de gros fichiers. . Un exemple d'utilisation:

class Program
{
    static void Main(string[] args)
    {
        var writer = new CsvWriter<Person>("Persons.csv");

        writer.AddFormatter<DateTime>(d => d.ToString("MM/dd/yyyy"));

        writer.WriteHeaders();
        writer.WriteRows(GetPersons());

        writer.Flush();
        writer.Close();
    }

    private static IEnumerable<Person> GetPersons()
    {
        yield return new Person
            {
                FirstName = "Jhon", 
                LastName = "Doe", 
                Sex = 'M'
            };

        yield return new Person
            {
                FirstName = "Jhane", 
                LastName = "Doe",
                Sex = 'F',
                BirthDate = DateTime.Now
            };
        }
    }


    class Person
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public char Sex  { get; set; }

        public DateTime BirthDate { get; set; }
    }
Joseph
la source
0

Vous n'avez besoin que d'une seule fonction pour ce faire. Il vous suffit de créer un dossier dans votre explorateur de solutions et d'y stocker le fichier csv, puis d'exporter ce fichier vers l'utilisateur.

Comme dans mon cas, j'ai un dossier à télécharger. Tout d'abord, j'exporte tout mon contenu dans ce répertoire, puis je l'exporte vers l'utilisateur. Pour la gestion de response.end, j'ai utilisé l'exception ThreadAbortException. C'est donc une fonction 100% authentique et fonctionnelle dans ma solution.

protected void lnkExport_OnClick(object sender, EventArgs e)
{

    string filename = strFileName = "Export.csv";

    DataTable dt = obj.GetData();  

// call the content and load it into the datatable

    strFileName = Server.MapPath("Downloads") + "\\" + strFileName;

// creating a file in the downloads folder in your solution explorer

    TextWriter tw = new StreamWriter(strFileName);

// using the built in class textwriter for writing your content in the exporting file

    string strData = "Username,Password,City";

// above line is the header for your exported file. So add headings for your coloumns in excel(.csv) file and seperate them with ","

    strData += Environment.NewLine;

// setting the environment to the new line

    foreach (DataRow dr in dt.Rows)
    {
       strData += dr["Username"].ToString() + "," + dr["Password"].ToString() + "," +      dr["City"].ToString();
       strData += Environment.NewLine;
    }

// everytime when loop execute, it adds a line into the file
    tw.Write(strData);

// writing the contents in file
    tw.Close();

// closing the file
    Response.Redirect("Downloads/" + filename);

// exporting the file to the user as a popup to save as....
}
Suhaib Janjua
la source