Thomas
August
Ryan

Work Posts Résumé

Using Sep to Export a CSV in Razor Pages

After years of using CSVHelper I learned about Sep earlier this year. Sep is a zero-allocation library for parsing CSV files in .NET. Not allocating additional memory for parsing is wonderful, especially when merely reading a CSV file can consume hundreds of MBs of memory.

For the web apps I write, my goal is to take a CSV file stored locally and then convert it into an array of objects in C#. That array will then get wrapped in a FrozenDictionary and passed around the app as singleton so I can perform lookups on it using a foreign key. The CSV starts its life as an export from another app that I want my app to link back to.

Serializing a CSV to an array of objects using Sep is straightforward. We start by creating a reader object, passing it a file path as a parameter, and then looping over rows to construct each object from the desired columns in the CSV. Getting the value of a column for the specific row is as simple as passing the row object the name of the column, like you would with a dictionary and then calling .ToString(). This makes it easy to process or format that value before building your object out of it.

using var reader = Sep.New(‘,’).Reader().FromFile(“pathToFile.csv”);

Sep also has support for ParallelEnumeration so that you can process the rows in your CSV in parallel, which reduces the amount of time it takes to get your array of objects together.

Using Sep for Exporting Data

Recently I built a status page in my app so that I could verify that it had all the data that it needed to work correctly. After showing this page to the team, they asked for an export to CSV button so they could play with the data in Excel. I turned to Sep as the perfect tool to implement this. But I hadn’t used Sep for writing CSVs before. Working through the readme.md on Github left me more confused. I decided to hunt for practical examples to reference as I wrote my own implementation.

Credit where credit was due, the example I needed was in the readme and in the section labeled 'example' but I’m going to expand on it here because I didn’t understand after reading it the first few times. If we start with an array of objects in C# we need to loop over them to construct our CSV row by row. That’s easy enough with a foreach loop, but we’ll also need to create a writer object using Sep to submit our rows to.

In the Sep readme they show creating a writer from an existing reader. But my status page doesn’t expect CSVs as input, so I don’t have a reader object to start from. To work around this, we can create our own writer object by calling the Sep library, passing it the delimiter we want to use (in this case the char ‘,’), and then calling for a writer object that produces text, rather than a file.

using var writer = Sep.New(‘,’).Writer().ToText();

foreach (var item in ArrayOfObjects) {

    using var row = writer.NewRow();
    row[“ThingIWant”].Set(item.ThingIWant);
    row[“NumberOfThings”].Set(item.Num.ToString());

}

Dealing with Dirty Data

When testing the CSV I realized that one of the columns had strings with commas in them, this broke the CSV output by formatting some rows with additional columns. Sep supports escaping columns using double quotes but doing so requires using the SepWriterOptions struct which was a bit awkward at first.

In other libraries you can modify the properties of your reader and writer objects by doting into them and then setting a new value, but that doesn’t work with Sep because the reader and writer objects are not simple classes. They are readonly record structs which prevents us from updating them after they’ve been created. We can use the ‘with’ operator in C# to create a copy of this struct with updated fields and replace the existing SepWriterOptions struct. This pattern isn’t explicitly in the readme.md but is clearly described in a supplemental blog post by Sep’s author on CSV Escape Support.

using var writer = Sep.New(‘,’).Writer(o => o with { Escape = true }).ToText();

Wrapping it up for Razor Pages

Now that I’ve written about reading and writing CSV with Sep here’s how I integrated this work with my .NET 10, ASP.NET Core Razor pages web app. I started by adding an OnPostAsCSV() route handler to the index.cs that is paired with my index.cshtml file in the Razor Pages project-style. The purpose of this route is to dump the data from the OnGet() route into a CSV. It takes no parameters and it returns a FileResult which is the CSV.

public FileResult OnPostAsCSV() {

    // Gather the data
    // Write the CSV using Sep

    return File(Encoding.UTF8.GetBytes(writer.ToString(),
     “text/csv”, $”AppName{DateTime.UtcNow}.csv”));
}

Back on the .cshtml page all we have to do is add an export button that calls the AsCSV route and is wrapped in a form set to POST.

<form method=”post”>
	<input type=”submit” asp-page-handler=”AsCSV” />
</form>

There you have it, an Export to CSV button for a Razer Pages web app built using the Sep library. Enjoy!