@@ -20,8 +20,20 @@ namespace JsonApiDotNetCore.Middleware;
20
20
[ PublicAPI ]
21
21
public sealed class JsonApiMiddleware
22
22
{
23
- private static readonly MediaTypeHeaderValue MediaType = MediaTypeHeaderValue . Parse ( HeaderConstants . MediaType ) ;
24
- private static readonly MediaTypeHeaderValue AtomicOperationsMediaType = MediaTypeHeaderValue . Parse ( HeaderConstants . AtomicOperationsMediaType ) ;
23
+ private static readonly string [ ] NonOperationsContentTypes = [ HeaderConstants . MediaType ] ;
24
+ private static readonly MediaTypeHeaderValue [ ] NonOperationsMediaTypes = [ MediaTypeHeaderValue . Parse ( HeaderConstants . MediaType ) ] ;
25
+
26
+ private static readonly string [ ] OperationsContentTypes =
27
+ [
28
+ HeaderConstants . AtomicOperationsMediaType ,
29
+ HeaderConstants . RelaxedAtomicOperationsMediaType
30
+ ] ;
31
+
32
+ private static readonly MediaTypeHeaderValue [ ] OperationsMediaTypes =
33
+ [
34
+ MediaTypeHeaderValue . Parse ( HeaderConstants . AtomicOperationsMediaType ) ,
35
+ MediaTypeHeaderValue . Parse ( HeaderConstants . RelaxedAtomicOperationsMediaType )
36
+ ] ;
25
37
26
38
private readonly RequestDelegate ? _next ;
27
39
@@ -56,8 +68,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
56
68
57
69
if ( primaryResourceType != null )
58
70
{
59
- if ( ! await ValidateContentTypeHeaderAsync ( HeaderConstants . MediaType , httpContext , options . SerializerWriteOptions ) ||
60
- ! await ValidateAcceptHeaderAsync ( MediaType , httpContext , options . SerializerWriteOptions ) )
71
+ if ( ! await ValidateContentTypeHeaderAsync ( NonOperationsContentTypes , httpContext , options . SerializerWriteOptions ) ||
72
+ ! await ValidateAcceptHeaderAsync ( NonOperationsMediaTypes , httpContext , options . SerializerWriteOptions ) )
61
73
{
62
74
return ;
63
75
}
@@ -68,8 +80,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
68
80
}
69
81
else if ( IsRouteForOperations ( routeValues ) )
70
82
{
71
- if ( ! await ValidateContentTypeHeaderAsync ( HeaderConstants . AtomicOperationsMediaType , httpContext , options . SerializerWriteOptions ) ||
72
- ! await ValidateAcceptHeaderAsync ( AtomicOperationsMediaType , httpContext , options . SerializerWriteOptions ) )
83
+ if ( ! await ValidateContentTypeHeaderAsync ( OperationsContentTypes , httpContext , options . SerializerWriteOptions ) ||
84
+ ! await ValidateAcceptHeaderAsync ( OperationsMediaTypes , httpContext , options . SerializerWriteOptions ) )
73
85
{
74
86
return ;
75
87
}
@@ -126,16 +138,19 @@ private async Task<bool> ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso
126
138
: null ;
127
139
}
128
140
129
- private static async Task < bool > ValidateContentTypeHeaderAsync ( string allowedContentType , HttpContext httpContext , JsonSerializerOptions serializerOptions )
141
+ private static async Task < bool > ValidateContentTypeHeaderAsync ( ICollection < string > allowedContentTypes , HttpContext httpContext ,
142
+ JsonSerializerOptions serializerOptions )
130
143
{
131
144
string ? contentType = httpContext . Request . ContentType ;
132
145
133
- if ( contentType != null && contentType != allowedContentType )
146
+ if ( contentType != null && ! allowedContentTypes . Contains ( contentType , StringComparer . OrdinalIgnoreCase ) )
134
147
{
148
+ string allowedValues = string . Join ( " or " , allowedContentTypes . Select ( value => $ "'{ value } '") ) ;
149
+
135
150
await FlushResponseAsync ( httpContext . Response , serializerOptions , new ErrorObject ( HttpStatusCode . UnsupportedMediaType )
136
151
{
137
152
Title = "The specified Content-Type header value is not supported." ,
138
- Detail = $ "Please specify ' { allowedContentType } ' instead of '{ contentType } ' for the Content-Type header value.",
153
+ Detail = $ "Please specify { allowedValues } instead of '{ contentType } ' for the Content-Type header value.",
139
154
Source = new ErrorSource
140
155
{
141
156
Header = "Content-Type"
@@ -148,7 +163,7 @@ private static async Task<bool> ValidateContentTypeHeaderAsync(string allowedCon
148
163
return true ;
149
164
}
150
165
151
- private static async Task < bool > ValidateAcceptHeaderAsync ( MediaTypeHeaderValue allowedMediaTypeValue , HttpContext httpContext ,
166
+ private static async Task < bool > ValidateAcceptHeaderAsync ( ICollection < MediaTypeHeaderValue > allowedMediaTypes , HttpContext httpContext ,
152
167
JsonSerializerOptions serializerOptions )
153
168
{
154
169
string [ ] acceptHeaders = httpContext . Request . Headers . GetCommaSeparatedValues ( "Accept" ) ;
@@ -164,15 +179,15 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
164
179
{
165
180
if ( MediaTypeHeaderValue . TryParse ( acceptHeader , out MediaTypeHeaderValue ? headerValue ) )
166
181
{
167
- headerValue . Quality = null ;
168
-
169
182
if ( headerValue . MediaType == "*/*" || headerValue . MediaType == "application/*" )
170
183
{
171
184
seenCompatibleMediaType = true ;
172
185
break ;
173
186
}
174
187
175
- if ( allowedMediaTypeValue . Equals ( headerValue ) )
188
+ headerValue . Quality = null ;
189
+
190
+ if ( allowedMediaTypes . Contains ( headerValue ) )
176
191
{
177
192
seenCompatibleMediaType = true ;
178
193
break ;
@@ -182,10 +197,12 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
182
197
183
198
if ( ! seenCompatibleMediaType )
184
199
{
200
+ string allowedValues = string . Join ( " or " , allowedMediaTypes . Select ( value => $ "'{ value } '") ) ;
201
+
185
202
await FlushResponseAsync ( httpContext . Response , serializerOptions , new ErrorObject ( HttpStatusCode . NotAcceptable )
186
203
{
187
204
Title = "The specified Accept header value does not contain any supported media types." ,
188
- Detail = $ "Please include ' { allowedMediaTypeValue } ' in the Accept header values.",
205
+ Detail = $ "Please include { allowedValues } in the Accept header values.",
189
206
Source = new ErrorSource
190
207
{
191
208
Header = "Accept"
0 commit comments