.Net Core MVC — не может получить 406 — неприемлемо, всегда возвращает 200 OK с Json

Я хочу, чтобы мое приложение учитывало заголовок браузера и возвращало 406, если оно не соответствует формату ответа.

У меня есть эти параметры, установленные в конфигурации Mvc:

    /// <summary>
    /// This method gets called by the runtime. Use this method to add services to the container.
    /// </summary>
    /// <param name="services">The collection of the services.</param>
    /// <returns>The provider of the service.</returns>
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        // add mvc services
        services.AddMvc(options =>
                {
                    options.RespectBrowserAcceptHeader = true;
                    options.ReturnHttpNotAcceptable = true;

                    options.CacheProfiles.Add(
                        "CodebookCacheProfile",
                        new CacheProfile()
                        {
                            Duration = (int)TimeSpan.FromDays(1).TotalSeconds
                        });
                })
                .AddControllersAsServices()
                .AddJsonOptions(options =>
                {
                    options.SerializerSettings.Converters.Add(new StringEmptyToNullConverter());
                    options.SerializerSettings.Converters.Add(new StringEnumConverter(true));
                });

        // add response compression services
        services.AddResponseCompression(options =>
        {
            options.EnableForHttps = true;
            options.Providers.Add<GzipCompressionProvider>();
        });

        // add application services
        services.AddSwaggerDoc()
                .AddConfiguration(configuration)
                .AddModel()
                .AddDataAccess(configuration)
                .AddAuthentication(configuration)
                .AddDomainServices()
                .AddSchedulerContainer(() => serviceProvider);

        // initialize container
        serviceProvider = services.CreateServiceProvider();

        return serviceProvider;
    }

Когда я пытаюсь отправить запрос следующим образом: (с заголовком Accept, установленным на что угодно, например "text/xml")

Я всегда получаю 200 OK - с "application/json" Запрос с указанным заголовком принятия и ответом в формате json

Мой CountriesController выглядит так:

/// <summary>
/// REST API controller for actions with countries.
/// </summary>    
[AllowAnonymous]
[Area(Area.Common)]
[Route("[area]/Codebooks/[controller]")]
[ResponseCache(CacheProfileName = "CodebookCacheProfile")]
public class CountriesController : ApiController
{
    private readonly ICountryService countryService;

    /// <summary>
    /// Initializes a new instance of the <see cref="CountriesController" /> class.
    /// </summary>
    /// <param name="countryService">The country service.</param>
    public CountriesController(ICountryService countryService)
    {
        this.countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
    }

    /// <summary>
    /// Gets countries by search settings.
    /// </summary>
    /// <response code="200">The countries was returned correctly.</response>
    /// <response code="401">The unauthorized access.</response>
    /// <response code="406">The not acceptable format.</response>
    /// <response code="500">The unexpected error.</response>
    /// <param name="countrySearchSettings">The search settings of the country.</param>
    /// <returns>Data page of countries.</returns>        
    [HttpGet]
    [ProducesResponseType(typeof(IDataPage<Country>), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(void), StatusCodes.Status406NotAcceptable)]
    [ProducesResponseType(typeof(ApiErrorSummary), StatusCodes.Status500InternalServerError)]
    [SwaggerOperation("SearchCountries")]
    public IDataPage<Country> Get([FromQuery(Name = "")] CountrySearchSettings countrySearchSettings)
    {
        return countryService.Search(countrySearchSettings);
    }

    /// <summary>
    /// Gets a country.
    /// </summary>
    /// <response code="200">The country was returned correctly.</response>
    /// <response code="400">The country code is not valid.</response>
    /// <response code="401">The unauthorized access.</response>
    /// <response code="406">The not acceptable format.</response>
    /// <response code="500">The unexpected error.</response>
    /// <param name="countryCode">The code of the country.</param>
    /// <returns>Action result.</returns>   
    [HttpGet("{countryCode}")]
    [ProducesResponseType(typeof(Country), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ApiValidationErrorSummary), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(typeof(void), StatusCodes.Status406NotAcceptable)]
    [ProducesResponseType(typeof(ApiErrorSummary), StatusCodes.Status500InternalServerError)]
    [SwaggerOperation("GetCountry")]
    public IActionResult Get(string countryCode)
    {
        var country = countryService.GetByCode(countryCode);
        return Ok(country);
    }
}

У вас есть идеи, почему заголовок Accept запроса всегда игнорируется, а ответ всегда 200 OK с правильными данными Json? Что мне не хватает? Я думал, что настройка RespectBrowserAcceptHeader и ReturnHttpNotAcceptable подойдет... но, видимо, нет. Почему он всегда возвращается к форматировщику Json по умолчанию?


person Jakub    schedule 11.06.2018    source источник
comment
что возвращает ICountryService.Search?   -  person Sefe    schedule 11.06.2018
comment
IDataPage<Country>, который в основном представляет собой пользовательскую коллекцию... что-то вроде постраничного списка - он содержит IEnumerable<Country> и другие свойства, такие как Count, TotalPages, PageNumber, PageSize и т. д.   -  person Jakub    schedule 11.06.2018
comment
Где ваш код, который на самом деле генерирует результат 406? Когда вы всегда возвращаете коллекцию, вы всегда получаете 200.   -  person Sefe    schedule 11.06.2018
comment
Итак, мне нужно вручную проверить, что находится внутри заголовка accept, и вернуть 406? Или как сгенерировать 406? Я думал, что он будет автоматически возвращен, когда формат ответа отличается от того, что находится внутри заголовка accept.   -  person Jakub    schedule 11.06.2018
comment
Мои предположения основаны на том, что указано в здесь в разделе Процесс обсуждения содержания   -  person Jakub    schedule 11.06.2018


Ответы (2)


Чтобы ReturnHttpNotAcceptable работал, тип, возвращаемый действием, должен быть либо ObjectResult (например, Ok(retval)), либо типом, который не реализует IActionResult (в этом случае среда MVC завернет его в ObjectResult для вас).

Это связано с тем, что среда MVC проверяет значение ReturnHttpNotAcceptable только в ObjectResultExecutor, а не в какой-либо другой реализации IActionResultExecutor (например, ViewResultExecutor). (См. исходный код для ObjectResultExecutor и ViewResultExecutor)

Проще говоря, убедитесь, что возвращаемый вами тип не реализует (или не наследует от чего-либо, что реализует) IActionResult.

person 321_contact    schedule 10.01.2019
comment
Даже у меня была та же проблема, когда возвращаемый тип был IActionResult, но действие всегда возвращало экземпляр JsonResult. После того, как я изменился, чтобы вернуть экземпляр objectresult. Это сработало.!! - person neelesh bodgal; 22.06.2020

Добавьте следующую строку в метод ConfigureServices файла Startup.cs.

services.AddMvcCore().AddJsonFormatters().AddApiExplorer();
person Musab    schedule 10.09.2019
comment
Что это делает и почему это поможет ОП? - person Immortal Blue; 03.03.2021