Reputation: 690
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
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