11namespace Microsoft . Web . Http . Description
22{
3+ using Microsoft . OData . Edm ;
34 using System ;
45 using System . Collections . Generic ;
56 using System . Diagnostics . Contracts ;
67 using System . Linq ;
7- using System . Web . Http . Controllers ;
8- using System . Web . Http . Routing ;
8+ using System . Text ;
9+ using System . Web . Http . Description ;
10+ using System . Web . OData ;
11+ using System . Web . OData . Query ;
912 using System . Web . OData . Routing ;
1013 using static System . Linq . Enumerable ;
1114 using static System . String ;
15+ using static System . StringComparison ;
16+ using static System . Web . Http . Description . ApiParameterSource ;
1217
1318 sealed class ODataRouteBuilder
1419 {
15- readonly string routeTemplate ;
16- readonly IHttpRoute route ;
17- readonly HttpActionDescriptor actionDescriptor ;
18-
19- internal ODataRouteBuilder ( string routeTemplate , IHttpRoute route , HttpActionDescriptor actionDescriptor )
20+ static readonly Type GeographyType = typeof ( Spatial . Geography ) ;
21+ static readonly Type GeometryType = typeof ( Spatial . Geometry ) ;
22+ static readonly Dictionary < Type , string > quotedTypes = new Dictionary < Type , string > ( )
2023 {
21- Contract . Requires ( ! IsNullOrEmpty ( routeTemplate ) ) ;
22- Contract . Requires ( route != null ) ;
23- Contract . Requires ( actionDescriptor != null ) ;
24+ [ typeof ( string ) ] = "" ,
25+ [ typeof ( TimeSpan ) ] = "duration" ,
26+ [ typeof ( byte [ ] ) ] = "binary"
27+ } ;
2428
25- this . routeTemplate = routeTemplate ;
26- this . route = route ;
27- this . actionDescriptor = actionDescriptor ;
29+ internal ODataRouteBuilder ( ODataRouteBuilderContext context )
30+ {
31+ Contract . Requires ( context != null ) ;
32+ Context = context ;
2833 }
2934
3035 internal string Build ( )
3136 {
32- if ( ! ( route is ODataRoute odataRoute ) )
33- {
34- return routeTemplate ;
35- }
37+ var builder = new StringBuilder ( ) ;
38+
39+ BuildPath ( builder ) ;
40+ BuildQuery ( builder ) ;
41+
42+ return builder . ToString ( ) ;
43+ }
44+
45+ ODataRouteBuilderContext Context { get ; }
46+
47+ void BuildPath ( StringBuilder builder )
48+ {
49+ Contract . Requires ( builder != null ) ;
3650
3751 var segments = new List < string > ( ) ;
38- var prefix = odataRoute . RoutePrefix ? . Trim ( '/' ) ;
52+ var prefix = Context . Route . RoutePrefix ? . Trim ( '/' ) ;
3953
4054 if ( ! IsNullOrEmpty ( prefix ) )
4155 {
4256 segments . Add ( prefix ) ;
4357 }
4458
45- var controllerDescriptor = actionDescriptor . ControllerDescriptor ;
46- var path = controllerDescriptor . GetCustomAttributes < ODataRoutePrefixAttribute > ( ) . FirstOrDefault ( ) ? . Prefix ? . Trim ( '/' ) ;
59+ var path = GetEntitySetSegment ( ) + GetEntityKeySegment ( ) ;
60+
61+ segments . Add ( path ) ;
62+ builder . Append ( Join ( "/" , segments ) ) ;
63+ }
64+
65+ void BuildQuery ( StringBuilder builder )
66+ {
67+ Contract . Requires ( builder != null ) ;
68+
69+ var queryParameters = FilterQueryParameters ( Context . ParameterDescriptions ) ;
70+
71+ if ( queryParameters . Count == 0 )
72+ {
73+ return ;
74+ }
75+
76+ var queryString = new StringBuilder ( ) ;
4777
48- if ( IsNullOrEmpty ( path ) )
78+ using ( var iterator = queryParameters . GetEnumerator ( ) )
4979 {
50- path = controllerDescriptor . ControllerName ;
80+ iterator . MoveNext ( ) ;
81+ var name = iterator . Current . Name ;
82+
83+ queryString . Append ( name ) ;
84+ queryString . Append ( "={" ) ;
85+ queryString . Append ( name ) ;
86+ queryString . Append ( '}' ) ;
87+
88+ while ( iterator . MoveNext ( ) )
89+ {
90+ name = iterator . Current . Name ;
91+ queryString . Append ( '&' ) ;
92+ queryString . Append ( name ) ;
93+ queryString . Append ( "={" ) ;
94+ queryString . Append ( name ) ;
95+ queryString . Append ( '}' ) ;
96+ }
5197 }
5298
53- var template = actionDescriptor . GetCustomAttributes < ODataRouteAttribute > ( ) . FirstOrDefault ( ) ? . PathTemplate ;
99+ if ( queryString . Length > 0 )
100+ {
101+ builder . Append ( '?' ) ;
102+ builder . Append ( queryString ) ;
103+ }
104+ }
105+
106+ string GetEntitySetSegment ( )
107+ {
108+ var controllerDescriptor = Context . ActionDescriptor . ControllerDescriptor ;
109+ var prefix = controllerDescriptor . GetCustomAttributes < ODataRoutePrefixAttribute > ( ) . FirstOrDefault ( ) ? . Prefix ? . Trim ( '/' ) ;
110+ return IsNullOrEmpty ( prefix ) ? controllerDescriptor . ControllerName : prefix ;
111+ }
112+
113+ string GetEntityKeySegment ( )
114+ {
115+ var template = Context . ActionDescriptor . GetCustomAttributes < ODataRouteAttribute > ( ) . FirstOrDefault ( ) ? . PathTemplate ;
54116
55117 if ( ! IsNullOrEmpty ( template ) )
56118 {
57- path += template ;
119+ return template ;
58120 }
59121
60- segments . Add ( path ) ;
122+ var keys = Context . EntityKeys . Where ( key => Context . ParameterDescriptions . Any ( p => key . Name . Equals ( p . Name , OrdinalIgnoreCase ) ) ) ;
123+ var convention = new StringBuilder ( ) ;
124+
125+ using ( var iterator = keys . GetEnumerator ( ) )
126+ {
127+ if ( iterator . MoveNext ( ) )
128+ {
129+ convention . Append ( '(' ) ;
130+
131+ var key = iterator . Current ;
132+
133+ if ( iterator . MoveNext ( ) )
134+ {
135+ convention . Append ( key . Name ) ;
136+ convention . Append ( '=' ) ;
137+ ExpandParameterTemplate ( convention , key ) ;
138+
139+ while ( iterator . MoveNext ( ) )
140+ {
141+ convention . Append ( ',' ) ;
142+ convention . Append ( key . Name ) ;
143+ convention . Append ( '=' ) ;
144+ ExpandParameterTemplate ( convention , key ) ;
145+ }
146+ }
147+ else
148+ {
149+ ExpandParameterTemplate ( convention , key ) ;
150+ }
151+
152+ convention . Append ( ')' ) ;
153+ }
154+ }
155+
156+ return convention . ToString ( ) ;
157+ }
158+
159+ void ExpandParameterTemplate ( StringBuilder template , IEdmStructuralProperty key )
160+ {
161+ Contract . Requires ( template != null ) ;
162+ Contract . Requires ( key != null ) ;
163+
164+ var name = key . Name ;
165+ var typeDef = key . Type . Definition ;
166+
167+ template . Append ( "{" ) ;
168+ template . Append ( name ) ;
169+ template . Append ( "}" ) ;
170+
171+ if ( typeDef . TypeKind == EdmTypeKind . Enum )
172+ {
173+ template . Insert ( 0 , '\' ' ) ;
174+
175+ if ( ! Context . AllowUnqualifiedEnum )
176+ {
177+ template . Insert ( 0 , key . Type . FullName ( ) ) ;
178+ }
179+
180+ template . Append ( '\' ' ) ;
181+ return ;
182+ }
183+
184+ var type = typeDef . GetClrType ( Context . AssembliesResolver ) ;
185+
186+ if ( quotedTypes . TryGetValue ( type , out var prefix ) )
187+ {
188+ template . Insert ( 0 , '\' ' ) ;
189+ template . Insert ( 0 , prefix ) ;
190+ template . Append ( '\' ' ) ;
191+ }
192+ else if ( GeographyType . IsAssignableFrom ( type ) )
193+ {
194+ template . Insert ( 0 , "geography'" ) ;
195+ template . Append ( '\' ' ) ;
196+ }
197+ else if ( GeometryType . IsAssignableFrom ( type ) )
198+ {
199+ template . Insert ( 0 , "geometry'" ) ;
200+ template . Append ( '\' ' ) ;
201+ }
202+ }
203+
204+ IReadOnlyList < ApiParameterDescription > FilterQueryParameters ( IReadOnlyList < ApiParameterDescription > parameterDescriptions )
205+ {
206+ Contract . Requires ( parameterDescriptions != null ) ;
207+ Contract . Ensures ( Contract . Result < IReadOnlyList < ApiParameterDescription > > ( ) != null ) ;
208+
209+ var queryParameters = new List < ApiParameterDescription > ( ) ;
210+ var queryOptions = typeof ( ODataQueryOptions ) ;
211+ var actionParameters = typeof ( ODataActionParameters ) ;
212+
213+ foreach ( var parameter in parameterDescriptions )
214+ {
215+ if ( parameter . Source != FromUri )
216+ {
217+ continue ;
218+ }
219+
220+ var parameterType = parameter . ParameterDescriptor ? . ParameterType ;
221+
222+ if ( parameterType == null ||
223+ queryOptions . IsAssignableFrom ( parameterType ) ||
224+ actionParameters . IsAssignableFrom ( parameterType ) )
225+ {
226+ continue ;
227+ }
228+
229+ if ( ! Context . EntityKeys . Any ( key => key . Name . Equals ( parameter . Name , OrdinalIgnoreCase ) ) )
230+ {
231+ queryParameters . Add ( parameter ) ;
232+ }
233+ }
61234
62- return Join ( "/" , segments ) ;
235+ return queryParameters ;
63236 }
64237 }
65238}
0 commit comments