Skip to content

Commit d48c835

Browse files
author
Chris Martinez
committed
Support reading API version from URL segment. Fixes #91
1 parent 2b3935f commit d48c835

File tree

10 files changed

+332
-46
lines changed

10 files changed

+332
-46
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 Routing;
9+
#else
10+
using Microsoft.AspNetCore.Routing;
11+
using Routing;
12+
#endif
13+
using System.Diagnostics.Contracts;
14+
15+
/// <summary>
16+
/// Represents a service API version reader that reads the value from a path segment in the request URL.
17+
/// </summary>
18+
public partial class UrlSegmentApiVersionReader : IApiVersionReader
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="UrlSegmentApiVersionReader"/> class.
22+
/// </summary>
23+
public UrlSegmentApiVersionReader() { }
24+
}
25+
}

src/Microsoft.AspNet.WebApi.Versioning/Routing/ApiVersionRouteConstraint.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace Microsoft.Web.Http.Routing
22
{
3-
using System;
43
using System.Collections.Generic;
54
using System.Net.Http;
65
using System.Web.Http;
@@ -25,22 +24,36 @@ public sealed class ApiVersionRouteConstraint : IHttpRouteConstraint
2524
/// <returns>True if the route constraint is matched; otherwise, false.</returns>
2625
public bool Match( HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection )
2726
{
27+
if ( IsNullOrEmpty( parameterName ) )
28+
{
29+
return false;
30+
}
31+
2832
var value = default( string );
2933

34+
if ( values.TryGetValue( parameterName, out value ) )
35+
{
36+
request.SetRouteParameterName( parameterName );
37+
}
38+
else
39+
{
40+
return false;
41+
}
42+
3043
if ( routeDirection == UriGeneration )
3144
{
32-
return !IsNullOrEmpty( parameterName ) && values.TryGetValue( parameterName, out value ) && !IsNullOrEmpty( value );
45+
return !IsNullOrEmpty( value );
3346
}
3447

3548
var requestedVersion = default( ApiVersion );
3649

37-
if ( !values.TryGetValue( parameterName, out value ) || !TryParse( value, out requestedVersion ) )
50+
if ( TryParse( value, out requestedVersion ) )
3851
{
39-
return false;
52+
request.SetRequestedApiVersion( requestedVersion );
53+
return true;
4054
}
4155

42-
request.SetRequestedApiVersion( requestedVersion );
43-
return true;
56+
return false;
4457
}
4558
}
46-
}
59+
}

src/Microsoft.AspNet.WebApi.Versioning/System.Web.Http/HttpRequestMessageExtensions.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55
using Diagnostics.Contracts;
66
using Microsoft;
77
using Microsoft.Web.Http;
8+
using Microsoft.Web.Http.Routing;
89
using Microsoft.Web.Http.Versioning;
910
using Net;
1011
using Net.Http;
1112
using System;
12-
using static Microsoft.Web.Http.ApiVersion;
1313
using static ComponentModel.EditorBrowsableState;
14+
using static Microsoft.Web.Http.ApiVersion;
15+
using static System.String;
1416

1517
/// <summary>
1618
/// Provides extension methods for the <see cref="HttpRequestMessage"/> class.
1719
/// </summary>
1820
public static class HttpRequestMessageExtensions
1921
{
2022
private const string ApiVersionKey = "MS_" + nameof( ApiVersion );
23+
private const string ApiVersionRouteParameterName = "MS_" + nameof( ApiVersionRouteConstraint ) + "_ParameterName";
2124

2225
[SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Handled by the caller." )]
2326
private static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, Func<bool, HttpError> errorCreator )
@@ -140,5 +143,33 @@ public static void SetRequestedApiVersion( this HttpRequestMessage request, ApiV
140143
request.Properties[ApiVersionKey] = version;
141144
}
142145
}
146+
147+
internal static string GetRouteParameterNameAssignedByApiVersionRouteConstraint( this HttpRequestMessage request )
148+
{
149+
Contract.Requires( request != null );
150+
151+
var parameterName = default( string );
152+
153+
if ( request.Properties.TryGetValue( ApiVersionRouteParameterName, out parameterName ) )
154+
{
155+
return parameterName;
156+
}
157+
158+
return null;
159+
}
160+
161+
internal static void SetRouteParameterName( this HttpRequestMessage request, string parameterName )
162+
{
163+
Contract.Requires( request != null );
164+
165+
if ( IsNullOrEmpty( parameterName ) )
166+
{
167+
request.Properties.Remove( ApiVersionRouteParameterName );
168+
}
169+
else
170+
{
171+
request.Properties[ApiVersionRouteParameterName] = parameterName;
172+
}
173+
}
143174
}
144175
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
namespace Microsoft.Web.Http.Versioning
2+
{
3+
using Routing;
4+
using System.Diagnostics.Contracts;
5+
using System.Net.Http;
6+
using System.Web.Http;
7+
using System.Web.Http.Routing;
8+
using static System.String;
9+
10+
/// <content>
11+
/// Provides the implementation for ASP.NET Web API.
12+
/// </content>
13+
public partial class UrlSegmentApiVersionReader
14+
{
15+
/// <summary>
16+
/// Reads the service API version value from a request.
17+
/// </summary>
18+
/// <param name="request">The <see cref="HttpRequestMessage">HTTP request</see> to read the API version from.</param>
19+
/// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
20+
/// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
21+
public virtual string Read( HttpRequestMessage request )
22+
{
23+
Arg.NotNull( request, nameof( request ) );
24+
25+
var routeData = request.GetRouteData();
26+
27+
if ( routeData == null )
28+
{
29+
return null;
30+
}
31+
32+
var key = request.GetRouteParameterNameAssignedByApiVersionRouteConstraint();
33+
var subRouteData = routeData.GetSubRoutes() ?? new[] { routeData };
34+
var value = default( object );
35+
36+
if ( IsNullOrEmpty( key ) )
37+
{
38+
foreach ( var subRouteDatum in subRouteData )
39+
{
40+
key = GetRouteParameterNameFromConstraintNameInTemplate( subRouteDatum );
41+
42+
if ( key != null && subRouteDatum.Values.TryGetValue( key, out value ) )
43+
{
44+
return value.ToString();
45+
}
46+
}
47+
}
48+
else
49+
{
50+
foreach ( var subRouteDatum in subRouteData )
51+
{
52+
if ( subRouteDatum.Values.TryGetValue( key, out value ) )
53+
{
54+
return value.ToString();
55+
}
56+
}
57+
}
58+
59+
return null;
60+
}
61+
62+
static string GetRouteParameterNameFromConstraintNameInTemplate( IHttpRouteData routeData )
63+
{
64+
Contract.Requires( routeData != null );
65+
66+
foreach ( var constraint in routeData.Route.Constraints )
67+
{
68+
if ( constraint.Value is ApiVersionRouteConstraint )
69+
{
70+
return constraint.Key;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
}
77+
}

src/Microsoft.AspNetCore.Mvc.Versioning/HttpContextExtensions.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
namespace Microsoft.AspNetCore.Mvc
22
{
33
using Http;
4+
using Routing;
45
using System;
56
using System.ComponentModel;
67
using System.Diagnostics.Contracts;
78
using Versioning;
89
using static ApiVersion;
910
using static System.ComponentModel.EditorBrowsableState;
11+
using static System.String;
1012

1113
/// <summary>
1214
/// Provides extension methods for the <see cref="HttpContext"/> class.
@@ -15,6 +17,7 @@
1517
public static class HttpContextExtensions
1618
{
1719
private const string ApiVersionKey = "MS_" + nameof( ApiVersion );
20+
private const string ApiVersionRouteParameterName = "MS_" + nameof( ApiVersionRouteConstraint ) + "_ParameterName";
1821

1922
/// <summary>
2023
/// Gets the current raw, unparsed service API version requested.
@@ -76,5 +79,33 @@ internal static void SetRequestedApiVersion( this HttpContext context, ApiVersio
7679
context.Items[ApiVersionKey] = version;
7780
}
7881
}
82+
83+
internal static string GetRouteParameterNameAssignedByApiVersionRouteConstraint( this HttpContext context )
84+
{
85+
Contract.Requires( context != null );
86+
87+
var parameterName = default( string );
88+
89+
if ( context.Items.TryGetValue( ApiVersionRouteParameterName, out parameterName ) )
90+
{
91+
return parameterName;
92+
}
93+
94+
return null;
95+
}
96+
97+
internal static void SetRouteParameterName( this HttpContext context, string parameterName )
98+
{
99+
Contract.Requires( context != null );
100+
101+
if ( IsNullOrEmpty( parameterName ) )
102+
{
103+
context.Items.Remove( ApiVersionRouteParameterName );
104+
}
105+
else
106+
{
107+
context.Items[ApiVersionRouteParameterName] = parameterName;
108+
}
109+
}
79110
}
80-
}
111+
}

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,36 @@ public sealed class ApiVersionRouteConstraint : IRouteConstraint
2424
/// <returns>True if the route constraint is matched; otherwise, false.</returns>
2525
public bool Match( HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection )
2626
{
27+
if ( IsNullOrEmpty( routeKey ) )
28+
{
29+
return false;
30+
}
31+
2732
var value = default( string );
2833

34+
if ( values.TryGetValue( routeKey, out value ) )
35+
{
36+
httpContext.SetRouteParameterName( routeKey );
37+
}
38+
else
39+
{
40+
return false;
41+
}
42+
2943
if ( routeDirection == UrlGeneration )
3044
{
31-
return !IsNullOrEmpty( routeKey ) && values.TryGetValue( routeKey, out value ) && !IsNullOrEmpty( value );
45+
return !IsNullOrEmpty( value );
3246
}
3347

3448
var requestedVersion = default( ApiVersion );
3549

36-
if ( !values.TryGetValue( routeKey, out value ) || !TryParse( value, out requestedVersion ) )
50+
if ( TryParse( value, out requestedVersion ) )
3751
{
38-
return false;
52+
httpContext.SetRequestedApiVersion( requestedVersion );
53+
return true;
3954
}
4055

41-
httpContext.SetRequestedApiVersion( requestedVersion );
42-
return true;
56+
return false;
4357
}
4458
}
4559
}
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,35 @@
11
namespace Microsoft.AspNetCore.Mvc.Versioning
22
{
3+
using AspNetCore.Routing;
34
using Http;
45
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
76
using static System.String;
87

98
/// <content>
109
/// Provides the implementation for ASP.NET Core.
1110
/// </content>
1211
[CLSCompliant( false )]
13-
public partial class QueryStringOrHeaderApiVersionReader
12+
public partial class UrlSegmentApiVersionReader
1413
{
1514
/// <summary>
1615
/// Reads the service API version value from a request.
1716
/// </summary>
1817
/// <param name="request">The <see cref="HttpRequest">HTTP request</see> to read the API version from.</param>
1918
/// <returns>The raw, unparsed service API version value read from the request or <c>null</c> if request does not contain an API version.</returns>
2019
/// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
21-
public override string Read( HttpRequest request )
20+
public virtual string Read( HttpRequest request )
2221
{
23-
var version = base.Read( request );
24-
var versions = new HashSet<string>( StringComparer.OrdinalIgnoreCase );
22+
Arg.NotNull( request, nameof( request ) );
2523

26-
if ( !IsNullOrEmpty( version ) )
27-
{
28-
versions.Add( version );
29-
}
30-
31-
version = ReadFromHeader( request, HeaderNames );
24+
var context = request.HttpContext;
25+
var key = context.GetRouteParameterNameAssignedByApiVersionRouteConstraint();
3226

33-
if ( !IsNullOrEmpty( version ) )
27+
if ( IsNullOrEmpty( key ) )
3428
{
35-
versions.Add( version );
29+
return null;
3630
}
3731

38-
return versions.EnsureZeroOrOneApiVersions();
32+
return context.GetRouteValue( key )?.ToString();
3933
}
4034
}
41-
}
35+
}

0 commit comments

Comments
 (0)