Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7d09a45

Browse filesBrowse files
Support custom reporting HTTP headers. Resolves #875
1 parent caacc44 commit 7d09a45
Copy full SHA for 7d09a45

File tree

Expand file treeCollapse file tree

9 files changed

+167
-51
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+167
-51
lines changed

‎src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs

Copy file name to clipboardExpand all lines: src/Client/src/Asp.Versioning.Http.Client/ApiVersionEnumerator.cs
+34-33Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,48 @@
22

33
namespace Asp.Versioning.Http;
44

5+
#pragma warning disable CA1815 // Override equals and operator equals on value types
6+
57
using System.Collections;
68

7-
internal readonly struct ApiVersionEnumerator : IEnumerable<ApiVersion>
9+
/// <summary>
10+
/// Represents an enumerator of API versions from a HTTP header.
11+
/// </summary>
12+
public readonly struct ApiVersionEnumerator : IEnumerable<ApiVersion>
813
{
9-
private const string ApiSupportedVersions = "api-supported-versions";
10-
private const string ApiDeprecatedVersions = "api-deprecated-versions";
1114
private readonly IEnumerable<string> values;
12-
private readonly IApiVersionParser? parser;
15+
private readonly IApiVersionParser parser;
1316

14-
private ApiVersionEnumerator( IEnumerable<string> values, IApiVersionParser? parser = default )
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ApiVersionEnumerator"/> struct.
19+
/// </summary>
20+
/// <param name="response">The HTTP response to create the enumerator from.</param>
21+
/// <param name="headerName">The HTTP header name to enumerate.</param>
22+
/// <param name="parser">The optional <see cref="IApiVersionParser">API version parser</see>.</param>
23+
public ApiVersionEnumerator(
24+
HttpResponseMessage response,
25+
string headerName,
26+
IApiVersionParser? parser = default )
1527
{
16-
this.values = values;
17-
this.parser = parser;
28+
if ( response == null )
29+
{
30+
throw new ArgumentNullException( nameof( response ) );
31+
}
32+
33+
if ( string.IsNullOrEmpty( headerName ) )
34+
{
35+
throw new ArgumentNullException( nameof( headerName ) );
36+
}
37+
38+
this.values =
39+
response.Headers.TryGetValues( headerName, out var values )
40+
? values
41+
: Enumerable.Empty<string>();
42+
43+
this.parser = parser ?? ApiVersionParser.Default;
1844
}
1945

46+
/// <inheritdoc />
2047
public IEnumerator<ApiVersion> GetEnumerator()
2148
{
2249
using var iterator = values.GetEnumerator();
@@ -26,8 +53,6 @@ public IEnumerator<ApiVersion> GetEnumerator()
2653
yield break;
2754
}
2855

29-
var parser = this.parser ?? ApiVersionParser.Default;
30-
3156
if ( parser.TryParse( iterator.Current, out var value ) )
3257
{
3358
yield return value!;
@@ -43,28 +68,4 @@ public IEnumerator<ApiVersion> GetEnumerator()
4368
}
4469

4570
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
46-
47-
internal static ApiVersionEnumerator Supported(
48-
HttpResponseMessage response,
49-
IApiVersionParser? parser = default )
50-
{
51-
if ( response.Headers.TryGetValues( ApiSupportedVersions, out var values ) )
52-
{
53-
return new( values, parser );
54-
}
55-
56-
return new( Enumerable.Empty<string>() );
57-
}
58-
59-
internal static ApiVersionEnumerator Deprecated(
60-
HttpResponseMessage response,
61-
IApiVersionParser? parser = default )
62-
{
63-
if ( response.Headers.TryGetValues( ApiDeprecatedVersions, out var values ) )
64-
{
65-
return new( values, parser );
66-
}
67-
68-
return new( Enumerable.Empty<string>() );
69-
}
7071
}

‎src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs

Copy file name to clipboardExpand all lines: src/Client/src/Asp.Versioning.Http.Client/ApiVersionHandler.cs
+8-3Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class ApiVersionHandler : DelegatingHandler
99
{
1010
private readonly IApiVersionWriter apiVersionWriter;
1111
private readonly ApiVersion apiVersion;
12+
private readonly ApiVersionHeaderEnumerable enumerable;
1213
private readonly IApiNotification notification;
1314
private readonly IApiVersionParser parser;
1415

@@ -22,16 +23,20 @@ public class ApiVersionHandler : DelegatingHandler
2223
/// that is signaled when changes to an API are detected.</param>
2324
/// <param name="parser">The optional <see cref="IApiVersionParser">parser</see> used to process
2425
/// API versions from HTTP responses.</param>
26+
/// <param name="enumerable">The optional <see cref="ApiVersionHeaderEnumerable">enumerable</see>
27+
/// used to enumerate retrieved API versions from HTTP responses.</param>
2528
public ApiVersionHandler(
2629
IApiVersionWriter apiVersionWriter,
2730
ApiVersion apiVersion,
2831
IApiNotification? notification = default,
29-
IApiVersionParser? parser = default )
32+
IApiVersionParser? parser = default,
33+
ApiVersionHeaderEnumerable? enumerable = default)
3034
{
3135
this.apiVersionWriter = apiVersionWriter ?? throw new ArgumentNullException( nameof( apiVersionWriter ) );
3236
this.apiVersion = apiVersion ?? throw new ArgumentNullException( nameof( apiVersion ) );
3337
this.notification = notification ?? ApiNotification.None;
3438
this.parser = parser ?? ApiVersionParser.Default;
39+
this.enumerable = enumerable ?? new();
3540
}
3641

3742
/// <inheritdoc />
@@ -67,7 +72,7 @@ protected virtual bool IsDeprecatedApi( HttpResponseMessage response )
6772
throw new ArgumentNullException( nameof( response ) );
6873
}
6974

70-
foreach ( var reportedApiVersion in ApiVersionEnumerator.Deprecated( response, parser ) )
75+
foreach ( var reportedApiVersion in enumerable.Deprecated( response, parser ) )
7176
{
7277
// don't use '==' operator because a derived type may not overload it
7378
if ( apiVersion.CompareTo( reportedApiVersion ) == 0 )
@@ -91,7 +96,7 @@ protected virtual bool IsNewApiAvailable( HttpResponseMessage response )
9196
throw new ArgumentNullException( nameof( response ) );
9297
}
9398

94-
foreach ( var reportedApiVersion in ApiVersionEnumerator.Supported( response, parser ) )
99+
foreach ( var reportedApiVersion in enumerable.Supported( response, parser ) )
95100
{
96101
// don't use '<' operator because a derived type may not overload it
97102
if ( apiVersion.CompareTo( reportedApiVersion ) < 0 )
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
3+
namespace Asp.Versioning.Http;
4+
#pragma warning disable CA1815 // Override equals and operator equals on value types
5+
6+
/// <summary>
7+
/// Represents the enumerable object used to create API version enumerators.
8+
/// </summary>
9+
public sealed class ApiVersionHeaderEnumerable
10+
{
11+
private const string ApiSupportedVersions = "api-supported-versions";
12+
private const string ApiDeprecatedVersions = "api-deprecated-versions";
13+
private readonly string apiSupportedVersionsName;
14+
private readonly string apiDeprecatedVersionsName;
15+
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="ApiVersionHeaderEnumerable"/> class.
18+
/// </summary>
19+
/// <param name="supportedHeaderName">The HTTP header name used for supported API versions.
20+
/// The default value is "api-supported-versions".</param>
21+
/// <param name="deprecatedHeaderName">THe HTTP header name used for deprecated API versions.
22+
/// The default value is "api-deprecated-versions".</param>
23+
public ApiVersionHeaderEnumerable(
24+
string supportedHeaderName = ApiSupportedVersions,
25+
string deprecatedHeaderName = ApiDeprecatedVersions )
26+
{
27+
if ( string.IsNullOrEmpty( apiSupportedVersionsName = supportedHeaderName ) )
28+
{
29+
throw new ArgumentNullException( nameof( supportedHeaderName ) );
30+
}
31+
32+
if ( string.IsNullOrEmpty( apiDeprecatedVersionsName = deprecatedHeaderName ) )
33+
{
34+
throw new ArgumentNullException( nameof( deprecatedHeaderName ) );
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Creates and returns an enumerator for supported API versions.
40+
/// </summary>
41+
/// <param name="response">The <see cref="HttpResponseMessage">HTTP response</see> to evaluate.</param>
42+
/// <param name="parser">The optional <see cref="IApiVersionParser">API version parser</see>.</param>
43+
/// <returns>A new <see cref="ApiVersionEnumerator"/>.</returns>
44+
public ApiVersionEnumerator Supported(
45+
HttpResponseMessage response,
46+
IApiVersionParser? parser = default ) =>
47+
new( response, apiSupportedVersionsName, parser );
48+
49+
/// <summary>
50+
/// Creates and returns an enumerator for deprecated API versions.
51+
/// </summary>
52+
/// <param name="response">The <see cref="HttpResponseMessage">HTTP response</see> to evaluate.</param>
53+
/// <param name="parser">The optional <see cref="IApiVersionParser">API version parser</see>.</param>
54+
/// <returns>A new <see cref="ApiVersionEnumerator"/>.</returns>
55+
public ApiVersionEnumerator Deprecated(
56+
HttpResponseMessage response,
57+
IApiVersionParser? parser = default ) =>
58+
new( response, apiDeprecatedVersionsName, parser );
59+
}

‎src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs

Copy file name to clipboardExpand all lines: src/Client/src/Asp.Versioning.Http.Client/System.Net.Http/HttpClientExtensions.cs
+10-2Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public static class HttpClientExtensions
1818
/// <param name="requestUrl">The URL to get the API information from.</param>
1919
/// <param name="parser">The optional <see cref="IApiVersionParser">parser</see> used
2020
/// to process retrieved API versions.</param>
21+
/// <param name="enumerable">The optional <see cref="ApiVersionHeaderEnumerable">enumerable</see>
22+
/// used to enumerate retrieved API versions.</param>
2123
/// <param name="cancellationToken">The token that can be used to cancel the operation.</param>
2224
/// <returns>A task containing the retrieved <see cref="ApiInformation">API information</see>.</returns>
2325
/// <remarks>API information is retrieved by sending an OPTIONS request to the specified URL.
@@ -27,10 +29,12 @@ public static Task<ApiInformation> GetApiInformationAsync(
2729
this HttpClient client,
2830
string requestUrl,
2931
IApiVersionParser? parser = default,
32+
ApiVersionHeaderEnumerable? enumerable = default,
3033
CancellationToken cancellationToken = default ) =>
3134
client.GetApiInformationAsync(
3235
new Uri( requestUrl, UriKind.RelativeOrAbsolute ),
3336
parser,
37+
enumerable,
3438
cancellationToken );
3539

3640
/// <summary>
@@ -40,6 +44,8 @@ public static Task<ApiInformation> GetApiInformationAsync(
4044
/// <param name="requestUrl">The URL to get the API information from.</param>
4145
/// <param name="parser">The optional <see cref="IApiVersionParser">parser</see> used
4246
/// to process retrieved API versions.</param>
47+
/// <param name="enumerable">The optional <see cref="ApiVersionHeaderEnumerable">enumerable</see>
48+
/// used to enumerate retrieved API versions.</param>
4349
/// <param name="cancellationToken">The token that can be used to cancel the operation.</param>
4450
/// <returns>A task containing the retrieved <see cref="ApiInformation">API information</see>.</returns>
4551
/// <remarks>API information is retrieved by sending an OPTIONS request to the specified URL.
@@ -49,6 +55,7 @@ public static async Task<ApiInformation> GetApiInformationAsync(
4955
this HttpClient client,
5056
Uri requestUrl,
5157
IApiVersionParser? parser = default,
58+
ApiVersionHeaderEnumerable? enumerable = default,
5259
CancellationToken cancellationToken = default )
5360
{
5461
if ( client == null )
@@ -69,12 +76,13 @@ public static async Task<ApiInformation> GetApiInformationAsync(
6976
}
7077

7178
parser ??= ApiVersionParser.Default;
72-
var versions = new SortedSet<ApiVersion>( ApiVersionEnumerator.Supported( response, parser ) );
79+
enumerable ??= new();
80+
var versions = new SortedSet<ApiVersion>( enumerable.Supported( response, parser ) );
7381
var supported = versions.ToArray();
7482

7583
versions.Clear();
7684

77-
foreach ( var version in ApiVersionEnumerator.Deprecated( response, parser ) )
85+
foreach ( var version in enumerable.Deprecated( response, parser ) )
7886
{
7987
versions.Add( version );
8088
}

‎src/Client/src/Asp.Versioning.Http.Client/net6.0/ApiVersionHandlerLogger{T}.cs

Copy file name to clipboardExpand all lines: src/Client/src/Asp.Versioning.Http.Client/net6.0/ApiVersionHandlerLogger{T}.cs
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ public class ApiVersionHandlerLogger<T> : ApiNotification
1313
{
1414
private readonly ILogger logger;
1515
private readonly IApiVersionParser parser;
16+
private readonly ApiVersionHeaderEnumerable enumerable;
1617

1718
/// <summary>
1819
/// Initializes a new instance of the <see cref="ApiVersionHandlerLogger{T}"/> class.
1920
/// </summary>
2021
/// <param name="logger">The <see cref="ILogger{TCategoryName}">logger</see> used to log API notifications.</param>
2122
/// <param name="parser">The <see cref="IApiVersionParser">parser</see> used to process API versions.</param>
22-
public ApiVersionHandlerLogger( ILogger<T> logger, IApiVersionParser parser )
23+
/// <param name="enumerable">The <see cref="ApiVersionHeaderEnumerable">enumerable</see> used to enumerate API versions.</param>
24+
public ApiVersionHandlerLogger( ILogger<T> logger, IApiVersionParser parser, ApiVersionHeaderEnumerable enumerable )
2325
{
2426
this.logger = logger ?? throw new ArgumentNullException( nameof( logger ) );
2527
this.parser = parser ?? throw new ArgumentNullException( nameof( parser ) );
28+
this.enumerable = enumerable ?? throw new ArgumentNullException( nameof( enumerable ) );
2629
}
2730

2831
/// <inheritdoc />
@@ -51,7 +54,7 @@ protected override void OnNewApiAvailable( ApiNotificationContext context )
5154
var requestUrl = context.Response.RequestMessage!.RequestUri!;
5255
var currentApiVersion = context.ApiVersion;
5356
var sunsetPolicy = context.SunsetPolicy;
54-
var newApiVersion = ApiVersionEnumerator.Supported( context.Response, parser ).Max() ?? currentApiVersion;
57+
var newApiVersion = enumerable.Supported( context.Response, parser ).Max() ?? currentApiVersion;
5558

5659
logger.NewApiVersionAvailable( requestUrl, currentApiVersion, newApiVersion, sunsetPolicy );
5760
}

‎src/Client/src/Asp.Versioning.Http.Client/net6.0/DependencyInjection/IHttpClientBuilderExtensions.cs

Copy file name to clipboardExpand all lines: src/Client/src/Asp.Versioning.Http.Client/net6.0/DependencyInjection/IHttpClientBuilderExtensions.cs
+5-1Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public static IHttpClientBuilder AddApiVersion(
112112

113113
services.TryAddSingleton<IApiVersionWriter, QueryStringApiVersionWriter>();
114114
services.TryAddSingleton<IApiVersionParser, ApiVersionParser>();
115+
services.TryAddTransient<ApiVersionHeaderEnumerable>();
115116
builder.AddHttpMessageHandler( sp => NewApiVersionHandler( sp, apiVersion, apiVersionWriter ) );
116117

117118
return builder;
@@ -142,8 +143,11 @@ private static ApiVersionHandler NewApiVersionHandler(
142143
return default;
143144
}
144145

146+
var enumerable = serviceProvider.GetService<ApiVersionHeaderEnumerable>();
147+
145148
return new ApiVersionHandlerLogger<ApiVersionHandler>(
146149
logger,
147-
parser ?? ApiVersionParser.Default );
150+
parser ?? ApiVersionParser.Default,
151+
enumerable ?? new() );
148152
}
149153
}

‎src/Client/test/Asp.Versioning.Http.Client.Tests/net6.0/ApiVersionHandlerLoggerTTest.cs

Copy file name to clipboardExpand all lines: src/Client/test/Asp.Versioning.Http.Client.Tests/net6.0/ApiVersionHandlerLoggerTTest.cs
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public async Task on_api_deprecated_should_log_message()
1414
using var factory = TestLoggerFactory.Create();
1515
var logger = factory.CreateLogger<ApiVersionHandler>();
1616
var parser = ApiVersionParser.Default;
17-
var notification = new ApiVersionHandlerLogger<ApiVersionHandler>( logger, parser );
17+
var notification = new ApiVersionHandlerLogger<ApiVersionHandler>( logger, parser, new() );
1818
var response = new HttpResponseMessage()
1919
{
2020
RequestMessage = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" ),
@@ -50,7 +50,7 @@ public async Task on_new_api_available_should_log_message()
5050
using var factory = TestLoggerFactory.Create();
5151
var logger = factory.CreateLogger<ApiVersionHandler>();
5252
var parser = ApiVersionParser.Default;
53-
var notification = new ApiVersionHandlerLogger<ApiVersionHandler>( logger, parser );
53+
var notification = new ApiVersionHandlerLogger<ApiVersionHandler>( logger, parser, new() );
5454
var response = new HttpResponseMessage()
5555
{
5656
RequestMessage = new HttpRequestMessage( HttpMethod.Get, "http://tempuri.org" ),

‎src/Client/test/Asp.Versioning.Http.Client.Tests/net6.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs

Copy file name to clipboardExpand all lines: src/Client/test/Asp.Versioning.Http.Client.Tests/net6.0/DependencyInjection/IHttpClientBuilderExtensionsTest.cs
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ public async Task add_api_version_should_ignore_registered_writer()
7272
response.RequestMessage.RequestUri.Should().Be( new Uri( "http://tempuri.org?ver=2022-02-01" ) );
7373
}
7474

75+
[Fact]
76+
public void add_api_version_should_register_transient_header_enumerable()
77+
{
78+
// arrange
79+
var services = new ServiceCollection();
80+
81+
services.AddHttpClient( "Test" ).AddApiVersion( 1.0 );
82+
83+
var provider = services.BuildServiceProvider();
84+
85+
// act
86+
var result1 = provider.GetRequiredService<ApiVersionHeaderEnumerable>();
87+
var result2 = provider.GetRequiredService<ApiVersionHeaderEnumerable>();
88+
89+
// assert
90+
result1.Should().NotBeSameAs( result2 );
91+
}
92+
7593
#pragma warning disable CA1812
7694

7795
private sealed class LastHandler : DelegatingHandler

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.