c# excel csv export

¿Cómo crear un archivo CSV Excel C#?



export (13)

Estoy buscando una clase para crear archivos CSV Excel.

Características esperadas:

  • Extremadamente simple de usar
  • Escapes de comas y comillas para que Excel los maneje bien
  • Fecha y hora de las exportaciones en formato a prueba de zona horaria

¿Conoces alguna clase capaz de esto?


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

  • utilice un delimitador variable, en lugar de un código fijo en el código
  • reemplazando todas las líneas nuevas (/ 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()); } }


¿Qué hay de usar string.Join en lugar de todos los foreach Loops?


Agregué ExportToStream para que la 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; }


La clase original tiene un problema, y ​​es que si desea agregar una nueva columna, recibirá KeyNotFoundException en el método de exportación. 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 rendimiento en el uso de RAM e ideal para escribir archivos grandes. Además, le permite agregar formateadores al tipo de datos que desea . 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; } }


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


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 "lo arreglé, lo arreglé"

Así que hice un repositorio git simple del código del tema principal y todas las adiciones:

https://github.com/jitbit/CsvExport

También agregué un par de soluciones útiles a mí mismo. Todos pueden agregar sugerencias, teclear para contribuir, etc. etc. etc. Envíenme sus tenedores, así que los fusiono de nuevo en el repositorio.

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


Si alguien quisiera, lo convertí 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; } }


Solo necesita 1 función para hacer esto. Lo único que debe 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 response.end, 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.... }



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


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



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