8
8
using Microsoft ;
9
9
using Microsoft . OData . Edm ;
10
10
using Microsoft . Web . Http ;
11
+ using Microsoft . Web . Http . Routing ;
11
12
using Microsoft . Web . OData . Builder ;
12
13
using Microsoft . Web . OData . Routing ;
13
14
using OData . Batch ;
14
15
using OData . Extensions ;
15
16
using OData . Routing ;
16
17
using OData . Routing . Conventions ;
18
+ using Routing ;
17
19
using static Linq . Expressions . Expression ;
20
+ using static System . String ;
21
+ using static System . StringComparison ;
18
22
19
23
/// <summary>
20
24
/// Provides extension methods for the <see cref="HttpConfiguration"/> class.
21
25
/// </summary>
22
26
public static class HttpConfigurationExtensions
23
27
{
24
28
private const string ResolverSettingsKey = "System.Web.OData.ResolverSettingsKey" ;
29
+ private const string UnversionedRouteSuffix = "-Unversioned" ;
30
+ private const string ApiVersionConstraintName = "apiVersion" ;
31
+ private const string ApiVersionConstraint = "{" + ApiVersionConstraintName + "}" ;
25
32
private static readonly Lazy < Action < DefaultODataPathHandler , object > > setResolverSettings = new Lazy < Action < DefaultODataPathHandler , object > > ( GetResolverSettingsMutator ) ;
26
33
27
34
private static Action < DefaultODataPathHandler , object > GetResolverSettingsMutator ( )
@@ -192,46 +199,42 @@ public static IReadOnlyList<ODataRoute> MapVersionedODataRoutes(
192
199
var routeConventions = EnsureConventions ( routingConventions . ToList ( ) ) ;
193
200
var routes = configuration . Routes ;
194
201
195
- if ( ! string . IsNullOrEmpty ( routePrefix ) )
202
+ if ( ! IsNullOrEmpty ( routePrefix ) )
196
203
{
197
204
routePrefix = routePrefix . TrimEnd ( '/' ) ;
198
205
}
199
206
200
207
if ( batchHandler != null )
201
208
{
202
- var batchTemplate = string . IsNullOrEmpty ( routePrefix ) ? ODataRouteConstants . Batch : routePrefix + '/' + ODataRouteConstants . Batch ;
209
+ var batchTemplate = IsNullOrEmpty ( routePrefix ) ? ODataRouteConstants . Batch : routePrefix + '/' + ODataRouteConstants . Batch ;
203
210
routes . MapHttpBatchRoute ( routeName + "Batch" , batchTemplate , batchHandler ) ;
204
211
}
205
212
206
213
configuration . SetResolverSettings ( pathHandler ) ;
207
214
routeConventions . Insert ( 0 , null ) ;
208
215
209
216
var odataRoutes = new List < ODataRoute > ( ) ;
217
+ var unversionedConstraints = new List < IHttpRouteConstraint > ( ) ;
210
218
211
219
foreach ( var model in models )
212
220
{
213
221
var versionedRouteName = routeName ;
214
- var apiVersion = model . GetAnnotationValue < ApiVersionAnnotation > ( model ) ? . ApiVersion ;
215
222
var routeConstraint = default ( ODataPathRouteConstraint ) ;
216
223
217
224
routeConventions [ 0 ] = new VersionedAttributeRoutingConvention ( model , configuration ) ;
218
-
219
- if ( apiVersion == null )
220
- {
221
- routeConstraint = new ODataPathRouteConstraint ( pathHandler , model , versionedRouteName , routeConventions . ToArray ( ) ) ;
222
- }
223
- else
224
- {
225
- versionedRouteName += "-" + apiVersion . ToString ( ) ;
226
- routeConstraint = new VersionedODataPathRouteConstraint ( pathHandler , model , versionedRouteName , routeConventions . ToArray ( ) , apiVersion ) ;
227
- }
225
+ routeConstraint = new ODataPathRouteConstraint ( pathHandler , model , versionedRouteName , routeConventions . ToArray ( ) ) ;
226
+ unversionedConstraints . Add ( routeConstraint ) ;
227
+ routeConstraint = MakeVersionedODataRouteConstraint ( routeConstraint , pathHandler , routeConventions , model , ref versionedRouteName ) ;
228
228
229
229
var route = new ODataRoute ( routePrefix , routeConstraint ) ;
230
230
231
+ AddApiVersionConstraintIfNecessary ( route ) ;
231
232
routes . Add ( versionedRouteName , route ) ;
232
233
odataRoutes . Add ( route ) ;
233
234
}
234
235
236
+ AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch ( routeName , routePrefix , routes , odataRoutes , unversionedConstraints ) ;
237
+
235
238
return odataRoutes ;
236
239
}
237
240
@@ -329,14 +332,14 @@ public static ODataRoute MapVersionedODataRoute(
329
332
var routeConventions = EnsureConventions ( routingConventions . ToList ( ) ) ;
330
333
var routes = configuration . Routes ;
331
334
332
- if ( ! string . IsNullOrEmpty ( routePrefix ) )
335
+ if ( ! IsNullOrEmpty ( routePrefix ) )
333
336
{
334
337
routePrefix = routePrefix . TrimEnd ( '/' ) ;
335
338
}
336
339
337
340
if ( batchHandler != null )
338
341
{
339
- var batchTemplate = string . IsNullOrEmpty ( routePrefix ) ? ODataRouteConstants . Batch : routePrefix + '/' + ODataRouteConstants . Batch ;
342
+ var batchTemplate = IsNullOrEmpty ( routePrefix ) ? ODataRouteConstants . Batch : routePrefix + '/' + ODataRouteConstants . Batch ;
340
343
routes . MapHttpBatchRoute ( routeName + "Batch" , batchTemplate , batchHandler ) ;
341
344
}
342
345
@@ -347,9 +350,80 @@ public static ODataRoute MapVersionedODataRoute(
347
350
var routeConstraint = new VersionedODataPathRouteConstraint ( pathHandler , model , routeName , routeConventions . ToArray ( ) , apiVersion ) ;
348
351
var route = new ODataRoute ( routePrefix , routeConstraint ) ;
349
352
353
+ AddApiVersionConstraintIfNecessary ( route ) ;
350
354
routes . Add ( routeName , route ) ;
351
355
356
+ var unversionedRouteConstraint = new ODataPathRouteConstraint ( pathHandler , model , routeName , routeConventions . ToArray ( ) ) ;
357
+ var unversionedRoute = new ODataRoute ( routePrefix , new UnversionedODataPathRouteConstraint ( unversionedRouteConstraint , apiVersion ) ) ;
358
+
359
+ AddApiVersionConstraintIfNecessary ( unversionedRoute ) ;
360
+ routes . Add ( routeName + UnversionedRouteSuffix , unversionedRoute ) ;
361
+
352
362
return route ;
353
363
}
364
+
365
+ private static ODataPathRouteConstraint MakeVersionedODataRouteConstraint (
366
+ ODataPathRouteConstraint routeConstraint ,
367
+ IODataPathHandler pathHandler ,
368
+ IList < IODataRoutingConvention > routeConventions ,
369
+ IEdmModel model ,
370
+ ref string versionedRouteName )
371
+ {
372
+ Contract . Requires ( routeConstraint != null ) ;
373
+ Contract . Requires ( pathHandler != null ) ;
374
+ Contract . Requires ( routeConventions != null ) ;
375
+ Contract . Requires ( model != null ) ;
376
+ Contract . Requires ( ! IsNullOrEmpty ( versionedRouteName ) ) ;
377
+ Contract . Ensures ( Contract . Result < ODataPathRouteConstraint > ( ) != null ) ;
378
+
379
+ var apiVersion = model . GetAnnotationValue < ApiVersionAnnotation > ( model ) ? . ApiVersion ;
380
+
381
+ if ( apiVersion == null )
382
+ {
383
+ return routeConstraint ;
384
+ }
385
+
386
+ versionedRouteName += "-" + apiVersion . ToString ( ) ;
387
+ return new VersionedODataPathRouteConstraint ( pathHandler , model , versionedRouteName , routeConventions . ToArray ( ) , apiVersion ) ;
388
+ }
389
+
390
+ private static void AddApiVersionConstraintIfNecessary ( ODataRoute route )
391
+ {
392
+ Contract . Requires ( route != null ) ;
393
+
394
+ var routePrefix = route . RoutePrefix ;
395
+
396
+ if ( routePrefix == null || routePrefix . IndexOf ( ApiVersionConstraint , Ordinal ) < 0 || route . Constraints . ContainsKey ( ApiVersionConstraintName ) )
397
+ {
398
+ return ;
399
+ }
400
+
401
+ // note: even though the constraints are a dictionary, it's important to rebuild the entire collection
402
+ // to make sure the api version constraint is evaluated first; otherwise, the current api version will
403
+ // not be resolved when the odata versioning constraint is evaluated
404
+ var originalConstraints = new Dictionary < string , object > ( route . Constraints ) ;
405
+
406
+ route . Constraints . Clear ( ) ;
407
+ route . Constraints . Add ( ApiVersionConstraintName , new ApiVersionRouteConstraint ( ) ) ;
408
+
409
+ foreach ( var constraint in originalConstraints )
410
+ {
411
+ route . Constraints . Add ( constraint . Key , constraint . Value ) ;
412
+ }
413
+ }
414
+
415
+ private static void AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch ( string routeName , string routePrefix , HttpRouteCollection routes , List < ODataRoute > odataRoutes , List < IHttpRouteConstraint > unversionedConstraints )
416
+ {
417
+ Contract . Requires ( ! IsNullOrEmpty ( routeName ) ) ;
418
+ Contract . Requires ( routes != null ) ;
419
+ Contract . Requires ( odataRoutes != null ) ;
420
+ Contract . Requires ( unversionedConstraints != null ) ;
421
+
422
+ var unversionedRoute = new ODataRoute ( routePrefix , new UnversionedODataPathRouteConstraint ( unversionedConstraints ) ) ;
423
+
424
+ AddApiVersionConstraintIfNecessary ( unversionedRoute ) ;
425
+ routes . Add ( routeName + UnversionedRouteSuffix , unversionedRoute ) ;
426
+ odataRoutes . Add ( unversionedRoute ) ;
427
+ }
354
428
}
355
429
}
0 commit comments