Skip to content

Commit 6ca55f2

Browse files
Support 406 and 415 with ProblemDetails. Resolves #886
1 parent ad2372a commit 6ca55f2

File tree

17 files changed

+161
-50
lines changed

17 files changed

+161
-50
lines changed

src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/Values2Controller.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@
22

33
namespace Asp.Versioning.Http.UsingMediaType.Controllers;
44

5+
using Newtonsoft.Json.Linq;
56
using System.Web.Http;
67

78
[ApiVersion( "2.0" )]
8-
[Route( "api/values" )]
9+
[RoutePrefix( "api/values" )]
910
public class Values2Controller : ApiController
1011
{
11-
public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
12+
[Route]
13+
public IHttpActionResult Get() =>
14+
Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
15+
16+
[Route( "{id}", Name = "GetByIdV2" )]
17+
public IHttpActionResult Get( string id ) =>
18+
Ok( new { controller = GetType().Name, Id = id, version = Request.GetRequestedApiVersion().ToString() } );
19+
20+
public IHttpActionResult Post( [FromBody] JToken json ) =>
21+
CreatedAtRoute( "GetByIdV2", new { id = "42" }, json );
1222
}

src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/Controllers/ValuesController.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ namespace Asp.Versioning.Http.UsingMediaType.Controllers;
55
using System.Web.Http;
66

77
[ApiVersion( "1.0" )]
8-
[Route( "api/values" )]
8+
[RoutePrefix( "api/values" )]
99
public class ValuesController : ApiController
1010
{
11-
public IHttpActionResult Get() => Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
11+
[Route]
12+
public IHttpActionResult Get() =>
13+
Ok( new { controller = GetType().Name, version = Request.GetRequestedApiVersion().ToString() } );
14+
15+
[Route( "{id}" )]
16+
public IHttpActionResult Get( string id ) =>
17+
Ok( new { controller = GetType().Name, Id = id, version = Request.GetRequestedApiVersion().ToString() } );
1218
}

src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingMediaType/given a versioned ApiController/when using media type negotiation.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public async Task then_get_should_return_200( string controller, string apiVersi
3737
}
3838

3939
[Fact]
40-
public async Task then_get_should_return_415_for_an_unsupported_version()
40+
public async Task then_get_should_return_406_for_an_unsupported_version()
4141
{
4242
// arrange
4343
using var request = new HttpRequestMessage( Get, "api/values" )
@@ -49,6 +49,23 @@ public async Task then_get_should_return_415_for_an_unsupported_version()
4949
var response = await Client.SendAsync( request );
5050
var problem = await response.Content.ReadAsProblemDetailsAsync();
5151

52+
// assert
53+
response.StatusCode.Should().Be( NotAcceptable );
54+
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
55+
}
56+
57+
[Fact]
58+
public async Task then_post_should_return_415_for_an_unsupported_version()
59+
{
60+
// arrange
61+
var entity = new { text = "Test" };
62+
var mediaType = Parse( "application/json;v=3.0" );
63+
using var content = new ObjectContent( entity.GetType(), entity, new JsonMediaTypeFormatter(), mediaType );
64+
65+
// act
66+
var response = await Client.PostAsync( "api/values", content );
67+
var problem = await response.Content.ReadAsProblemDetailsAsync();
68+
5269
// assert
5370
response.StatusCode.Should().Be( UnsupportedMediaType );
5471
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );

src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,17 @@ private HttpResponseMessage CreateNotFound( ControllerSelectionResult convention
210210

211211
private HttpResponseMessage CreateUnsupportedMediaType()
212212
{
213+
var content = request.Content;
214+
var statusCode = content != null && content.Headers.ContentType != null ? UnsupportedMediaType : NotAcceptable;
213215
var version = request.GetRequestedApiVersion()?.ToString() ?? "(null)";
214216
var detail = string.Format( CultureInfo.CurrentCulture, SR.VersionedMediaTypeNotSupported, version );
215217

216218
TraceWriter.Info( request, ControllerSelectorCategory, detail );
217219

218220
var (type, title) = ProblemDetailsDefaults.Unsupported;
219-
var problem = ProblemDetails.CreateProblemDetails( request, (int) UnsupportedMediaType, title, type, detail );
221+
var problem = ProblemDetails.CreateProblemDetails( request, (int) statusCode, title, type, detail );
220222
var (mediaType, formatter) = request.GetProblemDetailsResponseType();
221223

222-
return request.CreateResponse( UnsupportedMediaType, problem, formatter, mediaType );
224+
return request.CreateResponse( statusCode, problem, formatter, mediaType );
223225
}
224226
}

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/Values2Controller.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ namespace Asp.Versioning.Mvc.UsingMediaType.Controllers;
1313
public class Values2Controller : ControllerBase
1414
{
1515
[HttpGet]
16-
public IActionResult Get( ApiVersion version ) => Ok( new { Controller = nameof( Values2Controller ), Version = version.ToString() } );
16+
public IActionResult Get( ApiVersion version ) =>
17+
Ok( new { Controller = nameof( Values2Controller ), Version = version.ToString() } );
18+
19+
[HttpGet( "{id}" )]
20+
public IActionResult Get( string id, ApiVersion version ) =>
21+
Ok( new { Controller = nameof( Values2Controller ), Id = id, Version = version.ToString() } );
22+
23+
[HttpPost]
24+
public IActionResult Post( JsonElement json ) => CreatedAtAction( nameof( Get ), new { id = "42" }, json );
1725

1826
[HttpPatch( "{id}" )]
1927
[Consumes( "application/merge-patch+json" )]
20-
public IActionResult MergePatch( JsonElement json ) => NoContent();
28+
public IActionResult MergePatch( string id, JsonElement json ) => NoContent();
2129
}

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/Controllers/ValuesController.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ namespace Asp.Versioning.Mvc.UsingMediaType.Controllers;
1111
public class ValuesController : ControllerBase
1212
{
1313
[HttpGet]
14-
public IActionResult Get() => Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } );
14+
public IActionResult Get() =>
15+
Ok( new { Controller = nameof( ValuesController ), Version = HttpContext.GetRequestedApiVersion().ToString() } );
16+
17+
[HttpGet( "{id}" )]
18+
public IActionResult Get( string id ) =>
19+
Ok( new { Controller = nameof( ValuesController ), Id = id, Version = HttpContext.GetRequestedApiVersion().ToString() } );
1520
}

src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Mvc/UsingMediaType/given a versioned Controller/when using media type negotiation.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public async Task then_get_should_return_200( string controller, string apiVersi
3838
}
3939

4040
[Fact]
41-
public async Task then_get_should_return_415_for_an_unsupported_version()
41+
public async Task then_get_should_return_406_for_an_unsupported_version()
4242
{
4343
// arrange
4444
using var request = new HttpRequestMessage( Get, "api/values" )
@@ -48,9 +48,29 @@ public async Task then_get_should_return_415_for_an_unsupported_version()
4848

4949
// act
5050
var response = await Client.SendAsync( request );
51+
var problem = await response.Content.ReadAsProblemDetailsAsync();
52+
53+
// assert
54+
response.StatusCode.Should().Be( NotAcceptable );
55+
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
56+
}
57+
58+
[Fact]
59+
public async Task then_post_should_return_415_for_an_unsupported_version()
60+
{
61+
// arrange
62+
using var request = new HttpRequestMessage( Post, "api/values" )
63+
{
64+
Content = JsonContent.Create( new { test = true }, Parse( "application/json;v=3.0" ) ),
65+
};
66+
67+
// act
68+
var response = await Client.SendAsync( request );
69+
var problem = await response.Content.ReadAsProblemDetailsAsync();
5170

5271
// assert
5372
response.StatusCode.Should().Be( UnsupportedMediaType );
73+
problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
5474
}
5575

5676
[Fact]

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList<Policy
139139
case EndpointType.AssumeDefault:
140140
rejection.AssumeDefault = edge.Destination;
141141
break;
142+
case EndpointType.NotAcceptable:
143+
rejection.NotAcceptable = edge.Destination;
144+
break;
142145
default:
143146
if ( versionsByUrl && state.RoutePatterns.Count > 0 )
144147
{

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Asp.Versioning.Routing;
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.AspNetCore.Routing.Matching;
77
using Microsoft.AspNetCore.Routing.Patterns;
8+
using Microsoft.Net.Http.Headers;
89
using System.Runtime.CompilerServices;
910

1011
internal sealed class ApiVersionPolicyJumpTable : PolicyJumpTable
@@ -36,13 +37,14 @@ internal ApiVersionPolicyJumpTable(
3637

3738
public override int GetDestination( HttpContext httpContext )
3839
{
40+
var request = httpContext.Request;
3941
var feature = httpContext.ApiVersioningFeature();
4042
var apiVersions = new List<string>( capacity: feature.RawRequestedApiVersions.Count + 1 );
4143

4244
apiVersions.AddRange( feature.RawRequestedApiVersions );
4345

4446
if ( versionsByUrl &&
45-
TryGetApiVersionFromPath( httpContext.Request, out var rawApiVersion ) &&
47+
TryGetApiVersionFromPath( request, out var rawApiVersion ) &&
4648
DoesNotContainApiVersion( apiVersions, rawApiVersion ) )
4749
{
4850
apiVersions.Add( rawApiVersion );
@@ -86,9 +88,17 @@ public override int GetDestination( HttpContext httpContext )
8688
return destination;
8789
}
8890

89-
return versionsByMediaTypeOnly
90-
? rejection.UnsupportedMediaType // 415
91-
: rejection.Exit; // 404
91+
if ( versionsByMediaTypeOnly )
92+
{
93+
if ( request.Headers.ContainsKey( HeaderNames.ContentType ) )
94+
{
95+
return rejection.UnsupportedMediaType; // 415
96+
}
97+
98+
return rejection.NotAcceptable; // 406
99+
}
100+
101+
return rejection.Exit; // 404
92102
}
93103

94104
var addedFromUrl = apiVersions.Count == apiVersions.Capacity;

src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ public EdgeBuilder(
2828
unspecifiedNotAllowed = !options.AssumeDefaultVersionWhenUnspecified;
2929
constraintName = options.RouteConstraintName;
3030
keys = new( capacity + 1 );
31-
edges = new( capacity + 5 )
31+
edges = new( capacity + 6 )
3232
{
3333
[EdgeKey.Malformed] = new( capacity: 1 ) { new MalformedApiVersionEndpoint( logger ) },
3434
[EdgeKey.Ambiguous] = new( capacity: 1 ) { new AmbiguousApiVersionEndpoint( logger ) },
3535
[EdgeKey.Unspecified] = new( capacity: 1 ) { new UnspecifiedApiVersionEndpoint( logger ) },
3636
[EdgeKey.UnsupportedMediaType] = new( capacity: 1 ) { new UnsupportedMediaTypeEndpoint() },
37+
[EdgeKey.NotAcceptable] = new( capacity: 1 ) { new NotAcceptableEndpoint() },
3738
};
3839
}
3940

0 commit comments

Comments
 (0)