Пользовательский TextOutputFormatter не выбирается, если заголовок Accept не соответствует точно поддерживаемому типу носителя, а содержит только его

#c# #csv #asp.net-core #formatter

Вопрос:

Я пытаюсь добавить поддержку CSV в конечные точки списка в моем REST API.

Однако у меня проблема в том, что мой пользовательский форматер вызывается только тогда, когда заголовок Accept является точным text/csv . Это не сработает, если я добавлю кодировку или версию (API поддерживает управление версиями).

Так что ничего из этого не работает

 text/csv; charset=utf-8
text/csv; v=1.0
text/csv; v=2.0
text/csv; charset=utf-8; v=1.0
text/csv; charset=utf-8; v=2.0
 

Однако пользователи API могут отправить кодировку и версию, поэтому это должно поддерживаться.

Я уже пытался:

  • Вставьте форматер в первую очередь. Это приводит к тому, что он постоянно вызывается, хотя по умолчанию у меня должен быть json.
  • Добавление полных заголовков с кодировкой и версией в SupportedMediaTypes . Это вообще не имеет никакого значения.
  • Добавление text/csv; charset=utf-8; v=2.0 и так далее в [Produces] атрибут метода контроллера. Это работает, но я просто не знаю всех возможных комбинаций во время компиляции, поэтому это невозможно.

Это мой класс

 public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));

        // I tried to add these explicitly, but it does not change anything.
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv; charset=utf-8"));
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv; charset=utf-8; v=1.0"));
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv; charset=utf-8; v=2.0"));

        // UTF-8 is default, but all are supported if requested.
        foreach (var encodingInfo in Encoding.GetEncodings())
        {
            var encoding = encodingInfo.GetEncoding();
            if (encoding == Encoding.UTF8)
                SupportedEncodings.Insert(0, encoding);
            else
                SupportedEncodings.Add(encoding);
        }
    }

    public override IReadOnlyList<string> GetSupportedContentTypes(string contentType, System.Type objectType)
    {
        // This method only gets called during startup with either "application/hal json" or "text/csv".
        // On my Controller method I have the attribute [Produces("application/hal json", "text/csv")]
        return base.GetSupportedContentTypes(contentType, objectType);
    }

    protected override bool CanWriteType(System.Type type)
    {
        // This method only gets called if the Accept header is exactly "text/csv".
        return typeof(Resource).IsAssignableFrom(type);
    }

    public override bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        // This method only gets called if the Accept header is exactly "text/csv" if the formatter is at the end of the list.
        if (context.ContentType != "text/csv")
            return false;

        var resource = context.Object as Resource;
        if (resource == null)
            return false;

        if (resource.Embedded == null)
            return false;

        if (!resource.Embedded.ContainsKey(Common.Constants.ListItems))
            return false;

        return true;
    }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        // This method only gets called if the Accept header is exactly "text/csv" if the formatter is at the end of the list.
        write the csv...
    }
}
 

Вот как это зарегистрировано в моем Startup классе

 services
    .AddControllers(options =>
    {
        options.OutputFormatters.Add(new CsvOutputFormatter());
        // Does not matter if it is true or false, the result is the same.
        options.RespectBrowserAcceptHeader = true;
    })
 

Из журналов видно, что ASP считает, что форматирование нормально только в том случае, если заголовок Accept точно text/csv

 [09:08:40 DBG] List of registered output formatters, in the following order: ["Microsoft.AspNetCore.Mvc.Formatters.HttpNoContentOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StreamOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter", "RESTworld.AspNetCore.Formatter.CsvOutputFormatter"]
[09:08:40 DBG] Attempting to select an output formatter based on Accept header '["text/csv"]' and explicitly specified content types '["application/hal json", "text/csv"]'. The content types in the accept header must be a subset of the explicitly set content types.
[09:08:40 DBG] Selected output formatter 'RESTworld.AspNetCore.Formatter.CsvOutputFormatter' and content type 'text/csv' to write the response.
 

Если text/csv; charset=utf-8 это так, то он выбирает не мой форматер, а JSON по умолчанию.

 [09:10:13 DBG] List of registered output formatters, in the following order: ["Microsoft.AspNetCore.Mvc.Formatters.HttpNoContentOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StreamOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter", "RESTworld.AspNetCore.Formatter.CsvOutputFormatter"]
[09:10:13 DBG] Attempting to select an output formatter based on Accept header '["text/csv; charset=utf-8"]' and explicitly specified content types '["application/hal json", "text/csv"]'. The content types in the accept header must be a subset of the explicitly set content types.
[09:10:13 DBG] Could not find an output formatter based on content negotiation. Accepted types were (["text/csv; charset=utf-8"])
[09:10:13 DBG] Attempting to select the first output formatter in the output formatters list which supports a content type from the explicitly specified content types '["application/hal json", "text/csv"]'.
[09:10:13 DBG] Selected output formatter 'Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter' and content type 'application/hal json' to write the response.
 

Комментарии:

1. Вы хотите получать много типов одновременно, а затем оценивать типы, чтобы давать разные заголовки приема?