Использование 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();
    }
}
Другие вопросы по тегам