Bulchsu
Bulchsu

Reputation: 690

How to use streams, to generate csv file, using CsvHelper in .NET Core 3.1?

I am developing an api, which has to return csv file on some endpoint. Here's my controller responsible for csv generation:

    [ApiController]
    [Route("api/[controller]")]
    [Authorize]
    public sealed class ReportController : BaseController
    {
        public ReportController(ICommandBus commandBus,
            IQueryBus queryBus)
                : base(commandBus, queryBus)
        {
        }

        [HttpGet]
        public async Task<IActionResult> GetReportAsync([FromQuery] GenerateReportRequest request)
        {
            try
            {
                var report = await QueryBus
                    .SendAsync<GenerateReportQuery, Report>(new GenerateReportQuery
                    {
                        Filters = request.Filters,
                        ResponseFileFormat = request.ResponseFileFormat,
                        WithPodOnly = request.WithPodOnly
                    });

                return File(report.Content,
                    report.Type,
                    report.Name);
            }
            catch (Exception e)
            {
                // ToDo: Handle exception in proper way
                return StatusCode(StatusCodes.Status500InternalServerError,
                    e.Message);
            }
        }
    }

When the request comes to my api, certain handler is invoked, and the csv generation starts in CsvGenerationStrategy class, which is attached below:

    public class CsvGenerationStrategy : IReportGenerationStrategy
    {
        public async Task<Report> GenerateReportAsync(ICollection<ShipmentEntity> shipmentEntities)
        {
            var shipment = shipmentEntities
                .Select(s => (Shipment) s)
                .ToList();

            await using var memoryStream = new MemoryStream();
            await using var streamWriter = new StreamWriter(memoryStream);
            await using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
            
            csvWriter.Configuration.Delimiter = ";";
            
            await csvWriter.WriteRecordsAsync(shipment);
            var content = memoryStream.ToArray();

            var report = new Report
            {
                Content = content,
                Type = ReportConstants.CsvFileType,
                Name = ReportConstants.CsvReportFileName
            };

            return report;
        }

        private class Shipment
        {
            [Name(ReportConstants.IssueColumnName)]
            public string Issue { get; set; }
            [Name(ReportConstants.MaterialReleaseReceiptColumnName)]
            public string MaterialReleaseReceipt { get; set; }
            [Name(ReportConstants.FreightBillIssueColumnName)]
            public string FreightBillIssue { get; set; }
            [Name(ReportConstants.InvoiceNumberColumnName)]
            public string InvoiceNumber { get; set; }
            [Name(ReportConstants.TaxCodeColumnName)]
            public string TaxCode { get; set; }
            [Name(ReportConstants.ContractorIdColumnName)]
            public string ContractorId { get; set; }
            [Name(ReportConstants.AddressIdColumnName)]
            public string AddressId { get; set; }
            [Name(ReportConstants.ContractorNameColumnName)]
            public string ContractorName { get; set; }
            [Name(ReportConstants.ShipmentCountryColumnName)]
            public string ShipmentCountry { get; set; }

            public static explicit operator Shipment(ShipmentEntity entity) =>
                entity != null
                    ? new Shipment
                        {
                            Issue = entity.Issue,
                            MaterialReleaseReceipt = entity.MaterialReleaseReceipt,
                            FreightBillIssue = entity.FreightBillIssue,
                            InvoiceNumber = entity.InvoiceNumber,
                            TaxCode = entity.TaxCode,
                            ContractorId = entity.ContractorId,
                            AddressId = entity.AddressId,
                            ContractorName = entity.ContractorName,
                            ShipmentCountry = entity.ShipmentCountry
                        }
                    : null;
        }
    }

The code looks properly, but the behavior of the class is quite strange. In most cases, the generation runs properly, but few times i have noticed a situation, when the MemoryStream object contains no data, even if shipment collection is correct. I believe, such a behavior does not depend on data passed as a parameter. Probably i've made something wrong with the streams. How to use them properly? How to generate csv file correctly using CsvHelper library?

Upvotes: 1

Views: 4399

Answers (1)

Bulchsu
Bulchsu

Reputation: 690

I've found a solution. StreamWriter has to be flushed, after writing records, so now the function looks like:

        public async Task<Report> GenerateReportAsync(ICollection<ShipmentEntity> shipmentEntities)
        {
            var shipment = shipmentEntities
                .Select(s => (Shipment) s)
                .ToList();

            await using var memoryStream = new MemoryStream();
            await using var streamWriter = new StreamWriter(memoryStream);
            await using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
            
            csvWriter.Configuration.Delimiter = ";";
            
            await csvWriter.WriteRecordsAsync(shipment);
            await streamWriter.FlushAsync();

            var report = new Report
            {
                Content = memoryStream.ToArray(),
                Type = ReportConstants.CsvFileType,
                Name = ReportConstants.CsvReportFileName
            };

            return report;
        }

And it works properly :)

Upvotes: 1

Related Questions