Skip to content

Commit c2bdc60

Browse files
Chris Martinezcommonsensesoftware
Chris Martinez
authored andcommitted
Handle model bound API version in route template. Fixes #671
1 parent 3098017 commit c2bdc60

File tree

4 files changed

+88
-54
lines changed

4 files changed

+88
-54
lines changed

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -324,69 +324,91 @@ protected virtual bool TryExpandUriParameters( IHttpRoute route, IParsedRoute pa
324324

325325
foreach ( var parameterDescription in parameterDescriptions )
326326
{
327-
if ( parameterDescription.Source == FromUri )
327+
switch ( parameterDescription.Source )
328328
{
329-
if ( parameterDescription.ParameterDescriptor == null )
330-
{
331-
// Undeclared route parameter handling generates query string like "?name={name}"
332-
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
333-
}
334-
else if ( parameterDescription.ParameterDescriptor.ParameterType.CanConvertFromString() )
335-
{
336-
// Simple type generates query string like "?name={name}"
337-
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
338-
}
339-
else if ( IsBindableCollection( parameterDescription.ParameterDescriptor.ParameterType ) )
340-
{
341-
var parameterName = parameterDescription.ParameterDescriptor.ParameterName;
342-
var innerType = GetCollectionElementType( parameterDescription.ParameterDescriptor.ParameterType );
343-
var innerTypeProperties = innerType.GetBindableProperties().ToArray();
329+
case FromUri:
330+
if ( parameterDescription.ParameterDescriptor == null )
331+
{
332+
// Undeclared route parameter handling generates query string like "?name={name}"
333+
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
334+
continue;
335+
}
336+
337+
var parameterType = parameterDescription.ParameterDescriptor.ParameterType;
344338

345-
if ( innerTypeProperties.Any() )
339+
if ( IsApiVersionRouteParameter( parameterType, route.Constraints.Values ) )
346340
{
347-
// Complex array and collection generate query string like
348-
// "?name[0].foo={name[0].foo}&name[0].bar={name[0].bar}&name[1].foo={name[1].foo}&name[1].bar={name[1].bar}"
349-
AddPlaceholderForProperties( parameterValuesForRoute, innerTypeProperties, parameterName + "[0]." );
350-
AddPlaceholderForProperties( parameterValuesForRoute, innerTypeProperties, parameterName + "[1]." );
341+
// model build parameter based on route constraint like "api/v{version:apiVersion}"
342+
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
351343
}
352-
else
344+
else if ( parameterType.CanConvertFromString() )
353345
{
354-
// Simple array and collection generate query string like "?name[0]={name[0]}&name[1]={name[1]}".
355-
AddPlaceholder( parameterValuesForRoute, parameterName + "[0]" );
356-
AddPlaceholder( parameterValuesForRoute, parameterName + "[1]" );
346+
// Simple type generates query string like "?name={name}"
347+
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
357348
}
358-
}
359-
else if ( IsBindableKeyValuePair( parameterDescription.ParameterDescriptor.ParameterType ) )
360-
{
361-
// KeyValuePair generates query string like "?key={key}&value={value}"
362-
AddPlaceholder( parameterValuesForRoute, "key" );
363-
AddPlaceholder( parameterValuesForRoute, "value" );
364-
}
365-
else if ( IsBindableDictionry( parameterDescription.ParameterDescriptor.ParameterType ) )
366-
{
367-
// Dictionary generates query string like
368-
// "?dict[0].key={dict[0].key}&dict[0].value={dict[0].value}&dict[1].key={dict[1].key}&dict[1].value={dict[1].value}"
369-
var parameterName = parameterDescription.ParameterDescriptor.ParameterName;
370-
AddPlaceholder( parameterValuesForRoute, parameterName + "[0].key" );
371-
AddPlaceholder( parameterValuesForRoute, parameterName + "[0].value" );
372-
AddPlaceholder( parameterValuesForRoute, parameterName + "[1].key" );
373-
AddPlaceholder( parameterValuesForRoute, parameterName + "[1].value" );
374-
}
375-
else if ( parameterDescription.CanConvertPropertiesFromString() )
376-
{
377-
if ( emitPrefixes )
349+
else if ( IsBindableCollection( parameterType ) )
378350
{
379-
prefix = parameterDescription.Name + ".";
351+
var parameterName = parameterDescription.ParameterDescriptor.ParameterName;
352+
var innerType = GetCollectionElementType( parameterType );
353+
var innerTypeProperties = innerType.GetBindableProperties().ToArray();
354+
355+
if ( innerTypeProperties.Any() )
356+
{
357+
// Complex array and collection generate query string like
358+
// "?name[0].foo={name[0].foo}&name[0].bar={name[0].bar}&name[1].foo={name[1].foo}&name[1].bar={name[1].bar}"
359+
AddPlaceholderForProperties( parameterValuesForRoute, innerTypeProperties, parameterName + "[0]." );
360+
AddPlaceholderForProperties( parameterValuesForRoute, innerTypeProperties, parameterName + "[1]." );
361+
}
362+
else
363+
{
364+
// Simple array and collection generate query string like "?name[0]={name[0]}&name[1]={name[1]}".
365+
AddPlaceholder( parameterValuesForRoute, parameterName + "[0]" );
366+
AddPlaceholder( parameterValuesForRoute, parameterName + "[1]" );
367+
}
368+
}
369+
else if ( IsBindableKeyValuePair( parameterType ) )
370+
{
371+
// KeyValuePair generates query string like "?key={key}&value={value}"
372+
AddPlaceholder( parameterValuesForRoute, "key" );
373+
AddPlaceholder( parameterValuesForRoute, "value" );
374+
}
375+
else if ( IsBindableDictionry( parameterType ) )
376+
{
377+
// Dictionary generates query string like
378+
// "?dict[0].key={dict[0].key}&dict[0].value={dict[0].value}&dict[1].key={dict[1].key}&dict[1].value={dict[1].value}"
379+
var parameterName = parameterDescription.ParameterDescriptor.ParameterName;
380+
AddPlaceholder( parameterValuesForRoute, parameterName + "[0].key" );
381+
AddPlaceholder( parameterValuesForRoute, parameterName + "[0].value" );
382+
AddPlaceholder( parameterValuesForRoute, parameterName + "[1].key" );
383+
AddPlaceholder( parameterValuesForRoute, parameterName + "[1].value" );
380384
}
385+
else if ( parameterDescription.CanConvertPropertiesFromString() )
386+
{
387+
if ( emitPrefixes )
388+
{
389+
prefix = parameterDescription.Name + ".";
390+
}
381391

382-
// Inserting the individual properties of the object in the query string as all the complex object can not be converted from string,
383-
// but all its individual properties can.
384-
AddPlaceholderForProperties( parameterValuesForRoute, parameterDescription.GetBindableProperties(), prefix );
385-
}
392+
// Inserting the individual properties of the object in the query string as all the complex object can not be converted from string,
393+
// but all its individual properties can.
394+
AddPlaceholderForProperties( parameterValuesForRoute, parameterDescription.GetBindableProperties(), prefix );
395+
}
396+
397+
break;
398+
case Unknown:
399+
if ( IsApiVersionRouteParameter( parameterDescription, route.Constraints.Values ) )
400+
{
401+
// model build parameter based on route constraint like "api/v{version:apiVersion}"
402+
AddPlaceholder( parameterValuesForRoute, parameterDescription.Name );
403+
}
404+
405+
break;
386406
}
387407
}
388408

389-
var boundRouteTemplate = parsedRoute.Bind( null, parameterValuesForRoute, new HttpRouteValueDictionary( route.Defaults ), new HttpRouteValueDictionary( route.Constraints ) );
409+
var defaultValues = new HttpRouteValueDictionary( route.Defaults );
410+
var constraints = new HttpRouteValueDictionary( route.Constraints );
411+
var boundRouteTemplate = parsedRoute.Bind( null, parameterValuesForRoute, defaultValues, constraints );
390412

391413
if ( boundRouteTemplate == null )
392414
{
@@ -398,6 +420,12 @@ protected virtual bool TryExpandUriParameters( IHttpRoute route, IParsedRoute pa
398420
return true;
399421
}
400422

423+
static bool IsApiVersionRouteParameter( ApiParameterDescription parameter, IEnumerable<object> constraints ) =>
424+
parameter.ParameterDescriptor != null && IsApiVersionRouteParameter( parameter.ParameterDescriptor.ParameterType, constraints );
425+
426+
static bool IsApiVersionRouteParameter( Type? parameterType, IEnumerable<object> constraints ) =>
427+
parameterType != null && typeof( ApiVersion ).IsAssignableFrom( parameterType ) && constraints.OfType<ApiVersionRouteConstraint>().Any();
428+
401429
static IEnumerable<IHttpRoute> FlattenRoutes( IEnumerable<IHttpRoute> routes )
402430
{
403431
foreach ( var route in routes )
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
None
1+
API Explorer Fails with API Version Parameter (#671)

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Routing/IParsedRoute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public interface IParsedRoute
1616
/// <param name="defaultValues">The <see cref="HttpRouteValueDictionary">dictionary</see> of default values.</param>
1717
/// <param name="constraints">The <see cref="HttpRouteValueDictionary">dictionary</see> of constraints.</param>
1818
/// <returns>A new <see cref="IBoundRouteTemplate">bound route template</see>.</returns>
19-
IBoundRouteTemplate Bind( IDictionary<string, object>? currentValues, IDictionary<string, object> values, HttpRouteValueDictionary defaultValues, HttpRouteValueDictionary constraints );
19+
IBoundRouteTemplate? Bind( IDictionary<string, object>? currentValues, IDictionary<string, object> values, HttpRouteValueDictionary defaultValues, HttpRouteValueDictionary constraints );
2020

2121
/// <summary>
2222
/// Gets the path segments associated with the parsed route.

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Routing/ParsedRouteAdapter{T}.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ public ParsedRouteAdapter( T adapted )
2222
pathSegments = new Lazy<IReadOnlyList<IPathSegment>>( AdaptToPathSegments );
2323
}
2424

25-
public IBoundRouteTemplate Bind( IDictionary<string, object>? currentValues, IDictionary<string, object> values, HttpRouteValueDictionary defaultValues, HttpRouteValueDictionary constraints )
25+
public IBoundRouteTemplate? Bind( IDictionary<string, object>? currentValues, IDictionary<string, object> values, HttpRouteValueDictionary defaultValues, HttpRouteValueDictionary constraints )
2626
{
2727
var boundRouteTemplate = bindFunc.Value( adapted, currentValues, values, defaultValues, constraints );
28+
29+
if ( boundRouteTemplate == null )
30+
{
31+
return default;
32+
}
33+
2834
var adapterType = typeof( BoundRouteTemplateAdapter<> ).MakeGenericType( boundRouteTemplate.GetType() );
2935
var adapter = (IBoundRouteTemplate) Activator.CreateInstance( adapterType, boundRouteTemplate );
3036

0 commit comments

Comments
 (0)