17
17
namespace JsonApiDotNetCore . Middleware
18
18
{
19
19
/// <summary>
20
- /// This sets all necessary parameters relating to the HttpContext for JADNC
20
+ /// Intercepts HTTP requests to populate injected <see cref="ICurrentRequest"/> instance for json:api requests.
21
21
/// </summary>
22
22
public sealed class CurrentRequestMiddleware
23
23
{
24
24
private readonly RequestDelegate _next ;
25
25
private HttpContext _httpContext ;
26
+ private IJsonApiOptions _options ;
26
27
private ICurrentRequest _currentRequest ;
27
28
private IResourceGraph _resourceGraph ;
28
- private IJsonApiOptions _options ;
29
29
private RouteValueDictionary _routeValues ;
30
- private IControllerResourceMapping _controllerResourceMapping ;
31
30
32
31
public CurrentRequestMiddleware ( RequestDelegate next )
33
32
{
@@ -41,116 +40,50 @@ public async Task Invoke(HttpContext httpContext,
41
40
IResourceGraph resourceGraph )
42
41
{
43
42
_httpContext = httpContext ;
43
+ _options = options ;
44
44
_currentRequest = currentRequest ;
45
- _controllerResourceMapping = controllerResourceMapping ;
46
45
_resourceGraph = resourceGraph ;
47
- _options = options ;
48
46
_routeValues = httpContext . GetRouteData ( ) . Values ;
49
- var requestResource = GetCurrentEntity ( ) ;
50
- if ( requestResource != null )
51
- {
52
- _currentRequest . SetRequestResource ( requestResource ) ;
53
- _currentRequest . IsRelationshipPath = PathIsRelationship ( ) ;
54
- _currentRequest . BasePath = GetBasePath ( requestResource . ResourceName ) ;
55
- _currentRequest . BaseId = GetBaseId ( ) ;
56
- _currentRequest . RelationshipId = GetRelationshipId ( ) ;
57
47
58
- if ( await IsValidAsync ( ) )
48
+ var resourceContext = CreateResourceContext ( controllerResourceMapping ) ;
49
+ if ( resourceContext != null )
50
+ {
51
+ if ( ! await ValidateContentTypeHeaderAsync ( ) || ! await ValidateAcceptHeaderAsync ( ) )
59
52
{
60
- _httpContext . SetJsonApiRequest ( ) ;
61
- await _next ( httpContext ) ;
53
+ return ;
62
54
}
63
55
64
- return ;
56
+ SetupCurrentRequest ( resourceContext ) ;
57
+
58
+ _httpContext . SetJsonApiRequest ( ) ;
65
59
}
66
60
67
61
await _next ( httpContext ) ;
68
62
}
69
63
70
- private string GetBaseId ( )
71
- {
72
- if ( _routeValues . TryGetValue ( "id" , out object stringId ) )
73
- {
74
- return ( string ) stringId ;
75
- }
76
-
77
- return null ;
78
- }
79
- private string GetRelationshipId ( )
64
+ private ResourceContext CreateResourceContext ( IControllerResourceMapping controllerResourceMapping )
80
65
{
81
- if ( ! _currentRequest . IsRelationshipPath )
66
+ var controllerName = ( string ) _routeValues [ "controller" ] ;
67
+ if ( controllerName == null )
82
68
{
83
69
return null ;
84
70
}
85
- var components = SplitCurrentPath ( ) ;
86
- var toReturn = components . ElementAtOrDefault ( 4 ) ;
87
71
88
- return toReturn ;
89
- }
90
- private string [ ] SplitCurrentPath ( )
91
- {
92
- var path = _httpContext . Request . Path . Value ;
93
- var ns = $ "/{ _options . Namespace } ";
94
- var nonNameSpaced = path . Replace ( ns , "" ) ;
95
- nonNameSpaced = nonNameSpaced . Trim ( '/' ) ;
96
- var individualComponents = nonNameSpaced . Split ( '/' ) ;
97
- return individualComponents ;
72
+ var resourceType = controllerResourceMapping . GetAssociatedResource ( controllerName ) ;
73
+ var resourceContext = _resourceGraph . GetResourceContext ( resourceType ) ;
74
+ return resourceContext ;
98
75
}
99
76
100
- private string GetBasePath ( string resourceName = null )
77
+ private async Task < bool > ValidateContentTypeHeaderAsync ( )
101
78
{
102
- var r = _httpContext . Request ;
103
- if ( _options . RelativeLinks )
104
- {
105
- return _options . Namespace ;
106
- }
107
-
108
- var customRoute = GetCustomRoute ( r . Path . Value , resourceName ) ;
109
- var toReturn = $ "{ r . Scheme } ://{ r . Host } /{ _options . Namespace } ";
110
- if ( customRoute != null )
111
- {
112
- toReturn += $ "/{ customRoute } ";
113
- }
114
- return toReturn ;
115
- }
116
-
117
- private object GetCustomRoute ( string path , string resourceName )
118
- {
119
- var trimmedComponents = path . Trim ( '/' ) . Split ( '/' ) . ToList ( ) ;
120
- var resourceNameIndex = trimmedComponents . FindIndex ( c => c == resourceName ) ;
121
- var newComponents = trimmedComponents . Take ( resourceNameIndex ) . ToArray ( ) ;
122
- var customRoute = string . Join ( '/' , newComponents ) ;
123
- if ( customRoute == _options . Namespace )
124
- {
125
- return null ;
126
- }
127
- else
128
- {
129
- return customRoute ;
130
- }
131
- }
132
-
133
- private bool PathIsRelationship ( )
134
- {
135
- var actionName = ( string ) _routeValues [ "action" ] ;
136
- return actionName . ToLowerInvariant ( ) . Contains ( "relationships" ) ;
137
- }
138
-
139
- private async Task < bool > IsValidAsync ( )
140
- {
141
- return await IsValidContentTypeHeaderAsync ( _httpContext ) && await IsValidAcceptHeaderAsync ( _httpContext ) ;
142
- }
143
-
144
- private async Task < bool > IsValidContentTypeHeaderAsync ( HttpContext context )
145
- {
146
- var contentType = context . Request . ContentType ;
79
+ var contentType = _httpContext . Request . ContentType ;
147
80
if ( contentType != null )
148
81
{
149
82
if ( ! MediaTypeHeaderValue . TryParse ( contentType , out var headerValue ) ||
150
83
headerValue . MediaType != HeaderConstants . MediaType || headerValue . CharSet != null ||
151
84
headerValue . Parameters . Any ( p => p . Name != "ext" ) )
152
85
{
153
- await FlushResponseAsync ( context , new Error ( HttpStatusCode . UnsupportedMediaType )
86
+ await FlushResponseAsync ( _httpContext , new Error ( HttpStatusCode . UnsupportedMediaType )
154
87
{
155
88
Title = "The specified Content-Type header value is not supported." ,
156
89
Detail = $ "Please specify '{ HeaderConstants . MediaType } ' instead of '{ contentType } ' for the Content-Type header value."
@@ -163,9 +96,9 @@ private async Task<bool> IsValidContentTypeHeaderAsync(HttpContext context)
163
96
return true ;
164
97
}
165
98
166
- private async Task < bool > IsValidAcceptHeaderAsync ( HttpContext context )
99
+ private async Task < bool > ValidateAcceptHeaderAsync ( )
167
100
{
168
- if ( context . Request . Headers . TryGetValue ( "Accept" , out StringValues acceptHeaders ) )
101
+ if ( _httpContext . Request . Headers . TryGetValue ( "Accept" , out StringValues acceptHeaders ) )
169
102
{
170
103
foreach ( var acceptHeader in acceptHeaders )
171
104
{
@@ -179,7 +112,7 @@ private async Task<bool> IsValidAcceptHeaderAsync(HttpContext context)
179
112
}
180
113
}
181
114
182
- await FlushResponseAsync ( context , new Error ( HttpStatusCode . NotAcceptable )
115
+ await FlushResponseAsync ( _httpContext , new Error ( HttpStatusCode . NotAcceptable )
183
116
{
184
117
Title = "The specified Accept header value is not supported." ,
185
118
Detail = $ "Please include '{ HeaderConstants . MediaType } ' in the Accept header values."
@@ -213,28 +146,74 @@ private async Task FlushResponseAsync(HttpContext context, Error error)
213
146
context . Response . Body . Flush ( ) ;
214
147
}
215
148
216
- /// <summary>
217
- /// Gets the current entity that we need for serialization and deserialization.
218
- /// </summary>
219
- /// <returns></returns>
220
- private ResourceContext GetCurrentEntity ( )
149
+ private void SetupCurrentRequest ( ResourceContext resourceContext )
221
150
{
222
- var controllerName = ( string ) _routeValues [ "controller" ] ;
223
- if ( controllerName == null )
151
+ _currentRequest . SetRequestResource ( resourceContext ) ;
152
+ _currentRequest . BaseId = GetBaseId ( ) ;
153
+ _currentRequest . BasePath = GetBasePath ( resourceContext . ResourceName ) ;
154
+ _currentRequest . IsRelationshipPath = GetIsRelationshipPath ( ) ;
155
+ _currentRequest . RelationshipId = GetRelationshipId ( ) ;
156
+
157
+ if ( _routeValues . TryGetValue ( "relationshipName" , out object relationshipName ) )
224
158
{
225
- return null ;
159
+ _currentRequest . RequestRelationship = resourceContext . Relationships . SingleOrDefault ( r => r . PublicRelationshipName == ( string ) relationshipName ) ;
226
160
}
227
- var resourceType = _controllerResourceMapping . GetAssociatedResource ( controllerName ) ;
228
- var requestResource = _resourceGraph . GetResourceContext ( resourceType ) ;
229
- if ( requestResource == null )
161
+ }
162
+
163
+ private string GetBaseId ( )
164
+ {
165
+ return _routeValues . TryGetValue ( "id" , out var id ) ? ( string ) id : null ;
166
+ }
167
+
168
+ private string GetBasePath ( string resourceName )
169
+ {
170
+ if ( _options . RelativeLinks )
230
171
{
231
- return null ;
172
+ return _options . Namespace ;
232
173
}
233
- if ( _routeValues . TryGetValue ( "relationshipName" , out object relationshipName ) )
174
+
175
+ var customRoute = GetCustomRoute ( _httpContext . Request . Path . Value , resourceName ) ;
176
+ var toReturn = $ "{ _httpContext . Request . Scheme } ://{ _httpContext . Request . Host } /{ _options . Namespace } ";
177
+ if ( customRoute != null )
178
+ {
179
+ toReturn += $ "/{ customRoute } ";
180
+ }
181
+ return toReturn ;
182
+ }
183
+
184
+ private string GetCustomRoute ( string path , string resourceName )
185
+ {
186
+ var trimmedComponents = path . Trim ( '/' ) . Split ( '/' ) . ToList ( ) ;
187
+ var resourceNameIndex = trimmedComponents . FindIndex ( c => c == resourceName ) ;
188
+ var newComponents = trimmedComponents . Take ( resourceNameIndex ) . ToArray ( ) ;
189
+ var customRoute = string . Join ( '/' , newComponents ) ;
190
+ return customRoute == _options . Namespace ? null : customRoute ;
191
+ }
192
+
193
+ private bool GetIsRelationshipPath ( )
194
+ {
195
+ var actionName = ( string ) _routeValues [ "action" ] ;
196
+ return actionName . ToLowerInvariant ( ) . Contains ( "relationships" ) ;
197
+ }
198
+
199
+ private string GetRelationshipId ( )
200
+ {
201
+ if ( ! _currentRequest . IsRelationshipPath )
234
202
{
235
- _currentRequest . RequestRelationship = requestResource . Relationships . SingleOrDefault ( r => r . PublicRelationshipName == ( string ) relationshipName ) ;
203
+ return null ;
236
204
}
237
- return requestResource ;
205
+
206
+ var components = SplitCurrentPath ( ) ;
207
+ return components . ElementAtOrDefault ( 4 ) ;
208
+ }
209
+
210
+ private string [ ] SplitCurrentPath ( )
211
+ {
212
+ var path = _httpContext . Request . Path . Value ;
213
+ var ns = $ "/{ _options . Namespace } ";
214
+ var nonNameSpaced = path . Replace ( ns , "" ) ;
215
+ nonNameSpaced = nonNameSpaced . Trim ( '/' ) ;
216
+ return nonNameSpaced . Split ( '/' ) ;
238
217
}
239
218
}
240
219
}
0 commit comments