@@ -200,6 +200,29 @@ protected virtual HttpResponseMessage ExecuteGet(string request, params string[]
200200 return Execute ( req ) ;
201201 }
202202
203+ /// <summary>
204+ /// Execute a GET request asynchronously with an array of parameters.
205+ /// </summary>
206+ protected Task < HttpResponseMessage > ExecuteGetAsync ( string action , string [ ] parameters , CancellationToken cancellationToken )
207+ {
208+ var url = BuildUrl ( action , parameters ) ;
209+ return Client . GetAsync ( url , cancellationToken ) ;
210+ }
211+
212+ /// <summary>
213+ /// Execute a GET request asynchronously with up to 3 name/value parameters.
214+ /// </summary>
215+ protected Task < HttpResponseMessage > ExecuteGetAsync (
216+ string action ,
217+ string param1 , string value1 ,
218+ string param2 = null , string value2 = null ,
219+ string param3 = null , string value3 = null ,
220+ CancellationToken cancellationToken = default )
221+ {
222+ var url = BuildUrl ( action , param1 , value1 , param2 , value2 , param3 , value3 ) ;
223+ return Client . GetAsync ( url , cancellationToken ) ;
224+ }
225+
203226 private HttpResponseMessage Execute ( HttpRequestMessage request )
204227 {
205228 //.NET Note: Bridging from Async to Sync, this is not ideal and we could consider changing the interface to be Async or provide Async overloads
@@ -217,6 +240,31 @@ private string QueryString(string request, params string[] parameters)
217240 . Join ( "&" , parameters . Select ( WebUtility . UrlEncode ) . InPairs ( ( key , val ) => string . Format ( "{0}={1}" , key , val ) ) ) ) ;
218241 }
219242
243+ // Add this property so subclasses can access the HttpClient instance
244+ protected HttpClient Client => httpc ;
245+
246+ // BuildUrl helpers (mirror the QueryString overloads)
247+ protected virtual string BuildUrl ( string action , string [ ] parameters )
248+ {
249+ // QueryString has signature: QueryString(string request, params string[] parameters)
250+ return QueryString ( action , parameters ) ;
251+ }
252+
253+ protected virtual string BuildUrl (
254+ string action ,
255+ string param1 , string value1 ,
256+ string param2 = null , string value2 = null ,
257+ string param3 = null , string value3 = null )
258+ {
259+ // Forward to QueryString which accepts params string[]
260+ if ( param2 == null && param3 == null )
261+ {
262+ return QueryString ( action , param1 , value1 ) ;
263+ }
264+ return QueryString ( action , param1 , value1 , param2 , value2 , param3 , value3 ) ;
265+ }
266+
267+
220268 /// <summary>
221269 /// Internal utility: input stream of the provided response.
222270 /// </summary>
@@ -262,6 +310,37 @@ public virtual Stream GetResponseStream(HttpResponseMessage response, bool consu
262310 return result ;
263311 }
264312
313+ /// <summary>
314+ /// Internal utility: input stream of the provided response asynchronously.
315+ /// </summary>
316+ /// <exception cref="IOException"></exception>
317+ public virtual async Task < Stream > GetResponseStreamAsync ( HttpResponseMessage response , CancellationToken cancellationToken = default )
318+ {
319+ #if NET8_0_OR_GREATER
320+ Stream result = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
321+ #else
322+ Stream result = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
323+ #endif
324+ return result ;
325+ }
326+
327+ /// <summary>
328+ /// Internal utility: input stream of the provided response asynchronously, which optionally
329+ /// consumes the response's resources when the input stream is exhausted.
330+ /// </summary>
331+ /// <exception cref="IOException"></exception>
332+ public virtual async Task < Stream > GetResponseStreamAsync ( HttpResponseMessage response , bool consume , CancellationToken cancellationToken = default )
333+ {
334+ #if NET8_0_OR_GREATER
335+ Stream result = await response . Content . ReadAsStreamAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
336+ #else
337+ Stream result = await response . Content . ReadAsStreamAsync ( ) . ConfigureAwait ( false ) ;
338+ #endif
339+ if ( consume )
340+ result = new ConsumingStream ( result ) ;
341+ return result ;
342+ }
343+
265344 /// <summary>
266345 /// Returns <c>true</c> if this instance was <see cref="Dispose(bool)"/>ed, otherwise
267346 /// returns <c>false</c>. Note that if you override <see cref="Dispose(bool)"/>, you must call
@@ -319,6 +398,59 @@ protected virtual T DoAction<T>(HttpResponseMessage response, bool consume, Func
319398 return default ; // silly, if we're here, IOUtils.reThrow always throws an exception
320399 }
321400
401+ /// <summary>
402+ /// Do a specific async action and validate after the action that the status is still OK,
403+ /// and if not, attempt to extract the actual server side exception. Optionally
404+ /// release the response at exit, depending on <paramref name="consume"/> parameter.
405+ /// </summary>
406+ protected virtual async Task < T > DoActionAsync < T > ( HttpResponseMessage response , bool consume , Func < Task < T > > call )
407+ {
408+ Exception th = null ;
409+ try
410+ {
411+ VerifyStatus ( response ) ;
412+ return await call ( ) . ConfigureAwait ( false ) ;
413+ }
414+ catch ( Exception t ) when ( t . IsThrowable ( ) )
415+ {
416+ th = t ;
417+ }
418+ finally
419+ {
420+ try
421+ {
422+ VerifyStatus ( response ) ;
423+ }
424+ finally
425+ {
426+ if ( consume )
427+ {
428+ try
429+ {
430+ ConsumeQuietly ( response ) ;
431+ }
432+ catch
433+ {
434+ // ignore on purpose
435+ }
436+ }
437+ }
438+ }
439+
440+ if ( Debugging . AssertsEnabled ) Debugging . Assert ( th != null ) ;
441+ Util . IOUtils . ReThrow ( th ) ;
442+ return default ! ; // never reached, rethrow above always throws
443+ }
444+
445+ /// <summary>
446+ /// Calls the overload <see cref="DoActionAsync{T}(HttpResponseMessage, bool, Func{Task{T}})"/> passing <c>true</c> to consume.
447+ /// </summary>
448+ protected virtual Task < T > DoActionAsync < T > ( HttpResponseMessage response , Func < Task < T > > call )
449+ {
450+ return DoActionAsync ( response , true , call ) ;
451+ }
452+
453+
322454 /// <summary>
323455 /// Disposes this <see cref="HttpClientBase"/>.
324456 /// When called with <code>true</code>, this disposes the underlying <see cref="HttpClient"/>.
0 commit comments