@@ -20,8 +20,20 @@ namespace JsonApiDotNetCore.Middleware;
2020[ PublicAPI ]
2121public sealed class JsonApiMiddleware
2222{
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+ ] ;
2537
2638 private readonly RequestDelegate ? _next ;
2739
@@ -56,8 +68,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
5668
5769 if ( primaryResourceType != null )
5870 {
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 ) )
6173 {
6274 return ;
6375 }
@@ -68,8 +80,8 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin
6880 }
6981 else if ( IsRouteForOperations ( routeValues ) )
7082 {
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 ) )
7385 {
7486 return ;
7587 }
@@ -126,16 +138,19 @@ private async Task<bool> ValidateIfMatchHeaderAsync(HttpContext httpContext, Jso
126138 : null ;
127139 }
128140
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 )
130143 {
131144 string ? contentType = httpContext . Request . ContentType ;
132145
133- if ( contentType != null && contentType != allowedContentType )
146+ if ( contentType != null && ! allowedContentTypes . Contains ( contentType , StringComparer . OrdinalIgnoreCase ) )
134147 {
148+ string allowedValues = string . Join ( " or " , allowedContentTypes . Select ( value => $ "'{ value } '") ) ;
149+
135150 await FlushResponseAsync ( httpContext . Response , serializerOptions , new ErrorObject ( HttpStatusCode . UnsupportedMediaType )
136151 {
137152 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.",
139154 Source = new ErrorSource
140155 {
141156 Header = "Content-Type"
@@ -148,7 +163,7 @@ private static async Task<bool> ValidateContentTypeHeaderAsync(string allowedCon
148163 return true ;
149164 }
150165
151- private static async Task < bool > ValidateAcceptHeaderAsync ( MediaTypeHeaderValue allowedMediaTypeValue , HttpContext httpContext ,
166+ private static async Task < bool > ValidateAcceptHeaderAsync ( ICollection < MediaTypeHeaderValue > allowedMediaTypes , HttpContext httpContext ,
152167 JsonSerializerOptions serializerOptions )
153168 {
154169 string [ ] acceptHeaders = httpContext . Request . Headers . GetCommaSeparatedValues ( "Accept" ) ;
@@ -164,15 +179,15 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
164179 {
165180 if ( MediaTypeHeaderValue . TryParse ( acceptHeader , out MediaTypeHeaderValue ? headerValue ) )
166181 {
167- headerValue . Quality = null ;
168-
169182 if ( headerValue . MediaType == "*/*" || headerValue . MediaType == "application/*" )
170183 {
171184 seenCompatibleMediaType = true ;
172185 break ;
173186 }
174187
175- if ( allowedMediaTypeValue . Equals ( headerValue ) )
188+ headerValue . Quality = null ;
189+
190+ if ( allowedMediaTypes . Contains ( headerValue ) )
176191 {
177192 seenCompatibleMediaType = true ;
178193 break ;
@@ -182,10 +197,12 @@ private static async Task<bool> ValidateAcceptHeaderAsync(MediaTypeHeaderValue a
182197
183198 if ( ! seenCompatibleMediaType )
184199 {
200+ string allowedValues = string . Join ( " or " , allowedMediaTypes . Select ( value => $ "'{ value } '") ) ;
201+
185202 await FlushResponseAsync ( httpContext . Response , serializerOptions , new ErrorObject ( HttpStatusCode . NotAcceptable )
186203 {
187204 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.",
189206 Source = new ErrorSource
190207 {
191208 Header = "Accept"
0 commit comments