406 Not Accepted when returning ObjectResult with content type 'application/json; charset=utf-8'
- 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
Assessment
This issue has not been assessed yet.