Использование OutputFormatterWriteContext.WriterFactory для вывода UTF-8 с спецификацией - PullRequest
2 голосов
/ 22 мая 2019

Мне нужно вернуть дружественный Excel-файл csv с нелатинскими символами из реализации TextOutputFormatter. Следующий код показывает основные биты:

public class CsvOutputFormatter : TextOutputFormatter
{
    private readonly UTF8Encoding _encoding;

    public CsvOutputFormatter()
    {
        _encoding = new UTF8Encoding(true);
        SupportedEncodings.Add(_encoding);
        SupportedMediaTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/csv"));
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {

        var response = context.HttpContext.Response;

        response.Headers.Add("Content-Disposition", $"attachment; filename=test.csv");

        response.ContentType = "text/csv";
        var preamble = _encoding.GetPreamble();
        response.Body.Write(preamble, 0, preamble.Length);
        // this works
        //using (var writer = new StreamWriter(response.Body, _encoding))
        // this doesn't work
        using (var writer = context.WriterFactory(response.Body, _encoding))
        {
            var csv = new CsvWriter(writer);
            csv.Configuration.HasHeaderRecord = true;
            csv.Configuration.QuoteAllFields = true;
            csv.WriteRecords((IEnumerable<object>)context.Object);
            await writer.FlushAsync();
        }
    }

Мои основные вопросы: почему не выводится спецификация при использовании OutputFormatterWriteContext.WriterFactory?

Дополнительные вопросы:

  1. Какая дополнительная польза от использования OutputFormatterWriteContext.WriterFactory вместо обычного StreamWriter, который в этом случае прекрасно работает?

  2. Есть ли способ избежать явной записи спецификации, например есть писатель, автоматически вызывающий Encoding.GetPreamble()?

  3. Я знаю, что UTF-8 с BOM нестандартен, интересно, хотя есть ли способ избежать этого в этом случае?

1 Ответ

0 голосов
/ 22 мая 2019

Какая дополнительная польза от использования OutputFormatterWriteContext.WriterFactory вместо обычного StreamWriter, который в этом случае прекрасно работает?

На самом деле, вы можете написать HttpResponse.Body напрямую, если хотите. Дело в том, что, как описано в документе, не используйте WriterFactory, когда вы хотите записать двоичные данные.

Ядро ASP.NET использует HttpResponseStreamWriter для записи потока за сценой (см. MemoryPoolHttpResponseStreamWriterFactory ) , Эта реализация предоставляет несколько методов, которые очень похожи на StreamWriter. Но HttpResponseStreamWriter использует ArrayPool<> за капотом. Согласно этому документу , он должен улучшать производительность при частом создании и уничтожении массивов.

Мои основные вопросы: почему не выводится спецификация при использовании OutputFormatterWriteContext.WriterFactory?

Это потому, что HttpResponseStreamWriter вообще не пишет BOM:

/// <summary<
/// Writes to the  using the supplied .
/// <b>It does not write the BOM and also does not close the stream.</b>
/// </summary<
public class HttpResponseStreamWriter : TextWriter
{
    private const int MinBufferSize = 128;
    internal const int DefaultBufferSize = 16 * 1024;
    ...
}

Есть ли способ избежать явной записи спецификации, например, есть писатель, вызывающий Encoding.GetPreamble () автоматически?

Если вы используете встроенный OutputFormatterWriteContext.WriterFactory, я думаю, что ответ будет ДА . Вы должны написать заголовок спецификации самостоятельно, если хотите.


Наконец, вы не должны записывать заголовки в методе WriteResponseBodyAsync() . Это обязанность WriteResponseHeaders(ctx). Эти коды лучше перенести на WriteResponseHeaders(OutputFormatterWriteContext ctx ):

    public override void WriteResponseHeaders(OutputFormatterWriteContext ctx )
    {
        var response = ctx.HttpContext.Response;
        response.Headers.Add("Content-Disposition", $"attachment; filename=test.csv");
        response.ContentType = "text/csv";
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        var response = context.HttpContext.Response;
        var preamble = _encoding.GetPreamble();
        response.Body.Write(preamble, 0, preamble.Length);
        using (var writer = context.WriterFactory(response.Body, _encoding))
        {
            var csv = new CsvWriter(writer);
            csv.Configuration.HasHeaderRecord = true;
            csv.Configuration.QuoteAllFields =  true;
            csv.WriteRecords((IEnumerable<object>)context.Object);
            await writer.FlushAsync();
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...