Skip to content

Commit e71d903

Browse files
February 2017 Bug Fixes (#92)
* Use all candidates instead of just filtered matches. Fixes #83. * Reduce dependencies to Microsoft.AspNetMvc.Mvc.Core and update to version 1.1.1 per MSA4010983. Resolves #66. Fixes #78. * Strongly-typed resources should be scope internal * Support reading API version from URL segment. Fixes #91 * Refactored API version readers to use composition over inheritance * Added new support for providing custom 400 and 405 responses related to API versioning * Refactored error respones to return 405 over 400 when appropriate. Fixes #65 * Corrected license text to be compliant with Microsoft CELA guidelines
1 parent 64e7bbb commit e71d903

File tree

69 files changed

+1374
-756
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1374
-756
lines changed

ApiVersioningWithSamples.sln

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning",
4949
src\Common\Versioning\ConstantApiVersionSelector.cs = src\Common\Versioning\ConstantApiVersionSelector.cs
5050
src\Common\Versioning\CurrentImplementationApiVersionSelector.cs = src\Common\Versioning\CurrentImplementationApiVersionSelector.cs
5151
src\Common\Versioning\DefaultApiVersionSelector.cs = src\Common\Versioning\DefaultApiVersionSelector.cs
52+
src\Common\Versioning\ErrorResponseContext.cs = src\Common\Versioning\ErrorResponseContext.cs
5253
src\Common\Versioning\HeaderApiVersionReader.cs = src\Common\Versioning\HeaderApiVersionReader.cs
5354
src\Common\Versioning\IApiVersionNeutral.cs = src\Common\Versioning\IApiVersionNeutral.cs
5455
src\Common\Versioning\IApiVersionProvider.cs = src\Common\Versioning\IApiVersionProvider.cs
5556
src\Common\Versioning\IApiVersionReader.cs = src\Common\Versioning\IApiVersionReader.cs
5657
src\Common\Versioning\IApiVersionSelector.cs = src\Common\Versioning\IApiVersionSelector.cs
58+
src\Common\Versioning\IErrorResponseProvider.cs = src\Common\Versioning\IErrorResponseProvider.cs
5759
src\Common\Versioning\LowestImplementedApiVersionSelector.cs = src\Common\Versioning\LowestImplementedApiVersionSelector.cs
5860
src\Common\Versioning\QueryStringApiVersionReader.cs = src\Common\Versioning\QueryStringApiVersionReader.cs
59-
src\Common\Versioning\QueryStringOrHeaderApiVersionReader.cs = src\Common\Versioning\QueryStringOrHeaderApiVersionReader.cs
61+
src\Common\Versioning\UrlSegmentApiVersionReader.cs = src\Common\Versioning\UrlSegmentApiVersionReader.cs
6062
EndProjectSection
6163
EndProject
6264
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{915BB224-B1D0-4E27-A348-67FCC77AAA44}"

LICENSE

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
The MIT License (MIT)
1+
MIT License
22

3-
Copyright (c) 2015 Commonsense Software
3+
Copyright (c) Microsoft Corporation. All rights reserved.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -18,5 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
22-
21+
SOFTWARE

samples/aspnetcore/BasicSample/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "1.0.0",
55
"type": "platform"
66
},
7-
"Microsoft.AspNetCore.Mvc": "1.0.0",
7+
"Microsoft.AspNetCore.Mvc": "1.1.1",
88
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
99
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
1010
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",

samples/aspnetcore/ConventionsSample/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "1.0.0",
55
"type": "platform"
66
},
7-
"Microsoft.AspNetCore.Mvc": "1.0.0",
7+
"Microsoft.AspNetCore.Mvc": "1.1.1",
88
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
99
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
1010
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",

samples/webapi/AdvancedODataWebApiSample/Startup.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,9 @@ public void Configuration( IAppBuilder appBuilder )
2424
{
2525
o.ReportApiVersions = true;
2626
o.AssumeDefaultVersionWhenUnspecified = true;
27-
o.ApiVersionReader = new QueryStringOrHeaderApiVersionReader()
28-
{
29-
HeaderNames =
30-
{
31-
"api-version",
32-
"x-ms-version"
33-
}
34-
};
27+
o.ApiVersionReader = ApiVersionReader.Combine(
28+
new QueryStringApiVersionReader(),
29+
new HeaderApiVersionReader( "api-version", "x-ms-version" ) );
3530
} );
3631
configuration.EnableCaseInsensitive( true );
3732
configuration.EnableUnqualifiedNameCall( true );

src/Common/Versioning/ApiVersionReader.cs

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,98 @@ namespace Microsoft.Web.Http.Versioning
44
namespace Microsoft.AspNetCore.Mvc.Versioning
55
#endif
66
{
7+
#if !WEBAPI
8+
using Http;
9+
#endif
710
using System;
811
using System.Linq;
12+
using System.Collections.Generic;
13+
using System.Diagnostics.Contracts;
14+
#if WEBAPI
15+
using HttpRequest = System.Net.Http.HttpRequestMessage;
16+
#endif
17+
using static System.String;
918

1019
/// <summary>
11-
/// Represents the base implementation of a service API version reader.
20+
/// Provides utility functions for service API version readers.
1221
/// </summary>
13-
public abstract partial class ApiVersionReader : IApiVersionReader
22+
public static class ApiVersionReader
1423
{
1524
/// <summary>
16-
/// Initializes a new instance of the <see cref="ApiVersionReader"/> class.
25+
/// Returns a new API version reader that is a combination of the specified set.
1726
/// </summary>
18-
protected ApiVersionReader()
27+
/// <param name="apiVersionReaders">The <see cref="Array">array</see> of
28+
/// <see cref="IApiVersionReader">API version readers</see> to combine.</param>
29+
/// <returns>A new, unioned <see cref="IApiVersionReader">API version reader</see>.</returns>
30+
#if !WEBAPI
31+
[CLSCompliant( false )]
32+
#endif
33+
public static IApiVersionReader Combine( params IApiVersionReader[] apiVersionReaders )
34+
{
35+
Arg.NotNull( apiVersionReaders, nameof( apiVersionReaders ) );
36+
Contract.Ensures( Contract.Result<IApiVersionReader>() != null );
37+
Contract.EndContractBlock();
38+
39+
if ( apiVersionReaders.Length == 0 )
40+
{
41+
throw new ArgumentException( SR.ZeroApiVersionReaders, nameof( apiVersionReaders ) );
42+
}
43+
44+
return new CombinedApiVersionReader( apiVersionReaders );
45+
}
46+
47+
/// <summary>
48+
/// Returns a new API version reader that is a combination of the specified set.
49+
/// </summary>
50+
/// <param name="apiVersionReaders">The <see cref="IEnumerable{T}">sequence</see> of
51+
/// <see cref="IApiVersionReader">API version readers</see> to combine.</param>
52+
/// <returns>A new, unioned <see cref="IApiVersionReader">API version reader</see>.</returns>
53+
#if !WEBAPI
54+
[CLSCompliant( false )]
55+
#endif
56+
public static IApiVersionReader Combine( IEnumerable<IApiVersionReader> apiVersionReaders )
1957
{
58+
Arg.NotNull( apiVersionReaders, nameof( apiVersionReaders ) );
59+
Contract.Ensures( Contract.Result<IApiVersionReader>() != null );
60+
Contract.EndContractBlock();
61+
62+
var items = apiVersionReaders.ToArray();
63+
64+
if ( items.Length == 0 )
65+
{
66+
throw new ArgumentException( SR.ZeroApiVersionReaders, nameof( apiVersionReaders ) );
67+
}
68+
69+
return new CombinedApiVersionReader( items );
70+
}
71+
72+
sealed class CombinedApiVersionReader : IApiVersionReader
73+
{
74+
readonly IApiVersionReader[] apiVersionReaders;
75+
76+
internal CombinedApiVersionReader( IApiVersionReader[] apiVersionReaders )
77+
{
78+
Contract.Requires( apiVersionReaders != null );
79+
Contract.Requires( apiVersionReaders.Length > 0 );
80+
this.apiVersionReaders = apiVersionReaders;
81+
}
82+
83+
public string Read( HttpRequest request )
84+
{
85+
var versions = new HashSet<string>( StringComparer.OrdinalIgnoreCase );
86+
87+
foreach ( var apiVersionReader in apiVersionReaders )
88+
{
89+
var version = apiVersionReader.Read( request );
90+
91+
if ( !IsNullOrEmpty( version ) )
92+
{
93+
versions.Add( version );
94+
}
95+
}
96+
97+
return versions.EnsureZeroOrOneApiVersions();
98+
}
2099
}
21100
}
22-
}
101+
}

src/Common/Versioning/ApiVersioningOptions.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ namespace Microsoft.AspNetCore.Mvc.Versioning
77
using Conventions;
88
using System;
99
using System.Diagnostics.Contracts;
10+
#if WEBAPI
11+
using static Microsoft.Web.Http.Versioning.ApiVersionReader;
12+
#else
13+
using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionReader;
14+
#endif
1015

1116
/// <summary>
1217
/// Represents the possible API versioning options for services.
1318
/// </summary>
1419
public partial class ApiVersioningOptions
1520
{
1621
private ApiVersion defaultApiVersion = ApiVersion.Default;
17-
private IApiVersionReader apiVersionReader = new QueryStringApiVersionReader();
22+
private IApiVersionReader apiVersionReader = Combine( new QueryStringApiVersionReader(), new UrlSegmentApiVersionReader() );
1823
private IApiVersionSelector apiVersionSelector;
24+
private IErrorResponseProvider errorResponseProvider = new DefaultErrorResponseProvider();
1925
private ApiVersionConventionBuilder conventions = new ApiVersionConventionBuilder();
2026

2127
/// <summary>
@@ -80,7 +86,7 @@ public ApiVersion DefaultApiVersion
8086
/// service API version specified by a client. The default value is the
8187
/// <see cref="QueryStringApiVersionReader"/>, which only reads the service API version from
8288
/// the "api-version" query string parameter. Replace the default value with an alternate
83-
/// implementation, such as the <see cref="QueryStringOrHeaderApiVersionReader"/>, which
89+
/// implementation, such as the <see cref="HeaderApiVersionReader"/>, which
8490
/// can read the service API version from additional information like HTTP headers.</remarks>
8591
#if !WEBAPI
8692
[CLSCompliant( false )]
@@ -140,9 +146,31 @@ public ApiVersionConventionBuilder Conventions
140146
}
141147
set
142148
{
143-
Arg.NotNull( conventions, nameof( conventions ) );
149+
Arg.NotNull( value, nameof( value ) );
144150
conventions = value;
145151
}
146152
}
153+
154+
/// <summary>
155+
/// Gets or sets the object used to generate HTTP error responses related to API versioning.
156+
/// </summary>
157+
/// <value>An <see cref="IErrorResponseProvider">error response provider</see> object.
158+
/// The default value is an instance of the <see cref="DefaultErrorResponseProvider"/>.</value>
159+
#if !WEBAPI
160+
[CLSCompliant( false )]
161+
#endif
162+
public IErrorResponseProvider ErrorResponses
163+
{
164+
get
165+
{
166+
Contract.Ensures( errorResponseProvider != null );
167+
return errorResponseProvider;
168+
}
169+
set
170+
{
171+
Arg.NotNull( value, nameof( value ) );
172+
errorResponseProvider = value;
173+
}
174+
}
147175
}
148176
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#if WEBAPI
2+
namespace Microsoft.Web.Http.Versioning
3+
#else
4+
namespace Microsoft.AspNetCore.Mvc.Versioning
5+
#endif
6+
{
7+
/// <summary>
8+
/// Represents the contextual information used when generating HTTP error responses related to API versioning.
9+
/// </summary>
10+
public partial class ErrorResponseContext
11+
{
12+
/// <summary>
13+
/// Gets the associated error code.
14+
/// </summary>
15+
/// <value>The associated error code.</value>
16+
public string Code { get; }
17+
18+
/// <summary>
19+
/// Gets the associated error message.
20+
/// </summary>
21+
/// <value>The error message.</value>
22+
public string Message { get; }
23+
24+
/// <summary>
25+
/// Gets the detailed error message.
26+
/// </summary>
27+
/// <value>The detailed error message, if any.</value>
28+
public string MessageDetail { get; }
29+
}
30+
}

src/Common/Versioning/HeaderApiVersionReader.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ namespace Microsoft.AspNetCore.Mvc.Versioning
1111
/// <summary>
1212
/// Represents a service API version reader that reads the value from a HTTP header.
1313
/// </summary>
14-
public partial class HeaderApiVersionReader : ApiVersionReader
14+
public partial class HeaderApiVersionReader : IApiVersionReader
1515
{
1616
/// <summary>
1717
/// Initializes a new instance of the <see cref="HeaderApiVersionReader"/> class.
1818
/// </summary>
19-
public HeaderApiVersionReader()
20-
{
21-
}
19+
public HeaderApiVersionReader() { }
2220

2321
/// <summary>
2422
/// Initializes a new instance of the <see cref="HeaderApiVersionReader"/> class.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#if WEBAPI
2+
namespace Microsoft.Web.Http.Versioning
3+
#else
4+
namespace Microsoft.AspNetCore.Mvc.Versioning
5+
#endif
6+
{
7+
#if WEBAPI
8+
using IActionResult = System.Net.Http.HttpResponseMessage;
9+
#else
10+
using Http;
11+
#endif
12+
using System;
13+
14+
/// <summary>
15+
/// Defines the behavior of an object that provides HTTP error responses related to API versioning.
16+
/// </summary>
17+
#if !WEBAPI
18+
[CLSCompliant( false )]
19+
#endif
20+
public interface IErrorResponseProvider
21+
{
22+
/// <summary>
23+
/// Creates and returns a new HTTP 400 (Bad Request) given the provided context.
24+
/// </summary>
25+
/// <param name="context">The <see cref="ErrorResponseContext">error context</see> used to generate response.</param>
26+
/// <returns>The generated <see cref="IActionResult">response</see>.</returns>
27+
IActionResult BadRequest( ErrorResponseContext context );
28+
29+
/// <summary>
30+
/// Creates and returns a new HTTP 405 (Method Not Allowed) given the provided context.
31+
/// </summary>
32+
/// <param name="context">The <see cref="ErrorResponseContext">error context</see> used to generate response.</param>
33+
/// <returns>The generated <see cref="IActionResult">response</see>.</returns>
34+
IActionResult MethodNotAllowed( ErrorResponseContext context );
35+
}
36+
}

src/Common/Versioning/QueryStringApiVersionReader.cs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ namespace Microsoft.Web.Http.Versioning
44
namespace Microsoft.AspNetCore.Mvc.Versioning
55
#endif
66
{
7+
using Routing;
78
using System;
8-
using System.Linq;
9+
using System.Diagnostics.Contracts;
910

1011
/// <summary>
1112
/// Represents a service API version reader that reads the value from the query string in a URL.
1213
/// </summary>
13-
public partial class QueryStringApiVersionReader : ApiVersionReader
14+
public partial class QueryStringApiVersionReader : IApiVersionReader
1415
{
16+
string parameterName = "api-version";
17+
1518
/// <summary>
1619
/// Initializes a new instance of the <see cref="QueryStringApiVersionReader"/> class.
1720
/// </summary>
18-
public QueryStringApiVersionReader()
19-
{
20-
ParameterName = "api-version";
21-
}
21+
public QueryStringApiVersionReader() { }
2222

2323
/// <summary>
2424
/// Initializes a new instance of the <see cref="QueryStringApiVersionReader"/> class.
@@ -31,10 +31,22 @@ public QueryStringApiVersionReader( string parameterName )
3131
}
3232

3333
/// <summary>
34-
/// Gets the name of the query parameter to read the service API version from.
34+
/// Gets or sets the name of the query parameter to read the service API version from.
3535
/// </summary>
3636
/// <value>The name of the query parameter to read the service API version from.
3737
/// The default value is "api-version".</value>
38-
protected string ParameterName { get; }
38+
public string ParameterName
39+
{
40+
get
41+
{
42+
Contract.Ensures( !string.IsNullOrEmpty( parameterName ) );
43+
return parameterName;
44+
}
45+
set
46+
{
47+
Arg.NotNullOrEmpty( value, nameof( value ) );
48+
parameterName = value;
49+
}
50+
}
3951
}
40-
}
52+
}

0 commit comments

Comments
 (0)