diff --git a/files-csharp/WriteToCsv/WriteToCsv/Contact.cs b/files-csharp/WriteToCsv/WriteToCsv/Contact.cs new file mode 100644 index 0000000000..b1c114085e --- /dev/null +++ b/files-csharp/WriteToCsv/WriteToCsv/Contact.cs @@ -0,0 +1,8 @@ +namespace WritingCSV +{ + public class Contact + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + } +} diff --git a/files-csharp/WriteToCsv/WriteToCsv/Employee.cs b/files-csharp/WriteToCsv/WriteToCsv/Employee.cs new file mode 100644 index 0000000000..d34be1b031 --- /dev/null +++ b/files-csharp/WriteToCsv/WriteToCsv/Employee.cs @@ -0,0 +1,46 @@ +using CsvHelper.Configuration.Attributes; + +namespace WritingCSV +{ + // The two class-level attributes take effect only through a + // configuration built with the class's attributes, for example + // CsvConfiguration.FromAttributes(CultureInfo.InvariantCulture); + // a CsvWriter built from a CultureInfo alone ignores them. + [Delimiter(";")] + [HasHeaderRecord(false)] + public class Employee + { + [Name("Identifier")] + [Index(0)] + public int Id { get; set; } + + [Name("Full Name")] + [Index(1)] + public string Name { get; set; } = string.Empty; + + [Index(2)] + [Format("yyyy-MM-dd")] + public DateTime Hired { get; set; } + + [Index(3)] + [CultureInfo("en-US")] + [Format("C")] + public decimal Salary { get; set; } + + [Index(4)] + [BooleanTrueValues("yes")] + [BooleanFalseValues("no")] + public bool Active { get; set; } + + [Index(5)] + [Constant("EU")] + public string Region { get; set; } = string.Empty; + + [Index(6)] + [NullValues("N/A")] + public string? Nickname { get; set; } + + [Ignore] + public string Notes { get; set; } = string.Empty; + } +} diff --git a/files-csharp/WriteToCsv/WriteToCsv/Person.cs b/files-csharp/WriteToCsv/WriteToCsv/Person.cs index 24da251395..1e98c6afc8 100644 --- a/files-csharp/WriteToCsv/WriteToCsv/Person.cs +++ b/files-csharp/WriteToCsv/WriteToCsv/Person.cs @@ -13,7 +13,7 @@ public class Person [Index(0)] public int Id { get; set; } [Index(2)] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Index(1)] public bool IsLiving { get; set; } [Format("yyyy-MM-dd")] diff --git a/files-csharp/WriteToCsv/WriteToCsv/PersonMap.cs b/files-csharp/WriteToCsv/WriteToCsv/PersonMap.cs new file mode 100644 index 0000000000..f70769a462 --- /dev/null +++ b/files-csharp/WriteToCsv/WriteToCsv/PersonMap.cs @@ -0,0 +1,14 @@ +using CsvHelper.Configuration; + +namespace WritingCSV +{ + public sealed class PersonMap : ClassMap + { + public PersonMap() + { + Map(p => p.Id).Name("Identifier").Index(0); + Map(p => p.IsLiving).Index(1); + Map(p => p.Name).Index(2); + } + } +} diff --git a/files-csharp/WriteToCsv/WriteToCsv/WriteMethods.cs b/files-csharp/WriteToCsv/WriteToCsv/WriteMethods.cs index 7254ea68eb..5b2e8babba 100644 --- a/files-csharp/WriteToCsv/WriteToCsv/WriteMethods.cs +++ b/files-csharp/WriteToCsv/WriteToCsv/WriteMethods.cs @@ -1,4 +1,5 @@ using CsvHelper; +using CsvHelper.Configuration; using System; using System.Collections.Generic; using System.Globalization; @@ -27,5 +28,111 @@ public static void WritePersonsWithDoB() } } + + public static List CreatePersons() => new() + { + new Person { Id = 1, IsLiving = true, Name = "John", DateOfBirth = Convert.ToDateTime("03/05/2006") }, + new Person { Id = 2, IsLiving = true, Name = "Steve", DateOfBirth = Convert.ToDateTime("03/09/1998") }, + new Person { Id = 3, IsLiving = true, Name = "James", DateOfBirth = Convert.ToDateTime("03/08/1994") } + }; + + public static void WriteWithDefaultSettings(string path = "filePersons.csv") + { + using (var writer = new StreamWriter(path)) + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + csv.WriteRecords(CreatePersons()); + } + } + + public static void WriteWithoutHeader(string path = "filePersons.csv") + { + var configPersons = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = false + }; + + using (var writer = new StreamWriter(path)) + using (var csv = new CsvWriter(writer, configPersons)) + { + csv.WriteRecords(CreatePersons()); + } + } + + public static void WriteWithClassMap(string path = "filePersons.csv") + { + using var writer = new StreamWriter(path); + using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); + + csv.Context.RegisterClassMap(); + csv.WriteRecords(CreatePersons()); + } + + public static void AppendPersons(string path = "filePersons.csv") + { + var configPersons = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = false + }; + + using (var stream = File.Open(path, FileMode.Append)) + using (var writer = new StreamWriter(stream)) + using (var csv = new CsvWriter(writer, configPersons)) + { + csv.WriteRecords(CreatePersons()); + } + } + + public static string WriteTrickyValues() + { + var trickyContacts = new List() + { + new Contact { Id = 1, Name = "Doe, John" }, + new Contact { Id = 2, Name = "He said \"hi\"" }, + new Contact { Id = 3, Name = "line1\nline2" } + }; + + using var writer = new StringWriter(); + using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + { + csv.WriteRecords(trickyContacts); + } + + return writer.ToString(); + } + + public static async Task WritePersonsAsync(string path = "filePersons.csv") + { + await using var writer = new StreamWriter(path); + await using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); + + await csv.WriteRecordsAsync(CreatePersons()); + } + + public static string WriteEmployees(IWriterConfiguration configuration) + { + var employees = new List() + { + new Employee + { + Id = 1, + Name = "Doe, John", + Hired = new DateTime(2020, 3, 5), + Salary = 1234.50m, + Active = true, + Region = "US", + Nickname = null, + Notes = "not written" + } + }; + + using var writer = new StringWriter(); + using (var csv = new CsvWriter(writer, configuration)) + { + csv.WriteRecords(employees); + } + + return writer.ToString(); + } } } diff --git a/files-csharp/WriteToCsv/WriteToCsv/WriteToCsv.csproj b/files-csharp/WriteToCsv/WriteToCsv/WriteToCsv.csproj index 0658f11724..45c7dd411b 100644 --- a/files-csharp/WriteToCsv/WriteToCsv/WriteToCsv.csproj +++ b/files-csharp/WriteToCsv/WriteToCsv/WriteToCsv.csproj @@ -2,13 +2,13 @@ Exe - net6.0 + net10.0 enable enable - + diff --git a/files-csharp/WriteToCsv/WriteToCsvTests/WriteMethodsTests.cs b/files-csharp/WriteToCsv/WriteToCsvTests/WriteMethodsTests.cs new file mode 100644 index 0000000000..a374de81fa --- /dev/null +++ b/files-csharp/WriteToCsv/WriteToCsvTests/WriteMethodsTests.cs @@ -0,0 +1,142 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CsvHelper.Configuration; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using WritingCSV; + +namespace WriteToCsvTests +{ + [TestClass] + public class WriteMethodsTests + { + private static readonly string[] DataRows = + { + "1,True,John,2006-03-05", + "2,True,Steve,1998-03-09", + "3,True,James,1994-03-08" + }; + + private static string NewTempPath() => + Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + [TestMethod] + public void GivenPersons_WhenWritingWithDefaultSettings_ThenHeaderComesFromTheClass() + { + var path = NewTempPath(); + try + { + WriteMethods.WriteWithDefaultSettings(path); + + var expected = "Identifier,IsLiving,Name,DateOfBirth\r\n" + string.Join("\r\n", DataRows) + "\r\n"; + Assert.AreEqual(expected, File.ReadAllText(path)); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + public void GivenNoHeaderConfig_WhenWriting_ThenFirstLineIsData() + { + var path = NewTempPath(); + try + { + WriteMethods.WriteWithoutHeader(path); + + CollectionAssert.AreEqual(DataRows, File.ReadAllLines(path)); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + public void GivenClassMap_WhenWriting_ThenHeaderAndOrderFollowTheMap() + { + var path = NewTempPath(); + try + { + WriteMethods.WriteWithClassMap(path); + + var expected = "Identifier,IsLiving,Name\r\n1,True,John\r\n2,True,Steve\r\n3,True,James\r\n"; + Assert.AreEqual(expected, File.ReadAllText(path)); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + public void GivenAFileWithoutHeader_WhenAppending_ThenRowsAreAddedAtTheEnd() + { + var path = NewTempPath(); + try + { + WriteMethods.WriteWithoutHeader(path); + WriteMethods.AppendPersons(path); + + CollectionAssert.AreEqual(DataRows.Concat(DataRows).ToArray(), File.ReadAllLines(path)); + } + finally + { + File.Delete(path); + } + } + + [TestMethod] + public void GivenTrickyValues_WhenWriting_ThenFieldsAreQuotedAndQuotesDoubled() + { + var expected = "Id,Name\r\n" + + "1,\"Doe, John\"\r\n" + + "2,\"He said \"\"hi\"\"\"\r\n" + + "3,\"line1\nline2\"\r\n"; + + Assert.AreEqual(expected, WriteMethods.WriteTrickyValues()); + } + + [TestMethod] + public async Task GivenPersons_WhenWritingAsync_ThenFileMatchesTheSynchronousWrite() + { + var syncPath = NewTempPath(); + var asyncPath = NewTempPath(); + try + { + WriteMethods.WriteWithDefaultSettings(syncPath); + await WriteMethods.WritePersonsAsync(asyncPath); + + CollectionAssert.AreEqual(File.ReadAllBytes(syncPath), File.ReadAllBytes(asyncPath)); + } + finally + { + File.Delete(syncPath); + File.Delete(asyncPath); + } + } + + [TestMethod] + public void GivenAttributedEmployee_WhenWritingWithACulture_ThenClassLevelAttributesAreIgnored() + { + var csv = WriteMethods.WriteEmployees(new CsvConfiguration(CultureInfo.InvariantCulture)); + + var expected = "Identifier,Full Name,Hired,Salary,Active,Region,Nickname\r\n" + + "1,\"Doe, John\",2020-03-05,\"$1,234.50\",yes,EU,N/A\r\n"; + Assert.AreEqual(expected, csv); + } + + [TestMethod] + public void GivenAttributedEmployee_WhenWritingFromAttributes_ThenClassLevelAttributesApply() + { + var configuration = CsvConfiguration.FromAttributes(CultureInfo.InvariantCulture); + + var csv = WriteMethods.WriteEmployees(configuration); + + Assert.AreEqual("1;Doe, John;2020-03-05;$1,234.50;yes;EU;N/A\r\n", csv); + } + } +} diff --git a/files-csharp/WriteToCsv/WriteToCsvTests/WriteToCsvTests.csproj b/files-csharp/WriteToCsv/WriteToCsvTests/WriteToCsvTests.csproj index 968e48e18d..c21fac0092 100644 --- a/files-csharp/WriteToCsv/WriteToCsvTests/WriteToCsvTests.csproj +++ b/files-csharp/WriteToCsv/WriteToCsvTests/WriteToCsvTests.csproj @@ -1,18 +1,18 @@ - net6.0 + net10.0 enable false - - - - - + + + + +