1
1
namespace Microsoft . Web . Http . Description
2
2
{
3
+ using Microsoft . OData . Edm ;
3
4
using System ;
4
5
using System . Collections . Generic ;
5
6
using System . Diagnostics . Contracts ;
6
7
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 ;
9
12
using System . Web . OData . Routing ;
10
13
using static System . Linq . Enumerable ;
11
14
using static System . String ;
15
+ using static System . StringComparison ;
16
+ using static System . Web . Http . Description . ApiParameterSource ;
12
17
13
18
sealed class ODataRouteBuilder
14
19
{
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 > ( )
20
23
{
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
+ } ;
24
28
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 ;
28
33
}
29
34
30
35
internal string Build ( )
31
36
{
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 ) ;
36
50
37
51
var segments = new List < string > ( ) ;
38
- var prefix = odataRoute . RoutePrefix ? . Trim ( '/' ) ;
52
+ var prefix = Context . Route . RoutePrefix ? . Trim ( '/' ) ;
39
53
40
54
if ( ! IsNullOrEmpty ( prefix ) )
41
55
{
42
56
segments . Add ( prefix ) ;
43
57
}
44
58
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 ( ) ;
47
77
48
- if ( IsNullOrEmpty ( path ) )
78
+ using ( var iterator = queryParameters . GetEnumerator ( ) )
49
79
{
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
+ }
51
97
}
52
98
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 ;
54
116
55
117
if ( ! IsNullOrEmpty ( template ) )
56
118
{
57
- path += template ;
119
+ return template ;
58
120
}
59
121
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
+ }
61
234
62
- return Join ( "/" , segments ) ;
235
+ return queryParameters ;
63
236
}
64
237
}
65
238
}
0 commit comments