99using System . Security . Claims ;
1010using System . Text ;
1111using System . Text . Json ;
12+ using System . Text . Json . Serialization . Metadata ;
1213using System . Threading ;
1314using System . Threading . Tasks ;
1415using Microsoft . Extensions . Logging ;
@@ -166,8 +167,101 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
166167 HttpResponseMessage response = await CallApiInternalAsync ( serviceName , effectiveOptions , false ,
167168 null , user , cancellationToken ) . ConfigureAwait ( false ) ;
168169 return await DeserializeOutput < TOutput > ( response , effectiveOptions ) . ConfigureAwait ( false ) ;
169- }
170+ }
171+
172+ #if NET8_0_OR_GREATER
173+ /// <inheritdoc/>
174+ public async Task < TOutput ? > CallApiForUserAsync < TInput , TOutput > (
175+ string ? serviceName ,
176+ TInput input ,
177+ JsonTypeInfo < TInput > inputJsonTypeInfo ,
178+ JsonTypeInfo < TOutput > outputJsonTypeInfo ,
179+ Action < DownstreamApiOptions > ? downstreamApiOptionsOverride = null ,
180+ ClaimsPrincipal ? user = default ,
181+ CancellationToken cancellationToken = default )
182+ where TOutput : class
183+ {
184+ DownstreamApiOptions effectiveOptions = MergeOptions ( serviceName , downstreamApiOptionsOverride ) ;
185+ HttpContent ? effectiveInput = SerializeInput ( input , effectiveOptions , inputJsonTypeInfo ) ;
186+
187+ HttpResponseMessage response = await CallApiInternalAsync ( serviceName , effectiveOptions , false ,
188+ effectiveInput , user , cancellationToken ) . ConfigureAwait ( false ) ;
189+
190+ // Only dispose the HttpContent if was created here, not provided by the caller.
191+ if ( input is not HttpContent )
192+ {
193+ effectiveInput ? . Dispose ( ) ;
194+ }
195+
196+ return await DeserializeOutput < TOutput > ( response , effectiveOptions , outputJsonTypeInfo ) . ConfigureAwait ( false ) ;
197+ }
198+
199+ /// <inheritdoc/>
200+ public async Task < TOutput ? > CallApiForUserAsync < TOutput > (
201+ string serviceName ,
202+ JsonTypeInfo < TOutput > outputJsonTypeInfo ,
203+ Action < DownstreamApiOptions > ? downstreamApiOptionsOverride = null ,
204+ ClaimsPrincipal ? user = default ,
205+ CancellationToken cancellationToken = default )
206+ where TOutput : class
207+ {
208+ DownstreamApiOptions effectiveOptions = MergeOptions ( serviceName , downstreamApiOptionsOverride ) ;
209+ HttpResponseMessage response = await CallApiInternalAsync ( serviceName , effectiveOptions , false ,
210+ null , user , cancellationToken ) . ConfigureAwait ( false ) ;
211+ return await DeserializeOutput < TOutput > ( response , effectiveOptions , outputJsonTypeInfo ) . ConfigureAwait ( false ) ;
212+ }
213+
214+ /// <inheritdoc/>
215+ public async Task < TOutput ? > CallApiForAppAsync < TInput , TOutput > (
216+ string ? serviceName ,
217+ TInput input ,
218+ JsonTypeInfo < TInput > inputJsonTypeInfo ,
219+ JsonTypeInfo < TOutput > outputJsonTypeInfo ,
220+ Action < DownstreamApiOptions > ? downstreamApiOptionsOverride = null ,
221+ CancellationToken cancellationToken = default )
222+ where TOutput : class
223+ {
224+ DownstreamApiOptions effectiveOptions = MergeOptions ( serviceName , downstreamApiOptionsOverride ) ;
225+ HttpContent ? effectiveInput = SerializeInput ( input , effectiveOptions , inputJsonTypeInfo ) ;
226+ HttpResponseMessage response = await CallApiInternalAsync ( serviceName , effectiveOptions , true ,
227+ effectiveInput , null , cancellationToken ) . ConfigureAwait ( false ) ;
170228
229+ // Only dispose the HttpContent if was created here, not provided by the caller.
230+ if ( input is not HttpContent )
231+ {
232+ effectiveInput ? . Dispose ( ) ;
233+ }
234+
235+ return await DeserializeOutput < TOutput > ( response , effectiveOptions , outputJsonTypeInfo ) . ConfigureAwait ( false ) ;
236+ }
237+
238+ /// <inheritdoc/>
239+ public async Task < TOutput ? > CallApiForAppAsync < TOutput > (
240+ string serviceName ,
241+ JsonTypeInfo < TOutput > outputJsonTypeInfo ,
242+ Action < DownstreamApiOptions > ? downstreamApiOptionsOverride = null ,
243+ CancellationToken cancellationToken = default )
244+ where TOutput : class
245+ {
246+ DownstreamApiOptions effectiveOptions = MergeOptions ( serviceName , downstreamApiOptionsOverride ) ;
247+ HttpResponseMessage response = await CallApiInternalAsync ( serviceName , effectiveOptions , true ,
248+ null , null , cancellationToken ) . ConfigureAwait ( false ) ;
249+
250+ return await DeserializeOutput < TOutput > ( response , effectiveOptions , outputJsonTypeInfo ) . ConfigureAwait ( false ) ;
251+ }
252+
253+ internal static HttpContent ? SerializeInput < TInput > ( TInput input , DownstreamApiOptions effectiveOptions , JsonTypeInfo < TInput > inputJsonTypeInfo )
254+ {
255+ return SerializeInputImpl ( input , effectiveOptions , inputJsonTypeInfo ) ;
256+ }
257+
258+ internal static async Task < TOutput ? > DeserializeOutput < TOutput > ( HttpResponseMessage response , DownstreamApiOptions effectiveOptions , JsonTypeInfo < TOutput > outputJsonTypeInfo )
259+ where TOutput : class
260+ {
261+ return await DeserializeOutputImpl < TOutput > ( response , effectiveOptions , outputJsonTypeInfo ) ;
262+ }
263+ #endif
264+
171265 /// <summary>
172266 /// Merge the options from configuration and override from caller.
173267 /// </summary>
@@ -217,9 +311,14 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
217311 DownstreamApiOptionsReadOnlyHttpMethod clonedOptions = new DownstreamApiOptionsReadOnlyHttpMethod ( options , httpMethod . ToString ( ) ) ;
218312 calledApiOptionsOverride ? . Invoke ( clonedOptions ) ;
219313 return clonedOptions ;
314+ }
315+
316+ internal static HttpContent ? SerializeInput < TInput > ( TInput input , DownstreamApiOptions effectiveOptions )
317+ {
318+ return SerializeInputImpl ( input , effectiveOptions , null ) ;
220319 }
221320
222- internal static HttpContent ? SerializeInput < TInput > ( TInput input , DownstreamApiOptions effectiveOptions )
321+ private static HttpContent ? SerializeInputImpl < TInput > ( TInput input , DownstreamApiOptions effectiveOptions , JsonTypeInfo < TInput > ? inputJsonTypeInfo = null )
223322 {
224323 HttpContent ? httpContent ;
225324
@@ -234,17 +333,29 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
234333 {
235334 HttpContent content => content ,
236335 string str when ! string . IsNullOrEmpty ( effectiveOptions . ContentType ) && effectiveOptions . ContentType . StartsWith ( "text" , StringComparison . OrdinalIgnoreCase ) => new StringContent ( str ) ,
237- string str => new StringContent ( JsonSerializer . Serialize ( str ) , Encoding . UTF8 , "application/json" ) ,
336+ string str => new StringContent (
337+ inputJsonTypeInfo == null ? JsonSerializer . Serialize ( str ) : JsonSerializer . Serialize ( str , inputJsonTypeInfo ) ,
338+ Encoding . UTF8 ,
339+ "application/json" ) ,
238340 byte [ ] bytes => new ByteArrayContent ( bytes ) ,
239341 Stream stream => new StreamContent ( stream ) ,
240342 null => null ,
241- _ => new StringContent ( JsonSerializer . Serialize ( input ) , Encoding . UTF8 , "application/json" ) ,
343+ _ => new StringContent (
344+ inputJsonTypeInfo == null ? JsonSerializer . Serialize ( input ) : JsonSerializer . Serialize ( input , inputJsonTypeInfo ) ,
345+ Encoding . UTF8 ,
346+ "application/json" ) ,
242347 } ;
243348 }
244349 return httpContent ;
245350 }
246351
247352 internal static async Task < TOutput ? > DeserializeOutput < TOutput > ( HttpResponseMessage response , DownstreamApiOptions effectiveOptions )
353+ where TOutput : class
354+ {
355+ return await DeserializeOutputImpl < TOutput > ( response , effectiveOptions , null ) ;
356+ }
357+
358+ private static async Task < TOutput ? > DeserializeOutputImpl < TOutput > ( HttpResponseMessage response , DownstreamApiOptions effectiveOptions , JsonTypeInfo < TOutput > ? outputJsonTypeInfo = null )
248359 where TOutput : class
249360 {
250361 try
@@ -284,7 +395,14 @@ public Task<HttpResponseMessage> CallApiForAppAsync(
284395 string stringContent = await content . ReadAsStringAsync ( ) ;
285396 if ( mediaType == "application/json" )
286397 {
287- return JsonSerializer . Deserialize < TOutput > ( stringContent , new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ) ;
398+ if ( outputJsonTypeInfo != null )
399+ {
400+ return JsonSerializer . Deserialize < TOutput > ( stringContent , outputJsonTypeInfo ) ;
401+ }
402+ else
403+ {
404+ return JsonSerializer . Deserialize < TOutput > ( stringContent , new JsonSerializerOptions { PropertyNameCaseInsensitive = true } ) ;
405+ }
288406 }
289407 if ( mediaType != null && ! mediaType . StartsWith ( "text/" , StringComparison . OrdinalIgnoreCase ) )
290408 {
@@ -361,8 +479,8 @@ internal async Task UpdateRequestAsync(
361479 effectiveOptions . Scopes ,
362480 effectiveOptions ,
363481 user ,
364- cancellationToken ) . ConfigureAwait ( false ) ;
365-
482+ cancellationToken ) . ConfigureAwait ( false ) ;
483+
366484 httpRequestMessage . Headers . Add ( Authorization , authorizationHeader ) ;
367485 }
368486 else
0 commit comments