Использование CSVHelper для вывода потока в браузер
Я пытаюсь использовать CSVHelper для генерации файла CSV и отправки его обратно в браузер, чтобы пользователь мог выбрать место сохранения и имя файла и сохранить данные.
Сайт основан на MVC. Вот код кнопки jQuery, который я использую для вызова (данные - это некоторое сериализованное представление Json списка DTO):
$.ajax({
type: "POST",
url: unity.baseUrl + "common/ExportPayments",
data: data
});
Вот код контроллера:
[HttpPost]
public FileStreamResult ExportPayments()
{
MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);
CsvWriter writer = new CsvWriter(sw);
List<Payment_dto> pd = _commonService.GetPayments();
foreach (var record in pd)
{
writer.WriteRecord(record);
}
sw.Flush();
return new FileStreamResult(ms, "text/csv");
}
Кажется, что это ничего не дает - вызов метода приводит к правильному фрагменту кода, но ответ пуст, не говоря уже о том, чтобы предложить пользователю диалоговое окно для сохранения данных. Я прошел этот код, и он возвращает данные из службы, записывает их и не выдает ошибок. Так что я делаю не так?
РЕДАКТИРОВАТЬ: Возвращая это...
return File(ms.GetBuffer(), "text/csv", "export.csv");
... дает мне ответ, состоящий из данных в формате CSV, которые я ожидаю. Но браузер все еще не знает, что с ним делать - пользователю не предлагается опция загрузки.
5 ответов
Попробуйте код ниже:
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(IEnumerable<Payment_dto> records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}
Обновить
Ниже описано, как передать модель сложного типа в метод действия, который использует GET
HTTP метод. Я не предпочитаю такой подход, он просто дает вам представление, что есть подход для достижения этого.
модель
public class Data
{
public int Id { get; set; }
public string Value { get; set; }
public static string Serialize(Data data)
{
var serializer = new JavaScriptSerializer();
return serializer.Serialize(data);
}
public static Data Deserialize(string data)
{
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<Data>(data);
}
}
Действие:
[HttpGet]
public FileStreamResult ExportPayments(string model)
{
//Deserialize model here
var result = WriteCsvToMemory(GetPayments());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
Посмотреть:
@{
var data = new Data()
{
Id = 1,
Value = "This is test"
};
}
@Html.ActionLink("Export", "ExportPayments", new { model = Data.Serialize(data) })
Основное решение ASP.NET:
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); // No 'using' around this as it closes the underlying stream. StreamWriter.Dispose() is only really important when you're dealing with actual files anyhow.
using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture, true)) // Note the last argument being set to 'true'
csvWriter.WriteRecords(...);
streamWriter.Flush(); // Perhaps not necessary, but CsvWriter's documentation does not mention whether the underlying stream gets flushed or not
memoryStream.Position = 0;
Response.Headers["Content-Disposition"] = "attachment; filename=somename.csv";
return File(memoryStream, "text/csv");
В ядре asp.net, чтобы не выделять память для всего выходного файла, вы можете создать собственный результат, например:
public class CsvResult : ActionResult
{
readonly Func<CsvWriter, Task> writeCsv;
readonly string fileName;
public CsvResult(
Func<CsvWriter, Task> writeCsv,
string fileName)
{
this.writeCsv = writeCsv;
this.fileName = fileName;
}
public override async Task ExecuteResultAsync(
ActionContext context)
{
var httpContext = context.HttpContext;
httpContext.Response.ContentType = "text/csv";
httpContext.Response.Headers.ContentDisposition = $"attachment; filename=\"{fileName}\"";
await using var writer = new StreamWriter(httpContext.Response.Body);
await using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
await writeCsv(csv);
}
}
Который затем можно использовать как:
public CsvResult Get()
{
return new CsvResult(
async csv =>
{
// your code to retrieve data
var records = await GetRecords(...);
await csv.WriteRecordsAsync(records);
},
"YourFilename.csv");
}
Попробуй в контроллере:
HttpContext.Response.AddHeader("content-disposition", "attachment; filename=payments.csv");
Может также использовать динамическое ключевое слово для преобразования любых данных
Код от @Lin
public FileStreamResult ExportPayments()
{
var result = WriteCsvToMemory(_commonService.GetPayments()());
var memoryStream = new MemoryStream(result);
return new FileStreamResult(memoryStream, "text/csv") { FileDownloadName = "export.csv" };
}
public byte[] WriteCsvToMemory(dynamic records)
{
using (var memoryStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memoryStream))
using (var csvWriter = new CsvWriter(streamWriter))
{
csvWriter.WriteRecords(records);
streamWriter.Flush();
return memoryStream.ToArray();
}
}