Skip to content

Commit ef5d447

Browse files
Coalesce request properties to single object within the request pipeline (#96)
1 parent 6d6743b commit ef5d447

File tree

19 files changed

+213
-213
lines changed

19 files changed

+213
-213
lines changed

ApiVersioningWithSamples.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Versioning", "Versioning",
4444
src\Common\Versioning\ApiVersionModelDebugView.cs = src\Common\Versioning\ApiVersionModelDebugView.cs
4545
src\Common\Versioning\ApiVersionModelExtensions.cs = src\Common\Versioning\ApiVersionModelExtensions.cs
4646
src\Common\Versioning\ApiVersionReader.cs = src\Common\Versioning\ApiVersionReader.cs
47+
src\Common\Versioning\ApiVersionRequestProperties.cs = src\Common\Versioning\ApiVersionRequestProperties.cs
4748
src\Common\Versioning\ApiVersionsBaseAttribute.cs = src\Common\Versioning\ApiVersionsBaseAttribute.cs
4849
src\Common\Versioning\AttributeExtensions.cs = src\Common\Versioning\AttributeExtensions.cs
4950
src\Common\Versioning\ConstantApiVersionSelector.cs = src\Common\Versioning\ConstantApiVersionSelector.cs
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#if WEBAPI
2+
namespace Microsoft.Web.Http.Versioning
3+
#else
4+
namespace Microsoft.AspNetCore.Mvc.Versioning
5+
#endif
6+
{
7+
using Routing;
8+
using System;
9+
using System.ComponentModel;
10+
using static System.ComponentModel.EditorBrowsableState;
11+
using static ApiVersion;
12+
13+
/// <summary>
14+
/// Represents current API versioning request properties.
15+
/// </summary>
16+
public partial class ApiVersionRequestProperties
17+
{
18+
readonly Lazy<string> rawApiVersion;
19+
bool apiVersionInitialized;
20+
ApiVersion apiVersion;
21+
22+
/// <summary>
23+
/// Gets the raw, unparsed API version for the current request.
24+
/// </summary>
25+
/// <value>The unparsed API version value for the current request.</value>
26+
public string RawApiVersion => rawApiVersion.Value;
27+
28+
/// <summary>
29+
/// Gets the API version for the current request.
30+
/// </summary>
31+
/// <value>The current <see cref="ApiVersion">API version</see> for the current request.</value>
32+
/// <remarks>If an API version was not provided for the current request or the value
33+
/// provided is invalid, this property will return <c>null</c>.</remarks>
34+
public ApiVersion ApiVersion
35+
{
36+
get
37+
{
38+
if ( !apiVersionInitialized )
39+
{
40+
TryParse( RawApiVersion, out apiVersion );
41+
apiVersionInitialized = true;
42+
}
43+
44+
return apiVersion;
45+
}
46+
set
47+
{
48+
apiVersion = value;
49+
apiVersionInitialized = true;
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Gets or sets the route parameter name used in URL segment API versioning.
55+
/// </summary>
56+
/// <value>The route parameter name used in URL segment API versioning.</value>
57+
/// <remarks>This property is typically set by the <see cref="ApiVersionRouteConstraint"/>
58+
/// and is not meant to be directly used in your code.</remarks>
59+
[EditorBrowsable( Never )]
60+
public string RouteParameterName { get; set; }
61+
}
62+
}

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

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedODataPathRouteConstraint.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace Microsoft.Web.OData.Routing
22
{
33
using Http;
4+
using Http.Versioning;
5+
using Microsoft.OData.Core;
46
using Microsoft.OData.Edm;
57
using System.Collections.Generic;
68
using System.Diagnostics.CodeAnalysis;
@@ -10,6 +12,7 @@
1012
using System.Web.Http.Routing;
1113
using System.Web.OData.Routing;
1214
using System.Web.OData.Routing.Conventions;
15+
using static System.Net.HttpStatusCode;
1316
using static System.Web.Http.Routing.HttpRouteDirection;
1417

1518
/// <summary>
@@ -70,7 +73,8 @@ public override bool Match( HttpRequestMessage request, IHttpRoute route, string
7073
return base.Match( request, route, parameterName, values, routeDirection );
7174
}
7275

73-
var requestedVersion = request.GetRequestedApiVersionOrReturnBadRequest();
76+
var properties = request.ApiVersionProperties();
77+
var requestedVersion = GetRequestedApiVersionOrReturnBadRequest( request, properties );
7478

7579
if ( requestedVersion != null )
7680
{
@@ -92,14 +96,30 @@ public override bool Match( HttpRequestMessage request, IHttpRoute route, string
9296

9397
if ( options.AssumeDefaultVersionWhenUnspecified || IsServiceDocumentOrMetadataRoute( values ) )
9498
{
95-
request.SetRequestedApiVersion( ApiVersion );
99+
properties.ApiVersion = ApiVersion;
96100
return base.Match( request, route, parameterName, values, routeDirection );
97101
}
98102

99103
return false;
100104
}
101105

102-
private static void DecorateUrlHelperWithApiVersionRouteValueIfNecessary( HttpRequestMessage request, IDictionary<string, object> values )
106+
static ApiVersion GetRequestedApiVersionOrReturnBadRequest( HttpRequestMessage request, ApiVersionRequestProperties properties )
107+
{
108+
Contract.Requires( request != null );
109+
Contract.Requires( properties != null );
110+
111+
try
112+
{
113+
return properties.ApiVersion;
114+
}
115+
catch ( AmbiguousApiVersionException ex )
116+
{
117+
var error = new ODataError() { ErrorCode = "AmbiguousApiVersion", Message = ex.Message };
118+
throw new HttpResponseException( request.CreateResponse( BadRequest, error ) );
119+
}
120+
}
121+
122+
static void DecorateUrlHelperWithApiVersionRouteValueIfNecessary( HttpRequestMessage request, IDictionary<string, object> values )
103123
{
104124
Contract.Requires( request != null );
105125
Contract.Requires( values != null );

src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/ConventionRouteControllerSelector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal override ControllerSelectionResult SelectController( ApiVersionControll
7474
throw ambiguousException.Value;
7575
}
7676

77-
request.SetRequestedApiVersion( requestedVersion );
77+
request.ApiVersionProperties().ApiVersion = requestedVersion;
7878
result.Controller = versionedController;
7979

8080
return result;

src/Microsoft.AspNet.WebApi.Versioning/Dispatcher/DirectRouteControllerSelector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal override ControllerSelectionResult SelectController( ApiVersionControll
6565
throw CreateAmbiguousControllerException( new[] { versionNeutralController, versionedController } );
6666
}
6767

68-
request.SetRequestedApiVersion( requestedVersion );
68+
request.ApiVersionProperties().ApiVersion = requestedVersion;
6969
result.RequestedVersion = requestedVersion;
7070
result.Controller = versionedController;
7171

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private HttpResponseException CreateBadRequest( ControllerSelectionResult conven
6969
[SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Created exception cannot be disposed. Handled by the caller." )]
7070
private HttpResponseMessage CreateBadRequestForUnspecifiedApiVersionOrInvalidApiVersion( bool versionNeutral )
7171
{
72-
var requestedVersion = request.GetRawRequestedApiVersion();
72+
var requestedVersion = request.ApiVersionProperties().RawApiVersion;
7373
var parsedVersion = default( ApiVersion );
7474
var message = default( string );
7575
var context = default( ErrorResponseContext );

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete
3030
}
3131

3232
var value = default( string );
33+
var properties = request.ApiVersionProperties();
3334

3435
if ( values.TryGetValue( parameterName, out value ) )
3536
{
36-
request.SetRouteParameterName( parameterName );
37+
properties.RouteParameterName = parameterName;
3738
}
3839
else
3940
{
@@ -49,7 +50,7 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete
4950

5051
if ( TryParse( value, out requestedVersion ) )
5152
{
52-
request.SetRequestedApiVersion( requestedVersion );
53+
properties.ApiVersion = requestedVersion;
5354
return true;
5455
}
5556

Lines changed: 16 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
namespace System.Web.Http
22
{
3-
using ComponentModel;
43
using Diagnostics.CodeAnalysis;
54
using Diagnostics.Contracts;
65
using Microsoft;
76
using Microsoft.Web.Http;
8-
using Microsoft.Web.Http.Routing;
97
using Microsoft.Web.Http.Versioning;
108
using Net;
119
using Net.Http;
1210
using System;
13-
using static ComponentModel.EditorBrowsableState;
14-
using static Microsoft.Web.Http.ApiVersion;
15-
using static System.String;
1611

1712
/// <summary>
1813
/// Provides extension methods for the <see cref="HttpRequestMessage"/> class.
1914
/// </summary>
2015
public static class HttpRequestMessageExtensions
2116
{
22-
private const string ApiVersionKey = "MS_" + nameof( ApiVersion );
23-
private const string ApiVersionRouteParameterName = "MS_" + nameof( ApiVersionRouteConstraint ) + "_ParameterName";
17+
const string ApiVersionPropertiesKey = "MS_" + nameof( ApiVersionRequestProperties );
2418

2519
[SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Handled by the caller." )]
26-
private static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, Func<bool, HttpError> errorCreator )
20+
static HttpResponseMessage CreateErrorResponse( this HttpRequestMessage request, HttpStatusCode statusCode, Func<bool, HttpError> errorCreator )
2721
{
2822
Contract.Requires( request != null );
2923
Contract.Requires( errorCreator != null );
@@ -75,20 +69,23 @@ public static ApiVersioningOptions GetApiVersioningOptions( this HttpRequestMess
7569
}
7670

7771
/// <summary>
78-
/// Gets the current raw, unparsed service API version requested.
72+
/// Gets the current API versioning request properties.
7973
/// </summary>
80-
/// <param name="request">The <see cref="HttpRequestMessage">request</see> to get the API version for.</param>
81-
/// <returns>The raw, unparsed service API version or <c>null</c> if no service API version was requested.</returns>
82-
/// <remarks>This method is primarily meant for internal use and is generally only useful for instrumentation purposes.
83-
/// It is recommended that you use the <see cref="GetRequestedApiVersion(HttpRequestMessage)"/> instead.</remarks>
84-
/// <exception cref="AmbiguousApiVersionException">Multiple, different API versions were requested.</exception>
85-
[EditorBrowsable( Never )]
86-
public static string GetRawRequestedApiVersion( this HttpRequestMessage request )
74+
/// <param name="request">The <see cref="HttpRequestMessage">request</see> to get the API versioning properties for.</param>
75+
/// <returns>The current <see cref="ApiVersionRequestProperties">API versioning properties</see>.</returns>
76+
public static ApiVersionRequestProperties ApiVersionProperties( this HttpRequestMessage request )
8777
{
8878
Arg.NotNull( request, nameof( request ) );
79+
Contract.Ensures( Contract.Result<ApiVersionRequestProperties>() != null );
80+
81+
var properties = default( ApiVersionRequestProperties );
82+
83+
if ( !request.Properties.TryGetValue( ApiVersionPropertiesKey, out properties ) )
84+
{
85+
request.Properties[ApiVersionPropertiesKey] = properties = new ApiVersionRequestProperties( request );
86+
}
8987

90-
var reader = request.GetApiVersioningOptions().ApiVersionReader;
91-
return reader.Read( request );
88+
return properties;
9289
}
9390

9491
/// <summary>
@@ -103,73 +100,7 @@ public static string GetRawRequestedApiVersion( this HttpRequestMessage request
103100
public static ApiVersion GetRequestedApiVersion( this HttpRequestMessage request )
104101
{
105102
Arg.NotNull( request, nameof( request ) );
106-
107-
var version = default( ApiVersion );
108-
109-
if ( request.Properties.TryGetValue( ApiVersionKey, out version ) )
110-
{
111-
return version;
112-
}
113-
114-
var value = request.GetRawRequestedApiVersion();
115-
116-
if ( TryParse( value, out version ) )
117-
{
118-
request.Properties[ApiVersionKey] = version;
119-
return version;
120-
}
121-
122-
request.Properties[ApiVersionKey] = null;
123-
return null;
124-
}
125-
126-
/// <summary>
127-
/// Gets the current service API version requested.
128-
/// </summary>
129-
/// <param name="request">The <see cref="HttpRequestMessage">request</see> to get the API version for.</param>
130-
/// <param name="version">The <see cref="ApiVersion">API version</see> to be set as the requested value.</param>
131-
/// <remarks>This method is for internal use and is not meant to be called directly in your code.</remarks>
132-
[EditorBrowsable( Never )]
133-
public static void SetRequestedApiVersion( this HttpRequestMessage request, ApiVersion version )
134-
{
135-
Arg.NotNull( request, nameof( request ) );
136-
137-
if ( version == null )
138-
{
139-
request.Properties.Remove( ApiVersionKey );
140-
}
141-
else
142-
{
143-
request.Properties[ApiVersionKey] = version;
144-
}
145-
}
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-
}
103+
return request.ApiVersionProperties().ApiVersion;
173104
}
174105
}
175106
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace Microsoft.Web.Http.Versioning
2+
{
3+
using System;
4+
using System.Net.Http;
5+
using System.Web.Http;
6+
7+
/// <content>
8+
/// Provides additional implementation specific to ASP.NET Web API.
9+
/// </content>
10+
public partial class ApiVersionRequestProperties
11+
{
12+
readonly HttpRequestMessage request;
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="ApiVersionRequestProperties"/> class.
16+
/// </summary>
17+
/// <param name="request">The current <see cref="HttpRequestMessage">HTTP request</see>.</param>
18+
public ApiVersionRequestProperties( HttpRequestMessage request )
19+
{
20+
Arg.NotNull( request, nameof( request ) );
21+
22+
this.request = request;
23+
rawApiVersion = new Lazy<string>( GetRawApiVersion );
24+
}
25+
26+
string GetRawApiVersion()
27+
{
28+
var options = request.GetApiVersioningOptions();
29+
var reader = options.ApiVersionReader;
30+
return reader.Read( request );
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)