Introduction: What is the goal?
In the past two months at work, I was tasked with learning C#, as well as creating a web app using the .NET Core 3.1 MVC framework. I wanted to document the most interesting concepts in a series of blog posts.
In this blog, I will show you how to create a custom fileresult to export data into a CSV file using streaming in a .NET Core 3.1 MVC web app. This came about as I was asked to give the user the ability to export their dataset into a CSV file through the web app, but there was a lot of data to deal with. The goal here was to create something to export to a CSV file taking performance into account.
The actual code was more complex; this blog is my attempt to abstract the core concepts into a simple web app using Pusheen the Cat as a fun example!
P.S. If you don't know who Pusheen is, I am utterly obsessed with it. You'll probably recognise Pusheen from numerous gifs and stickers on social media.
C# Streaming: What is it and why is it useful?
Imagine you had a lot of data to read/write as part of a web app solution; you probably want to break it down into bitesize pieces, as you won't want to read/write a large file in one go! This can be achieved through streaming.
A stream sits between the application and the file; the benefit of using streaming is the read/write operations are a lot smoother. When you're writing data to somewhere, it is written to the stream and then from the stream, it then goes to your chosen destination, usually a file. When you're reading data, you read to the stream and then your web app can then read from the stream.
If you like to read on a bit more, this article by Guru99 is particularly helpful.
"C# provides standard IO (Input/Output) classes to read/write from different sources like a file, memory, network, isolated storage, etc. System.IO.Stream is an abstract class that provides standard methods to transfer bytes (read, write, etc.) to the source. It is like a wrapper class to transfer bytes. Classes that need to read/write bytes from a particular source must implement the Stream class." - Extracted from TutorialsTeacher
Creating a Custom FileResult With Streaming in .NET Core 3.1
Why Create a Custom Fileresult in the First Place?
The .NET Core 3.1 MVC framework provides the ActionResult class. Here's the link to the docs for it. It implements the IActionResult interface. ActionResult is essentially the return type of a controller method, it is the base class for lots of result classes to return models to views, file streams and more! There are many derived classes to choose from, but there wasn't one to produce a CSV result. I wanted a FileResult which has streaming and exported to CSV.
The cool thing was one of the derived classes is FileResult. This class represents an ActionResult that when executed will write a file as the response. We can extend on this class to create a custom FileResult to create a CSV FileResult.
Example of Custom FileResult With Streaming in .NET Core 3.1
Here is an example of a PusheenCsvResult which extends FileResult. As it is specific to exporting data about Pusheen the Cat, we give it some pusheenData as IEnumerable. In it's constructor, we pass in that data along with the fileDownloadName and set the file type to be "text/csv".
As we're extending on FileResult, we override ExecuteResultAsync with our implementation. In this example, we're using StreamWriter to write to the response body of the HttpContext of the ActionContext. We write the header row for our CSV file and then iterate through our _pusheenData and write that data. As a reminder, the stream sits in between the application and in this case the response body; the data is written to the stream (response body) and then from the stream, it then results in the CSV file being produced.
We define the StreamWriter within a using block. We use StreamWriter.FlushAsync method to clear all buffers for the current writer and results in any buffered data to be written to the underlying stream.
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace PusheenCustomExportCsv.Web.Models
{
public class PusheenCsvResult : FileResult
{
private readonly IEnumerable<Pusheen> _pusheenData;
public PusheenCsvResult(IEnumerable<Pusheen> pusheenData, string fileDownloadName) : base("text/csv")
{
_pusheenData = pusheenData;
FileDownloadName = fileDownloadName;
}
public async override Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
context.HttpContext.Response.Headers.Add("Content-Disposition", new[] { "attachment; filename=" + FileDownloadName });
using (var streamWriter = new StreamWriter(response.Body)) {
await streamWriter.WriteLineAsync(
$"Pusheen, Food, SuperPower"
);
foreach (var p in _pusheenData)
{
await streamWriter.WriteLineAsync(
$"{p.Name}, {p.FavouriteFood}, {p.SuperPower}"
);
await streamWriter.FlushAsync();
}
await streamWriter.FlushAsync();
}
}
}
}
Using the Custom FileResult in the Controller
Now that we have the PusheenCsvResult class, we can go ahead and use it in the controller.
#The rest of the code has been omitted for brevity! :)
public FileResult ExportCsv()
{
return File(_pusheenService.GetAllPusheens(), "pusheen.csv");
}
public virtual PusheenCsvResult File(IEnumerable<Pusheen> pusheenData, string fileDownloadName)
{
return new PusheenCsvResult(pusheenData, fileDownloadName);
}
App Demo
Here's some screenshots of what it looks like from the front-end!
Final Thoughts
I hope you found this useful! I thought an improvement for this PusheenCsvResult class would be to make it generic, so that it can work with all kinds of datasets, not just the Pusheen dataset! That's for another day! :)
Watch out for my next blog on unit testing the Custom FileResult which Exports Data into a CSV file Using Streaming in a .NET Core 3.1 MVC App. I have a series of .NET Core MVC blogs coming up, so it should be exciting times!
Thank you for reading my blog! :)
Link to Github Repo
You can find the link to my Github repo with the simple web app example containing the custom fileresult here.
Top comments (0)