Skip to content

Commit 8556f44

Browse files
author
Chris Martinez
committed
Added a versioned AttributeRoutingConvention, which skips processing for controllers with API versions that do not have corresponding models in the underlying EDM
1 parent 30571bc commit 8556f44

File tree

3 files changed

+167
-16
lines changed

3 files changed

+167
-16
lines changed

src/Microsoft.AspNet.OData.Versioning/System.Web.OData/HttpConfigurationExtensions.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
/// </summary>
2222
public static class HttpConfigurationExtensions
2323
{
24-
private const string UnsupportedVersionRouteNameFormat = "{0}-UnsupportedVersion-{1}";
2524
private const string ResolverSettingsKey = "System.Web.OData.ResolverSettingsKey";
2625
private static readonly Lazy<Action<DefaultODataPathHandler, object>> setResolverSettings = new Lazy<Action<DefaultODataPathHandler, object>>( GetResolverSettingsMutator );
2726

@@ -208,27 +207,23 @@ public static IReadOnlyList<ODataRoute> MapVersionedODataRoutes(
208207
routeConventions.Insert( 0, null );
209208

210209
var odataRoutes = new List<ODataRoute>();
211-
var unversionedRouteConstraints = new List<ODataPathRouteConstraint>();
212210

213211
foreach ( var model in models )
214212
{
215213
var versionedRouteName = routeName;
216214
var apiVersion = model.GetAnnotationValue<ApiVersionAnnotation>( model )?.ApiVersion;
217215
var routeConstraint = default( ODataPathRouteConstraint );
218216

219-
routeConventions[0] = new AttributeRoutingConvention( model, configuration );
220-
221-
var unversionedRouteConstraint = new ODataPathRouteConstraint( pathHandler, model, versionedRouteName, routeConventions.ToArray() );
217+
routeConventions[0] = new VersionedAttributeRoutingConvention( model, configuration );
222218

223219
if ( apiVersion == null )
224220
{
225-
routeConstraint = unversionedRouteConstraint;
221+
routeConstraint = new ODataPathRouteConstraint( pathHandler, model, versionedRouteName, routeConventions.ToArray() );
226222
}
227223
else
228224
{
229225
versionedRouteName += "-" + apiVersion.ToString();
230226
routeConstraint = new VersionedODataPathRouteConstraint( pathHandler, model, versionedRouteName, routeConventions.ToArray(), apiVersion );
231-
unversionedRouteConstraints.Add( unversionedRouteConstraint );
232227
}
233228

234229
var route = new ODataRoute( routePrefix, routeConstraint );
@@ -237,12 +232,6 @@ public static IReadOnlyList<ODataRoute> MapVersionedODataRoutes(
237232
odataRoutes.Add( route );
238233
}
239234

240-
for ( var i = 0; i < unversionedRouteConstraints.Count; i++ )
241-
{
242-
var routeConstraint = unversionedRouteConstraints[i];
243-
routes.Add( UnsupportedVersionRouteNameFormat.FormatInvariant( routeName, i ), new ODataRoute( routePrefix, routeConstraint ) );
244-
}
245-
246235
return odataRoutes;
247236
}
248237

@@ -353,8 +342,7 @@ public static ODataRoute MapVersionedODataRoute(
353342

354343
configuration.SetResolverSettings( pathHandler );
355344
model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) );
356-
routeConventions.Insert( 0, null );
357-
routeConventions[0] = new AttributeRoutingConvention( model, configuration );
345+
routeConventions.Insert( 0, new VersionedAttributeRoutingConvention( model, configuration ) );
358346

359347
var routeConstraint = new VersionedODataPathRouteConstraint( pathHandler, model, routeName, routeConventions.ToArray(), apiVersion );
360348
var route = new ODataRoute( routePrefix, routeConstraint );
@@ -364,4 +352,4 @@ public static ODataRoute MapVersionedODataRoute(
364352
return route;
365353
}
366354
}
367-
}
355+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
namespace Microsoft.Web.OData.Routing
2+
{
3+
using Http;
4+
using Microsoft.OData.Edm;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Contracts;
7+
using System.Linq;
8+
using System.Web.Http;
9+
using System.Web.Http.Controllers;
10+
using System.Web.OData.Routing;
11+
using System.Web.OData.Routing.Conventions;
12+
13+
/// <summary>
14+
/// Represents an OData attribute routing convention with additional support for API versioning.
15+
/// </summary>
16+
public class VersionedAttributeRoutingConvention : AttributeRoutingConvention
17+
{
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="VersionedAttributeRoutingConvention"/> class.
20+
/// </summary>
21+
/// <param name="model">The <see cref="IEdmModel">EDM model</see> associated with the routing convention.</param>
22+
/// <param name="configuration">The current <see cref="HttpConfiguration">HTTP configuration</see>.</param>
23+
public VersionedAttributeRoutingConvention( IEdmModel model, HttpConfiguration configuration )
24+
: base( model, configuration )
25+
{
26+
}
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="VersionedAttributeRoutingConvention"/> class.
30+
/// </summary>
31+
/// <param name="model">The <see cref="IEdmModel">EDM model</see> associated with the routing convention.</param>
32+
/// <param name="configuration">The current <see cref="HttpConfiguration">HTTP configuration</see>.</param>
33+
/// <param name="pathTemplateHandler">The <see cref="IODataPathTemplateHandler">OData path template handler</see> associated with the routing convention.</param>
34+
public VersionedAttributeRoutingConvention( IEdmModel model, HttpConfiguration configuration, IODataPathTemplateHandler pathTemplateHandler )
35+
: base( model, configuration, pathTemplateHandler )
36+
{
37+
}
38+
39+
/// <summary>
40+
/// Initializes a new instance of the <see cref="VersionedAttributeRoutingConvention"/> class.
41+
/// </summary>
42+
/// <param name="model">The <see cref="IEdmModel">EDM model</see> associated with the routing convention.</param>
43+
/// <param name="controllers">The <see cref="IEnumerable{T}">sequence</see> of <see cref="HttpControllerDescriptor">controller descriptors</see>
44+
public VersionedAttributeRoutingConvention( IEdmModel model, IEnumerable<HttpControllerDescriptor> controllers )
45+
: base( model, controllers )
46+
{
47+
}
48+
49+
/// <summary>
50+
/// Initializes a new instance of the <see cref="VersionedAttributeRoutingConvention"/> class.
51+
/// </summary>
52+
/// <param name="model">The <see cref="IEdmModel">EDM model</see> associated with the routing convention.</param>
53+
/// <param name="controllers">The <see cref="IEnumerable{T}">sequence</see> of <see cref="HttpControllerDescriptor">controller descriptors</see>
54+
/// associated with the routing convention.</param>
55+
/// <param name="pathTemplateHandler">The <see cref="IODataPathTemplateHandler">OData path template handler</see> associated with the routing convention.</param>
56+
public VersionedAttributeRoutingConvention( IEdmModel model, IEnumerable<HttpControllerDescriptor> controllers, IODataPathTemplateHandler pathTemplateHandler )
57+
: base( model, controllers, pathTemplateHandler )
58+
{
59+
}
60+
61+
/// <summary>
62+
/// Returns a value indicating whether the specified controller should be mapped using attribute routing conventions.
63+
/// </summary>
64+
/// <param name="controller">The <see cref="HttpControllerDescriptor">controller descriptor</see> to evaluate.</param>
65+
/// <returns>True if the <paramref name="controller"/> should be mapped as an OData controller; otherwise, false.</returns>
66+
/// <remarks>This method will match any OData controller that is API version-neutral or has a declared API version that
67+
/// matches the API version applied to the associated <see cref="P:Model">model</see>.</remarks>
68+
public override bool ShouldMapController( HttpControllerDescriptor controller )
69+
{
70+
Contract.Assume( controller != null );
71+
72+
var versionModel = controller.GetApiVersionModel();
73+
74+
if ( versionModel.IsApiVersionNeutral )
75+
{
76+
return true;
77+
}
78+
79+
var apiVersion = Model.GetAnnotationValue<ApiVersion>( Model );
80+
81+
return apiVersion != null && versionModel.DeclaredApiVersions.Contains( apiVersion );
82+
}
83+
}
84+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
namespace Microsoft.Web.OData.Routing
2+
{
3+
using Builder;
4+
using FluentAssertions;
5+
using Http;
6+
using Microsoft.OData.Edm;
7+
using Moq;
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using System.Web.Http;
12+
using System.Web.Http.Controllers;
13+
using System.Web.Http.Dispatcher;
14+
using System.Web.OData;
15+
using System.Web.OData.Builder;
16+
using Xunit;
17+
18+
public class VersionedAttributeRoutingConventionTest
19+
{
20+
[ApiVersionNeutral]
21+
private sealed class NeutralController : ODataController
22+
{
23+
}
24+
25+
[ApiVersion( "1.0" )]
26+
private sealed class ControllerV1 : ODataController
27+
{
28+
}
29+
30+
private static IEdmModel CreateModel( HttpConfiguration configuration, Type controllerType )
31+
{
32+
var controllerTypeResolver = new Mock<IHttpControllerTypeResolver>();
33+
var controllerTypes = new List<Type>() { controllerType };
34+
35+
controllerTypeResolver.Setup( ctr => ctr.GetControllerTypes( It.IsAny<IAssembliesResolver>() ) ).Returns( controllerTypes );
36+
configuration.Services.Replace( typeof( IHttpControllerTypeResolver ), controllerTypeResolver.Object );
37+
38+
var builder = new VersionedODataModelBuilder( configuration );
39+
40+
return builder.GetEdmModels().Single();
41+
}
42+
43+
[Fact]
44+
public void should_map_controller_should_return_true_for_versionX2Dneutral_controller()
45+
{
46+
// arrange
47+
var model = new ODataModelBuilder().GetEdmModel();
48+
var controller = new HttpControllerDescriptor( new HttpConfiguration(), string.Empty, typeof( NeutralController ) );
49+
var convention = new VersionedAttributeRoutingConvention( model, new HttpControllerDescriptor[0] );
50+
51+
model.SetAnnotationValue( model, new ApiVersion( 1, 0 ) );
52+
53+
// act
54+
var result = convention.ShouldMapController( controller );
55+
56+
// assert
57+
result.Should().BeTrue();
58+
}
59+
60+
[Theory]
61+
[InlineData( 1, true )]
62+
[InlineData( 2, false )]
63+
public void should_map_controller_should_return_expected_result_for_controller_version( int majorVersion, bool expected )
64+
{
65+
// arrange
66+
var model = new ODataModelBuilder().GetEdmModel();
67+
var controller = new HttpControllerDescriptor( new HttpConfiguration(), string.Empty, typeof( ControllerV1 ) );
68+
var convention = new VersionedAttributeRoutingConvention( model, new HttpControllerDescriptor[0] );
69+
70+
model.SetAnnotationValue( model, new ApiVersion( majorVersion, 0 ) );
71+
72+
// act
73+
var result = convention.ShouldMapController( controller );
74+
75+
// assert
76+
result.Should().Be( expected );
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)