Skip to content

Commit 37ea922

Browse files
Chris Martinezcommonsensesoftware
Chris Martinez
authored andcommitted
Update 405 handling for API version-neutral routes. Fixes #159
1 parent a15fe09 commit 37ea922

File tree

6 files changed

+83
-4
lines changed

6 files changed

+83
-4
lines changed

src/Microsoft.AspNetCore.Mvc.Versioning/Microsoft.AspNetCore.Mvc.Versioning.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<VersionPrefix>1.2.0</VersionPrefix>
4+
<VersionPrefix>1.2.1</VersionPrefix>
55
<AssemblyVersion>1.2.0.0</AssemblyVersion>
66
<TargetFrameworks>net451;netstandard1.6</TargetFrameworks>
77
<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>
@@ -11,7 +11,7 @@
1111
<Description>A service API versioning library for Microsoft ASP.NET Core.</Description>
1212
<RootNamespace>Microsoft.AspNetCore.Mvc</RootNamespace>
1313
<PackageTags>Microsoft;AspNet;AspNetCore;Versioning</PackageTags>
14-
<PackageReleaseNotes>• Remove need for UseApiVersioning (Issue #152)</PackageReleaseNotes>
14+
<PackageReleaseNotes>• Fix 405 for API version-neutral routes (Issue #159)</PackageReleaseNotes>
1515
</PropertyGroup>
1616

1717
<ItemGroup>

src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ RequestHandler ClientError( RouteContext context, ActionSelectionResult selectio
190190
var httpContext = context.HttpContext;
191191
var properties = httpContext.ApiVersionProperties();
192192
var code = default( string );
193+
var message = default( string );
193194
var requestedVersion = default( string );
194195
var parsedVersion = properties.ApiVersion;
195196
var actionNames = new Lazy<string>( () => Join( NewLine, candidates.Select( a => a.DisplayName ) ) );
@@ -198,9 +199,11 @@ RequestHandler ClientError( RouteContext context, ActionSelectionResult selectio
198199

199200
if ( parsedVersion == null )
200201
{
202+
var versionNeutral = new Lazy<bool>( () => candidates.Any( c => c.IsApiVersionNeutral() ) );
203+
201204
requestedVersion = properties.RawApiVersion;
202205

203-
if ( IsNullOrEmpty( requestedVersion ) )
206+
if ( IsNullOrEmpty( requestedVersion ) && !versionNeutral.Value )
204207
{
205208
code = ApiVersionUnspecified;
206209
Logger.ApiVersionUnspecified( actionNames.Value );
@@ -220,6 +223,18 @@ RequestHandler ClientError( RouteContext context, ActionSelectionResult selectio
220223
newRequestHandler = ( e, c, m ) => new MethodNotAllowedHandler( e, c, m, allowedMethods.Value.ToArray() );
221224
}
222225
}
226+
else if ( versionNeutral.Value )
227+
{
228+
Logger.ApiVersionUnspecified( actionNames.Value );
229+
message = SR.VersionNeutralResourceNotSupported.FormatDefault( httpContext.Request.GetDisplayUrl() );
230+
231+
if ( allowedMethods.Value.Contains( httpContext.Request.Method ) )
232+
{
233+
return new BadRequestHandler( ErrorResponseProvider, UnsupportedApiVersion, message );
234+
}
235+
236+
return new MethodNotAllowedHandler( ErrorResponseProvider, UnsupportedApiVersion, message, allowedMethods.Value.ToArray() );
237+
}
223238
else
224239
{
225240
code = InvalidApiVersion;
@@ -243,7 +258,7 @@ RequestHandler ClientError( RouteContext context, ActionSelectionResult selectio
243258
}
244259
}
245260

246-
var message = SR.VersionedResourceNotSupported.FormatDefault( httpContext.Request.GetDisplayUrl(), requestedVersion );
261+
message = SR.VersionedResourceNotSupported.FormatDefault( httpContext.Request.GetDisplayUrl(), requestedVersion );
247262
return newRequestHandler( ErrorResponseProvider, code, message );
248263
}
249264

src/Microsoft.AspNetCore.Mvc.Versioning/SR.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNetCore.Mvc.Versioning/SR.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@
154154
<data name="VersionedResourceNotSupported" xml:space="preserve">
155155
<value>The HTTP resource that matches the request URI '{0}' does not support the API version '{1}'.</value>
156156
</data>
157+
<data name="VersionNeutralResourceNotSupported" xml:space="preserve">
158+
<value>The HTTP resource that matches the request URI '{0}' is not supported.</value>
159+
</data>
157160
<data name="ZeroApiVersionReaders" xml:space="preserve">
158161
<value>At least one IApiVersionReader must be specified.</value>
159162
</data>

test/Microsoft.AspNet.WebApi.Acceptance.Tests/Http/Basic/given a version-neutral ApiController/when no version is specified.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
namespace given_a_version_neutral_ApiController
22
{
33
using FluentAssertions;
4+
using Microsoft.Web;
45
using Microsoft.Web.Http.Basic;
6+
using System.Net.Http;
57
using System.Threading.Tasks;
68
using Xunit;
79
using static System.Net.HttpStatusCode;
@@ -20,5 +22,31 @@ public async Task then_get_should_return_204()
2022
// assert
2123
response.StatusCode.Should().Be( NoContent );
2224
}
25+
26+
[Fact]
27+
public async Task then_post_should_return_405()
28+
{
29+
// arrange
30+
var entity = new { };
31+
32+
// act
33+
var response = await PostAsync( "api/ping", entity );
34+
var content = await response.Content.ReadAsAsync<OneApiErrorResponse>();
35+
36+
// assert
37+
response.StatusCode.Should().Be( MethodNotAllowed );
38+
response.Content.Headers.Allow.Should().BeEquivalentTo( "GET" );
39+
content.Error.ShouldBeEquivalentTo(
40+
new
41+
{
42+
Code = "UnsupportedApiVersion",
43+
InnerError = new
44+
{
45+
Code = default( string ),
46+
Message = "No route providing a controller name with API version '(null)' was found to match request URI 'http://localhost/api/ping'."
47+
},
48+
Message = "The HTTP resource that matches the request URI 'http://localhost/api/ping' does not support the API version '(null)'."
49+
} );
50+
}
2351
}
2452
}

test/Microsoft.AspNetCore.Mvc.Acceptance.Tests/Basic/given a version-neutral Controller/when no version is specified.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
namespace given_a_versionX2Dneutral_Controller
22
{
33
using FluentAssertions;
4+
using Microsoft.AspNetCore.Mvc;
45
using Microsoft.AspNetCore.Mvc.Basic;
6+
using System.Net.Http;
57
using System.Threading.Tasks;
68
using Xunit;
79
using static System.Net.HttpStatusCode;
@@ -20,5 +22,27 @@ public async Task then_get_should_return_204()
2022
// assert
2123
response.StatusCode.Should().Be( NoContent );
2224
}
25+
26+
[Fact]
27+
public async Task then_post_should_return_405()
28+
{
29+
// arrange
30+
var entity = new { };
31+
32+
// act
33+
var response = await PostAsync( "api/ping", entity );
34+
var content = await response.Content.ReadAsAsync<OneApiErrorResponse>();
35+
36+
// assert
37+
response.StatusCode.Should().Be( MethodNotAllowed );
38+
response.Content.Headers.Allow.Should().BeEquivalentTo( "GET" );
39+
content.Error.ShouldBeEquivalentTo(
40+
new
41+
{
42+
Code = "UnsupportedApiVersion",
43+
InnerError = default( OneApiInnerError ),
44+
Message = "The HTTP resource that matches the request URI 'http://localhost/api/ping' is not supported."
45+
} );
46+
}
2347
}
2448
}

0 commit comments

Comments
 (0)