¿Cómo crear un archivo CSV Excel C #? [cerrado]

132

Estoy buscando una clase para crear archivos CSV Excel.

Características esperadas:

  • Extremadamente simple de usar
  • Se escapa de comas y citas para que Excel las maneje bien
  • Exporta la fecha y la hora en formato a prueba de zona horaria

¿Conoces alguna clase capaz de esto?

Chris
fuente
12
es mejor plantear la pregunta en la parte PREGUNTA, y luego publicar su propia respuesta en la parte RESPUESTA. Asegúrese de agregar etiquetas y palabras clave en la pregunta para que se pueda buscar.
Cheeso
IMPORTANTE: también debe agregar comillas cuando haya DEVOLUCIONES DE TRANSPORTE en el "valor".
Alex
Gracias @Chris, una sugerencia si puedo, este código puede lanzar una KeyNotFoundException, por favor vea mi respuesta.
Joseph
Su mejor ejemplo ... pero cómo puedo agregar dos tablas en un solo archivo, significa que tengo una tabla de dos filas y otra tabla es de 10 filas y ambas tienen un nombre de columna único. Quiero agregar una tabla de dos filas en la parte superior y posterior espacio de dos líneas que quiero agregar segunda tabla.
Floki

Respuestas:

92

Versión ligeramente diferente que escribí usando la reflexión para mis necesidades. Tuve que exportar una lista de objetos a csv. En caso de que alguien quiera usarlo para el futuro.

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;

        }
    }

Muestra de uso: (actualizado por comentario)

CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList());
Response.Write(csv.Export());
neto
fuente
55
Era más como esto: List <BusinessObject> x = new List <BusinessObject> (); CsvExport <BusinessObject> x = new CsvExport <BusinessObject> (MUsers);
oculto el
55
¿De dónde vino su interfaz INullable?
Kilhoffer
Su mejor ejemplo ... pero cómo puedo agregar dos tablas en un solo archivo, significa que tengo una tabla de dos filas y otra tabla es de 10 filas y ambas tienen un nombre de columna único. Quiero agregar una tabla de dos filas en la parte superior y posterior espacio de dos líneas que quiero agregar segunda tabla.
Floki
2
Sé que la publicación original era de 2011, por lo que no estoy seguro de si fue posible en la versión .NET que se utilizó en ese momento. Pero, ¿por qué no eliminar el public string Export()método y cambiar el otro método a public string Export(bool includeHeaderLiner = true)(con un valor de parámetro predeterminado)? De nuevo, no estoy seguro de si los parámetros predeterminados estaban disponibles en 2011, pero el código actual me parece poco ortodoxo.
Kevin Cruijssen
19

Por favor perdoname

Pero creo que un repositorio público de código abierto es una mejor manera de compartir código y hacer contribuciones, correcciones y adiciones como "Arreglé esto, arreglé eso"

Así que hice un repositorio git simple con el código de inicio del tema y todas las adiciones:

https://github.com/jitbit/CsvExport

También agregué un par de soluciones útiles. Todos pueden agregar sugerencias, bifurcarlo para contribuir, etc., etc. etc. Envíenme sus tenedores para que los fusione nuevamente en el repositorio.

PD. Publiqué todos los avisos de copyright para Chris. @ Chris si estás en contra de esta idea, avísame, la mataré.

jitbit
fuente
11

Otra buena solución para leer y escribir archivos CSV es filehelpers (código abierto).

Jelle
fuente
NB: el soporte de Excel es solo para escenarios básicos : el soporte de Excel implementado actualmente es solo para escenarios básicos. Si necesita un formato personalizado, gráficos, etc., debe elegir un código personalizado. Se recomienda encarecidamente usar directamente la biblioteca NPOI
AK
6

¿Qué tal usar string.Join en lugar de todos los bucles foreach?

Hinek
fuente
String.Join solo funciona en string [], mientras que estoy usando algunas de las características de List <string>.
Chris
12
String.Join("," , List<string>)Funciona también.
Demencia el
6

Si a alguien le gustaría, convertí esto a un método de extensión en 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;
    }
}
KeyboardCowboy
fuente
5

Gran trabajo en esta clase. Simple y fácil de usar. Modifiqué la clase para incluir un título en la primera fila de la exportación; pensé que compartiría:

utilizar:

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

clase:

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());
    }
}
gnomo
fuente
3

Agregué ExportToStream para que el csv no tuviera que guardar primero en el disco duro.

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
fuente
3

he añadido

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);

}

El código anterior no funciona con versiones antiguas de .NET. Para la versión 3.5 de framework, use esta otra versión:

        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
fuente
2

¡Muchas gracias por eso! Modifiqué la clase a:

  • use un delimitador variable, en lugar de un código rígido
  • reemplazando todas las nuevas líneas (\ n \ r \ n \ r) en MakeValueCsvFriendly

Código:

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());

        }

    }
Munchies
fuente
1

La clase original tiene un problema, y ​​es que si desea agregar una nueva columna, recibirá KeyNotFoundException en el método Exportar. Por ejemplo:

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");
}

Para resolver esto, y usando la idea @KeyboardCowboy de usar la reflexión, modifiqué el código para permitir agregar filas que no tienen las mismas columnas. Puede usar instancias de clases anónimas. Por ejemplo:

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");
}

Puede descargar el código fuente aquí CsvExporter . Siéntase libre de usar y modificar.

Ahora, si todas las filas que desea escribir son de la misma clase, creé la clase genérica CsvWriter.cs , que tiene un mejor uso de RAM de rendimiento e ideal para escribir archivos grandes. Además, le permite agregar formateadores al tipo de datos que desee . Un ejemplo de uso:

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
fuente
0

Solo necesita 1 función para hacer esto. Solo tiene que hacer es crear una carpeta en su explorador de soluciones y almacenar el archivo csv allí y luego exportar ese archivo al usuario.

Como en mi caso tengo una carpeta de descargas. Primero exporto todo mi contenido a ese directorio y luego lo exporto al usuario. Para el manejo de respuesta y finalización, utilicé ThreadAbortException. Por lo tanto, es una función 100% genuina y funcional en mi solución.

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
fuente