Skip to content

Commit 69bacae

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Support array URL syntax for collection parameters. Fixes #496
1 parent 0537760 commit 69bacae

File tree

1 file changed

+161
-47
lines changed

1 file changed

+161
-47
lines changed

src/Common.OData.ApiExplorer/AspNet.OData/Routing/ODataRouteBuilder.cs

Lines changed: 161 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ void AppendEntitySetOrOperation( IList<string> segments )
9494
#else
9595
var controllerDescriptor = Context.ActionDescriptor;
9696
#endif
97-
var controllerName = controllerDescriptor.ControllerName;
9897

9998
if ( Context.IsAttributeRouted )
10099
{
@@ -103,32 +102,47 @@ void AppendEntitySetOrOperation( IList<string> segments )
103102
#else
104103
var prefix = controllerDescriptor.ControllerTypeInfo.GetCustomAttributes<ODataRoutePrefixAttribute>().FirstOrDefault()?.Prefix?.Trim( '/' );
105104
#endif
106-
var template = Context.RouteTemplate;
105+
AppendEntitySetOrOperationFromAttributes( segments, prefix );
106+
}
107+
else
108+
{
109+
AppendEntitySetOrOperationFromConvention( segments, controllerDescriptor.ControllerName );
110+
}
111+
}
112+
113+
void AppendEntitySetOrOperationFromAttributes( IList<string> segments, string prefix )
114+
{
115+
var template = Context.RouteTemplate;
107116

108-
if ( IsNullOrEmpty( prefix ) )
117+
if ( Context.IsOperation && Context.RouteTemplateGeneration == Client )
118+
{
119+
template = FixUpArrayParameters( template, Context.Operation );
120+
}
121+
122+
if ( IsNullOrEmpty( prefix ) )
123+
{
124+
segments.Add( template );
125+
}
126+
else
127+
{
128+
if ( IsNullOrEmpty( template ) )
129+
{
130+
segments.Add( prefix );
131+
}
132+
else if ( template[0] == '(' && Context.UrlKeyDelimiter == Parentheses )
109133
{
110-
segments.Add( Context.RouteTemplate );
134+
segments.Add( prefix + template );
111135
}
112136
else
113137
{
114-
if ( IsNullOrEmpty( template ) )
115-
{
116-
segments.Add( prefix );
117-
}
118-
else if ( template[0] == '(' && Context.UrlKeyDelimiter == Parentheses )
119-
{
120-
segments.Add( prefix + template );
121-
}
122-
else
123-
{
124-
segments.Add( prefix );
125-
segments.Add( template );
126-
}
138+
segments.Add( prefix );
139+
segments.Add( template );
127140
}
128-
129-
return;
130141
}
142+
}
131143

144+
void AppendEntitySetOrOperationFromConvention( IList<string> segments, string controllerName )
145+
{
132146
var builder = new StringBuilder();
133147

134148
switch ( Context.ActionType )
@@ -244,33 +258,23 @@ void AppendParametersFromConvention( StringBuilder builder, IEdmOperation operat
244258
var actionParameters = Context.ParameterDescriptions.ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase );
245259
var parameter = parameters.Current;
246260
var name = parameter.Name;
247-
#if WEBAPI
248-
var routeParameterName = actionParameters[name].ParameterDescriptor.ParameterName;
249-
#elif API_EXPLORER
250-
var routeParameterName = actionParameters[name].ParameterDescriptor.Name;
251-
#else
252-
var routeParameterName = actionParameters[name].Name;
253-
#endif
261+
var routeParameterName = GetRouteParameterName( actionParameters, name );
254262

255263
builder.Append( '(' );
256264
builder.Append( name );
257265
builder.Append( '=' );
266+
258267
ExpandParameterTemplate( builder, parameter, routeParameterName );
259268

260269
while ( parameters.MoveNext() )
261270
{
262271
parameter = parameters.Current;
263272
name = parameter.Name;
264-
#if WEBAPI
265-
routeParameterName = actionParameters[name].ParameterDescriptor.ParameterName;
266-
#elif API_EXPLORER
267-
routeParameterName = actionParameters[name].ParameterDescriptor.Name;
268-
#else
269-
routeParameterName = actionParameters[name].Name;
270-
#endif
273+
routeParameterName = GetRouteParameterName( actionParameters, name );
271274
builder.Append( ',' );
272275
builder.Append( name );
273276
builder.Append( '=' );
277+
274278
ExpandParameterTemplate( builder, parameter, routeParameterName );
275279
}
276280

@@ -305,29 +309,128 @@ void ExpandParameterTemplate( StringBuilder template, IEdmTypeReference typeRefe
305309
return;
306310
}
307311

308-
if ( typeDef.TypeKind == EdmTypeKind.Enum )
312+
switch ( typeDef.TypeKind )
313+
{
314+
case EdmTypeKind.Collection:
315+
template.Insert( offset, '[' );
316+
template.Append( ']' );
317+
break;
318+
case EdmTypeKind.Enum:
319+
var fullName = typeReference.FullName();
320+
321+
if ( !Context.AllowUnqualifiedEnum )
322+
{
323+
template.Insert( offset, fullName );
324+
offset += fullName.Length;
325+
}
326+
327+
template.Insert( offset, '\'' );
328+
template.Append( '\'' );
329+
break;
330+
default:
331+
var type = typeDef.GetClrType( Context.EdmModel );
332+
333+
if ( quotedTypes.TryGetValue( type, out var prefix ) )
334+
{
335+
template.Insert( offset, prefix );
336+
offset += prefix.Length;
337+
template.Insert( offset, '\'' );
338+
template.Append( '\'' );
339+
}
340+
341+
break;
342+
}
343+
}
344+
345+
string FixUpArrayParameters( string template, IEdmOperation operation )
346+
{
347+
Contract.Requires( !IsNullOrEmpty( template ) );
348+
Contract.Requires( operation != null );
349+
350+
if ( !operation.IsFunction() )
309351
{
310-
var fullName = typeReference.FullName();
352+
return template;
353+
}
311354

312-
if ( !Context.AllowUnqualifiedEnum )
355+
int IndexOfToken( StringBuilder builder, string token )
356+
{
357+
var index = -1;
358+
359+
for ( var i = 0; i < builder.Length; i++ )
313360
{
314-
template.Insert( offset, fullName );
315-
offset += fullName.Length;
361+
if ( builder[i] != '{' )
362+
{
363+
continue;
364+
}
365+
366+
index = i;
367+
++i;
368+
369+
var matched = true;
370+
371+
for ( var j = 0; j < token.Length; i++, j++ )
372+
{
373+
if ( builder[i] != token[j] )
374+
{
375+
matched = false;
376+
break;
377+
}
378+
}
379+
380+
if ( matched )
381+
{
382+
break;
383+
}
384+
385+
while ( builder[i] != '}' )
386+
{
387+
++i;
388+
}
316389
}
317390

318-
template.Insert( offset, '\'' );
319-
template.Append( '\'' );
320-
return;
391+
return index;
321392
}
322393

323-
var type = typeDef.GetClrType( Context.EdmModel );
394+
void InsertBrackets( StringBuilder builder, string token )
395+
{
396+
var index = IndexOfToken( builder, token );
324397

325-
if ( quotedTypes.TryGetValue( type, out var prefix ) )
398+
if ( index >= 0 )
399+
{
400+
builder.Insert( index, '[' ).Insert( index + token.Length + 3, ']' );
401+
}
402+
}
403+
404+
var collectionParameters = from param in operation.Parameters
405+
where param.Type.TypeKind() == EdmTypeKind.Collection &&
406+
param.Name != "bindingParameter"
407+
select param;
408+
409+
using ( var parameters = collectionParameters.GetEnumerator() )
326410
{
327-
template.Insert( offset, prefix );
328-
offset += prefix.Length;
329-
template.Insert( offset, '\'' );
330-
template.Append( '\'' );
411+
if ( !parameters.MoveNext() )
412+
{
413+
return template;
414+
}
415+
416+
var buffer = new StringBuilder( template );
417+
var actionParameters = Context.ParameterDescriptions.ToDictionary( p => p.Name, StringComparer.OrdinalIgnoreCase );
418+
var parameter = parameters.Current;
419+
var name = parameter.Name;
420+
var routeParameterName = GetRouteParameterName( actionParameters, name );
421+
422+
InsertBrackets( buffer, routeParameterName );
423+
424+
while ( parameters.MoveNext() )
425+
{
426+
parameter = parameters.Current;
427+
name = parameter.Name;
428+
routeParameterName = GetRouteParameterName( actionParameters, name );
429+
430+
InsertBrackets( buffer, routeParameterName );
431+
}
432+
433+
return buffer.ToString();
331434
}
332435
}
333436

@@ -416,6 +519,17 @@ IList<ApiParameterDescription> GetQueryParameters( IList<ApiParameterDescription
416519
return queryParameters;
417520
}
418521

522+
static string GetRouteParameterName( IReadOnlyDictionary<string, ApiParameterDescription> actionParameters, string name )
523+
{
524+
#if WEBAPI
525+
return actionParameters[name].ParameterDescriptor.ParameterName;
526+
#elif API_EXPLORER
527+
return actionParameters[name].ParameterDescriptor.Name;
528+
#else
529+
return actionParameters[name].Name;
530+
#endif
531+
}
532+
419533
static bool IsBuiltInParameter( Type parameterType ) => ODataQueryOptionsType.IsAssignableFrom( parameterType ) || ODataActionParametersType.IsAssignableFrom( parameterType );
420534

421535
static bool IsKey( IReadOnlyList<IEdmStructuralProperty> keys, ApiParameterDescription parameter )

0 commit comments

Comments
 (0)