dotnet / dotnet/aspnetcore

406 Not Accepted when returning ObjectResult with content type 'application/json; charset=utf-8'

Open
#56,357 1 comment 2 reactions 0 assignees View on GitHub
area-mvc
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

Returning `ObjectResult` from API action with `ContentTypes = ["application/json; charset=utf-8"]` results in `406 Not Accepted`.

It fails to match with `Microsoft.AspNetCore.Mvc.Formatters.NewtonsoftJsonOutputFormatter` due to the `; charset=utf-8` suffix.

By changing it to `ContentTypes = ["application/json"]`, it returns 200 OK with JSON response as expected.

## Why this happens
`NewtonsoftJsonOutputFormatter.CanWriteResult` returns false, because [`MediaTypeHeaderValue.IsSubsetOf()`](https://github.com/dotnet/aspnetcore/blob/main/src/Http/Headers/src/MediaTypeHeaderValue.cs#L385-L396) and [MediaTypeHeaderValue.MatchesParameters()](https://github.com/dotnet/aspnetcore/blob/main/src/Http/Headers/src/MediaTypeHeaderValue.cs#L783-L819) fail to recognize that `Accept: application/json` is compatible with `Content-Type: application/json; charset=utf-8`

Not sure if this is a bug or by design, but I expected it to be able to match regardless of charset - or at least if the charset matched what it would output itself.

Related issue on using IdempotentAPI to cache responses with this content type: https://github.com/ikyriak/IdempotentAPI/issues/78

## Sample code

```cs
[ApiController]
[Route("[controller]")]
public class FooController : ControllerBase
{
[HttpGet("fail")]
public IActionResult Fail()
{
return new ObjectResult(new
{
Message = """
This will fail with 406 Not Accepted due to unsupported response content type 'application/json; charset=utf-8':

Error message:
No output formatter was found for content types 'application/json; charset=utf-8, application/json; charset=utf-8' to write the response.
"""
})
{
ContentTypes = ["application/json; charset=utf-8"],
StatusCode = 200
};
}

[HttpGet("pass")]
public IActionResult Pass()
{
return new ObjectResult(new
{
Message = """
This will succeed due to supported response content type 'application/json'.
"""
})
{
ContentTypes = ["application/json"],
StatusCode = 200
};
}
}
```

## Log output

Key log entries:
```
Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector[9]
Attempting to select the first output formatter in the output formatters list which supports a content type from the explicitly specified content types 'application/json; charset=utf-8'.

Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[2]
No output formatter was found for content types 'application/json; charset=utf-8, application/json; charset=utf-8' to write the response.

Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 GET http://localhost:5176/foo/fail - 406 0 - 154.7296ms
```

Full log for GET http://localhost:5176/foo/fail

```
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 GET http://localhost:5176/foo/fail - - -
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]
Wildcard detected, all requests with hosts will be allowed.
trce: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[2]
All hosts are allowed.
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1001]
1 candidate(s) found for the request path '/foo/fail'
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1005]
Endpoint 'WebApplication2.Controllers.FooController.Fail (WebApplication2)' with route pattern 'Foo/fail' is valid for the request path '/foo/fail'
dbug: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[1]
Request matched endpoint 'WebApplication2.Controllers.FooController.Fail (WebApplication2)'
trce: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[8]
The endpoint does not specify the IRequestSizeLimitMetadata.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'WebApplication2.Controllers.FooController.Fail (WebApplication2)'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102]
Route matched with {action = "Fail", controller = "Foo"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Fail() on controller WebApplication2.Controllers.FooController (WebApplication2).
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Execution plan of authorization filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Execution plan of resource filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Execution plan of action filters (in the following order): Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter (Order: -3000), Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter (Order: -2000)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Execution plan of exception filters (in the following order): None
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Execution plan of result filters (in the following order): Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilter (Order: -2000)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[1]
Executing controller factory for controller WebApplication2.Controllers.FooController (WebApplication2)
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[2]
Executed controller factory for controller WebApplication2.Controllers.FooController (WebApplication2)
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[52]
Action Filter: Before executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[53]
Action Filter: After executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[52]
Action Filter: Before executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[53]
Action Filter: After executing OnActionExecuting on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[101]
Executing action method WebApplication2.Controllers.FooController.Fail (WebApplication2) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[103]
Executed action method WebApplication2.Controllers.FooController.Fail (WebApplication2), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 0.4942ms.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[52]
Action Filter: Before executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[53]
Action Filter: After executing OnActionExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[52]
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
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector[4]
No information found on request to perform content negotiation.
dbug: Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector[9]
Attempting to select the first output formatter in the output formatters list which supports a content type from the explicitly specified content types 'application/json; charset=utf-8'.
warn: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[2]
No output formatter was found for content types 'application/json; charset=utf-8, application/json; charset=utf-8' to write the response.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[6]
After executing action result Microsoft.AspNetCore.Mvc.ObjectResult.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[52]
Result Filter: Before executing OnResultExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilter.
trce: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[53]
Result Filter: After executing OnResultExecuted on filter Microsoft.AspNetCore.Mvc.Infrastructure.ClientErrorResultFilter.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105]
Executed action WebApplication2.Controllers.FooController.Fail (WebApplication2) in 55.8371ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'WebApplication2.Controllers.FooController.Fail (WebApplication2)'
dbug: Microsoft.AspNetCore.Server.Kestrel.Connections[9]
Connection id "0HN4HQQ1PUQ06" completed keep alive response.
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/1.1 GET http://localhost:5176/foo/fail - 406 0 - 154.7296ms
```

### Expected Behavior

Expected `GET http://localhost:5176/foo/fail` to return 200 OK, with JSON response.
It returns `406 Not Accepted` with no body.

`GET http://localhost:5176/foo/pass` does return 200 OK with JSON response.

### Steps To Reproduce

[repro-406-notaccepted-when-json-has-charset.zip](https://github.com/user-attachments/files/15925995/repro-406-notaccepted-when-json-has-charset.zip)

1. Extract zip
2. `dotnet run`
3. Call `GET http://localhost:5176/foo/fail`, it returns `406 Not Accepted`, but expected 200 OK with JSON body
4. Call `GET http://localhost:5176/foo/pass`, it returns `200 OK` as expected

It also does not help to specify any of these `Accept` headers in request:
- `Accept: application/json; charset=utf-8`
- `Accept: application/json`
- `Accept: application/*`
- `Accept: */*`

### Exceptions (if any)

_No response_

### .NET Version

8.0.301

### Anything else?

## Related issues
[406 Not Accepted on cached response with Content-Type: application/json; charset=utf-8 · Issue \#78 · ikyriak/IdempotentAPI](https://github.com/ikyriak/IdempotentAPI/issues/78)

## Versions

ASP.NET Core 8.0.6

dotnet --info

```
.NET SDK:
Version: 8.0.301
Commit: 1a0e9c0300
Workload version: 8.0.300-manifests.011fccd5
MSBuild version: 17.10.4+10fbfbf2e

Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.301\

.NET workloads installed:
[android]
Installation Source: SDK 8.0.300, VS 17.9.34902.65
Manifest Version: 34.0.95/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.android\34.0.95\WorkloadManifest.json
Install Type: Msi

[ios]
Installation Source: SDK 8.0.300, VS 17.9.34902.65
Manifest Version: 17.2.8053/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.ios\17.2.8053\WorkloadManifest.json
Install Type: Msi

[maccatalyst]
Installation Source: SDK 8.0.300, VS 17.9.34902.65
Manifest Version: 17.2.8053/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maccatalyst\17.2.8053\WorkloadManifest.json
Install Type: Msi

[maui-windows]
Installation Source: SDK 8.0.300, VS 17.9.34902.65
Manifest Version: 8.0.21/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maui\8.0.21\WorkloadManifest.json
Install Type: Msi

[wasi-experimental]
Installation Source: SDK 8.0.300
Manifest Version: 8.0.6/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.workload.mono.toolchain.current\8.0.6\WorkloadManifest.json
Install Type: Msi

Host:
Version: 8.0.6
Architecture: x64
Commit: 3b8b000a0e

.NET SDKs installed:
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.321 [C:\Program Files\dotnet\sdk]
6.0.423 [C:\Program Files\dotnet\sdk]
7.0.120 [C:\Program Files\dotnet\sdk]
7.0.317 [C:\Program Files\dotnet\sdk]
8.0.106 [C:\Program Files\dotnet\sdk]
8.0.205 [C:\Program Files\dotnet\sdk]
8.0.301 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.26 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.26 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.31 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.