Szukam klasy do tworzenia plików CSV Excel.
Oczekiwane funkcje:
- Niezwykle prosty w użyciu
- Pomija przecinki i cudzysłowy, więc program Excel radzi sobie z nimi dobrze
- Eksportuje datę i godzinę w formacie chronionym przed strefą czasową
Czy znasz jakąś klasę zdolną do tego?
Odpowiedzi:
Nieco inną wersję napisałem wykorzystując refleksję na moje potrzeby. Musiałem wyeksportować listę obiektów do csv. Na wypadek, gdyby ktoś chciał go wykorzystać w przyszłości.
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; } }
Próbka użycia: (aktualizowana na komentarz)
CsvExport<BusinessObject> csv= new CsvExport<BusinessObject>(GetBusinessObjectList()); Response.Write(csv.Export());
źródło
public string Export()
metody i zmienić inną metodę napublic string Export(bool includeHeaderLiner = true)
(z domyślną wartością parametru). Ponownie nie jestem pewien, czy parametry domyślne były dostępne w 2011 roku, ale obecny kod wygląda dla mnie na ortodoksyjny.Proszę wybacz mi
Ale myślę, że publiczne repozytorium open-source to lepszy sposób na udostępnianie kodu i wnoszenie wkładów, poprawek i dodatków typu „Naprawiłem to, naprawiłem to”
Więc stworzyłem proste repozytorium git z kodu startera tematu i wszystkich dodatków:
https://github.com/jitbit/CsvExport
Sam dodałem też kilka przydatnych poprawek. Każdy mógł dodawać sugestie, rozwidlać go, aby wnieść swój wkład itp. Itd. Itp. Wyślij mi swoje widelce, abym scalił je z powrotem w repozytorium.
PS. Opublikowałem wszystkie informacje o prawach autorskich dla Chrisa. @Chris, jeśli jesteś przeciwny temu pomysłowi - daj mi znać, zabiję to.
źródło
Innym dobrym rozwiązaniem do odczytu i zapisu plików CSV jest pomoc do plików (open source).
źródło
Co powiesz na użycie string.Join zamiast wszystkich foreach pętli?
źródło
String.Join("," , List<string>)
działa również.Jeśli ktoś chciałby przekonwertować to na metodę rozszerzenia na 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; } }
źródło
świetna robota na tej klasie. Prosty i łatwy w użyciu. Zmodyfikowałem klasę tak, aby zawierała tytuł w pierwszym wierszu eksportu; pomyślałem, że podzielę się:
posługiwać się:
CsvExport myExport = new CsvExport(); myExport.addTitle = String.Format("Name: {0},{1}", lastName, firstName));
klasa:
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()); } }
źródło
istnieje biblioteka open source dla CSV, którą można pobrać za pomocą nuget: http://joshclose.github.io/CsvHelper/
źródło
Dodałem ExportToStream, więc csv nie musiał najpierw zapisywać na dysku twardym.
public Stream ExportToStream() { MemoryStream stream = new MemoryStream(); StreamWriter writer = new StreamWriter(stream); writer.Write(Export(true)); writer.Flush(); stream.Position = 0; return stream; }
źródło
dodałem
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); }
Poprzedni kod nie działa ze starymi wersjami .NET. Dla wersji 3.5 frameworka użyj tej innej wersji:
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()); }
źródło
Wielkie dzięki za to! Zmodyfikowałem klasę, aby:
MakeValueCsvFriendly
Kod:
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()); } }
źródło
Możesz również użyć ADO, aby to zrobić: http://weblogs.asp.net/fmarguerie/archive/2003/10/01/29964.aspx
źródło
Oryginalna klasa ma problem i oznacza to, że jeśli chcesz dodać nową kolumnę, otrzymasz KeyNotFoundException w metodzie Export. Na przykład:
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"); }
Aby rozwiązać ten problem i korzystając z idei @KeyboardCowboy polegającej na użyciu odbicia, zmodyfikowałem kod, aby umożliwić dodawanie wierszy, które nie mają tych samych kolumn. Możesz używać instancji klas anonimowych. Na przykład:
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"); }
Możesz pobrać kod źródłowy tutaj CsvExporter . Zapraszam do używania i modyfikowania.
Teraz, jeśli wszystkie wiersze, które chcesz zapisać, są tej samej klasy, utworzyłem ogólną klasę CsvWriter.cs , która ma lepsze wykorzystanie pamięci RAM i jest idealna do pisania dużych plików, a ponadto pozwala dodawać elementy formatujące do żądanego typu danych . Przykładowe zastosowanie:
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; } }
źródło
Aby to zrobić, potrzebujesz tylko jednej funkcji. Wystarczy, że utworzysz folder w eksploratorze rozwiązań i zapiszesz tam plik csv, a następnie wyeksportujesz ten plik do użytkownika.
Jak w moim przypadku mam folder do pobrania. Najpierw eksportuję całą zawartość do tego katalogu, a następnie eksportuję ją do użytkownika. Do obsługi response.end użyłem ThreadAbortException. Jest to więc w 100% oryginalna i działająca funkcja w moim rozwiązaniu.
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.... }
źródło