33using System . Net ;
44using System . Net . Http ;
55using System . Net . Http . Headers ;
6- using System . Reflection ;
76using System . Security ;
87using System . Text ;
98using System . Text . RegularExpressions ;
@@ -18,6 +17,8 @@ namespace EasyNetQ.Management.Client
1817{
1918 public class ManagementClient : IManagementClient
2019 {
20+ private static readonly Regex ParameterNameRegex = new Regex ( "([a-z])([A-Z])" , RegexOptions . Compiled ) ;
21+
2122 private static Task CompletedTask { get ; } = Task . FromResult < object > ( null ) ;
2223
2324 private static readonly MediaTypeWithQualityHeaderValue JsonMediaTypeHeaderValue =
@@ -94,22 +95,27 @@ public ManagementClient(
9495 throw new ArgumentException ( "hostUrl is illegal" ) ;
9596 hostUrl = hostUrl . Contains ( "http://" ) ? hostUrl : "http://" + hostUrl ;
9697 }
98+
9799 if ( ! urlRegex . IsMatch ( hostUrl ) || ! Uri . TryCreate ( hostUrl , UriKind . Absolute , out var urlUri ) )
98100 {
99101 throw new ArgumentException ( "hostUrl is illegal" ) ;
100102 }
103+
101104 if ( string . IsNullOrEmpty ( username ) )
102105 {
103106 throw new ArgumentException ( "username is null or empty" ) ;
104107 }
108+
105109 if ( password == null || password . Length == 0 )
106110 {
107111 throw new ArgumentException ( "password is null or empty" ) ;
108112 }
113+
109114 if ( configureRequest == null )
110115 {
111116 configureRequest = x => { } ;
112117 }
118+
113119 HostUrl = hostUrl ;
114120 Username = username ;
115121 PortNumber = portNumber ;
@@ -131,7 +137,11 @@ public ManagementClient(
131137 public Task < Overview > GetOverviewAsync ( GetLengthsCriteria lengthsCriteria = null ,
132138 GetRatesCriteria ratesCriteria = null , CancellationToken cancellationToken = default ( CancellationToken ) )
133139 {
134- return GetAsync < Overview > ( "overview" , cancellationToken , lengthsCriteria , ratesCriteria ) ;
140+ var queryParameters = MergeQueryParameters (
141+ lengthsCriteria ? . ToQueryParameters ( ) ,
142+ ratesCriteria ? . ToQueryParameters ( )
143+ ) ;
144+ return GetAsync < Overview > ( "overview" , queryParameters , cancellationToken ) ;
135145 }
136146
137147 public Task < IEnumerable < Node > > GetNodesAsync ( CancellationToken cancellationToken = default ( CancellationToken ) )
@@ -168,7 +178,7 @@ public Task<Channel> GetChannelAsync(string channelName, GetRatesCriteria ratesC
168178 {
169179 Ensure . ArgumentNotNull ( channelName , nameof ( channelName ) ) ;
170180
171- return GetAsync < Channel > ( $ "channels/{ channelName } ", cancellationToken , ratesCriteria ) ;
181+ return GetAsync < Channel > ( $ "channels/{ channelName } ", ratesCriteria ? . ToQueryParameters ( ) , cancellationToken ) ;
172182 }
173183
174184 public Task < IEnumerable < Exchange > > GetExchangesAsync (
@@ -183,18 +193,21 @@ public Task<Exchange> GetExchangeAsync(string exchangeName, Vhost vhost, GetRate
183193 Ensure . ArgumentNotNull ( exchangeName , nameof ( exchangeName ) ) ;
184194 Ensure . ArgumentNotNull ( vhost , nameof ( vhost ) ) ;
185195
186- return GetAsync < Exchange > ( $ "exchanges/{ SanitiseVhostName ( vhost . Name ) } /{ exchangeName } ", cancellationToken ,
187- ratesCriteria ) ;
196+ return GetAsync < Exchange > ( $ "exchanges/{ SanitiseVhostName ( vhost . Name ) } /{ exchangeName } ", ratesCriteria ? . ToQueryParameters ( ) , cancellationToken ) ;
188197 }
189198
190199 public Task < Queue > GetQueueAsync ( string queueName , Vhost vhost , GetLengthsCriteria lengthsCriteria = null ,
191200 GetRatesCriteria ratesCriteria = null , CancellationToken cancellationToken = default ( CancellationToken ) )
192201 {
193202 Ensure . ArgumentNotNull ( queueName , nameof ( queueName ) ) ;
194203 Ensure . ArgumentNotNull ( vhost , nameof ( vhost ) ) ;
195-
204+
205+ var queryParameters = MergeQueryParameters (
206+ lengthsCriteria ? . ToQueryParameters ( ) ,
207+ ratesCriteria ? . ToQueryParameters ( )
208+ ) ;
196209 return GetAsync < Queue > ( $ "queues/{ SanitiseVhostName ( vhost . Name ) } /{ SanitiseName ( queueName ) } ",
197- cancellationToken , lengthsCriteria , ratesCriteria ) ;
210+ queryParameters , cancellationToken ) ;
198211 }
199212
200213 public async Task < Exchange > CreateExchangeAsync ( ExchangeInfo exchangeInfo , Vhost vhost ,
@@ -363,12 +376,12 @@ public Task DeleteBindingAsync(Binding binding,
363376 {
364377 throw new ArgumentException ( "Empty binding source isn't supported." ) ;
365378 }
366-
379+
367380 if ( string . IsNullOrEmpty ( binding . Destination ) )
368381 {
369382 throw new ArgumentException ( "Empty binding destination isn't supported." ) ;
370383 }
371-
384+
372385 if ( string . IsNullOrEmpty ( binding . DestinationType ) )
373386 {
374387 throw new ArgumentException ( "Empty binding destination type isn't supported." ) ;
@@ -452,10 +465,12 @@ public Task<IEnumerable<Policy>> GetPoliciesAsync(
452465 {
453466 throw new ArgumentException ( "Policy name is empty" ) ;
454467 }
468+
455469 if ( string . IsNullOrEmpty ( policy . Vhost ) )
456470 {
457471 throw new ArgumentException ( "vhost name is empty" ) ;
458472 }
473+
459474 if ( policy . Definition == null )
460475 {
461476 throw new ArgumentException ( "Definition should not be null" ) ;
@@ -487,14 +502,14 @@ public Task CreateParameterAsync(Parameter parameter,
487502 cancellationToken ) ;
488503 }
489504
490- public async Task DeleteParameterAsync ( string componentName , string vhost , string name ,
505+ public Task DeleteParameterAsync ( string componentName , string vhost , string name ,
491506 CancellationToken cancellationToken = default ( CancellationToken ) )
492507 {
493508 Ensure . ArgumentNotNull ( componentName , nameof ( componentName ) ) ;
494509 Ensure . ArgumentNotNull ( vhost , nameof ( vhost ) ) ;
495510 Ensure . ArgumentNotNull ( name , nameof ( name ) ) ;
496511
497- await DeleteAsync ( GetParameterUrl ( componentName , vhost , name ) , cancellationToken ) . ConfigureAwait ( false ) ;
512+ return DeleteAsync ( GetParameterUrl ( componentName , vhost , name ) , cancellationToken ) ;
498513 }
499514
500515 public async Task < User > CreateUserAsync ( UserInfo userInfo ,
@@ -574,6 +589,7 @@ public async Task<User> ChangeUserPasswordAsync(string userName, string newPassw
574589 {
575590 userInfo . AddTag ( tag . Trim ( ) ) ;
576591 }
592+
577593 return await CreateUserAsync ( userInfo , cancellationToken ) . ConfigureAwait ( false ) ;
578594 }
579595
@@ -588,35 +604,40 @@ public async Task<bool> IsAliveAsync(Vhost vhost,
588604 {
589605 Ensure . ArgumentNotNull ( vhost , nameof ( vhost ) ) ;
590606
591- var result =
592- await GetAsync < AlivenessTestResult > ( $ "aliveness-test/{ SanitiseVhostName ( vhost . Name ) } ",
593- cancellationToken ) . ConfigureAwait ( false ) ;
607+ var result = await GetAsync < AlivenessTestResult > ( $ "aliveness-test/{ SanitiseVhostName ( vhost . Name ) } ",
608+ cancellationToken ) . ConfigureAwait ( false ) ;
594609 return result . Status == "ok" ;
595610 }
596611
597612 private Task < T > GetAsync < T > (
598613 string path ,
599- CancellationToken cancellationToken = default ( CancellationToken ) ,
600- params object [ ] queryObjects )
614+ CancellationToken cancellationToken = default ( CancellationToken ) )
601615 {
602- var request = CreateRequestForPath ( path , HttpMethod . Get , queryObjects ) ;
616+ return GetAsync < T > ( path , null , cancellationToken ) ;
617+ }
618+
603619
620+ private Task < T > GetAsync < T > (
621+ string path ,
622+ IReadOnlyDictionary < string , string > queryParameters ,
623+ CancellationToken cancellationToken = default ( CancellationToken ) )
624+ {
625+ var request = CreateRequestForPath ( HttpMethod . Get , path , BuildQueryString ( queryParameters ) ) ;
604626 return httpClient . SendAsync ( request , cancellationToken )
605627 . ContinueWithOrThrow ( _ => AnalyseResponse < T > ( code => code == HttpStatusCode . OK , _ . Result ) , cancellationToken )
606- . ContinueWith ( __ =>
607- {
608- request ? . Dispose ( ) ;
609- return __ ;
610- } , cancellationToken ) . Unwrap ( ) ;
611-
628+ . ContinueWith ( __ =>
629+ {
630+ request ? . Dispose ( ) ;
631+ return __ ;
632+ } , cancellationToken ) . Unwrap ( ) ;
612633 }
613634
614635 private Task < TResult > PostAsync < TItem , TResult > (
615636 string path ,
616637 TItem item ,
617638 CancellationToken cancellationToken = default ( CancellationToken ) )
618639 {
619- var request = CreateRequestForPath ( path , HttpMethod . Post ) ;
640+ var request = CreateRequestForPath ( HttpMethod . Post , path , string . Empty ) ;
620641
621642 InsertRequestBody ( request , item ) ;
622643
@@ -633,35 +654,33 @@ bool Success(HttpStatusCode statusCode) =>
633654 request ? . Dispose ( ) ;
634655 return __ ;
635656 } , cancellationToken ) ;
636-
637- } , cancellationToken ) . Unwrap ( ) ;
657+ } , cancellationToken ) . Unwrap ( ) ;
638658 }
639659
640660 private Task DeleteAsync (
641661 string path ,
642662 CancellationToken cancellationToken = default ( CancellationToken ) )
643663 {
644- var request = CreateRequestForPath ( path , HttpMethod . Delete ) ;
645-
664+ var request = CreateRequestForPath ( HttpMethod . Delete , path , string . Empty ) ;
665+
646666 return httpClient . SendAsync ( request , cancellationToken )
647667 . ContinueWithOrThrow ( _ =>
648- AnalyseResponse ( statusCode => statusCode == HttpStatusCode . NoContent , _ . Result )
649- . ContinueWith ( __ =>
650- {
651- request ? . Dispose ( ) ;
652- return __ ;
653- } , cancellationToken ) . Unwrap ( )
668+ AnalyseResponse ( statusCode => statusCode == HttpStatusCode . NoContent , _ . Result )
669+ . ContinueWith ( __ =>
670+ {
671+ request ? . Dispose ( ) ;
672+ return __ ;
673+ } , cancellationToken ) . Unwrap ( )
654674 , cancellationToken ) ;
655-
656675 }
657676
658677 private Task PutAsync < T > (
659678 string path ,
660679 T item = default ( T ) ,
661680 CancellationToken cancellationToken = default ( CancellationToken ) ) where T : class
662681 {
663- var request = CreateRequestForPath ( path , HttpMethod . Put ) ;
664-
682+ var request = CreateRequestForPath ( HttpMethod . Put , path , string . Empty ) ;
683+
665684 if ( item != default ( T ) )
666685 InsertRequestBody ( request , item ) ;
667686
@@ -673,16 +692,15 @@ bool ResponseSucceeded(HttpStatusCode statusCode) => statusCode == HttpStatusCod
673692 statusCode == HttpStatusCode . NoContent ;
674693
675694 return AnalyseResponse ( ResponseSucceeded , _ . Result )
676- . ContinueWith ( __ =>
677- {
678- request ? . Dispose ( ) ;
679- return __ ;
680- } , cancellationToken ) . Unwrap ( ) ;
695+ . ContinueWith ( __ =>
696+ {
697+ request ? . Dispose ( ) ;
698+ return __ ;
699+ } , cancellationToken ) . Unwrap ( ) ;
681700 } , cancellationToken ) ;
682-
683701 }
684702
685- private Task AnalyseResponse (
703+ private static Task AnalyseResponse (
686704 Func < HttpStatusCode , bool > success ,
687705 HttpResponseMessage responseMessage )
688706 {
@@ -699,15 +717,15 @@ private Task AnalyseResponse(
699717 }
700718 }
701719
702- private static Task < TResult > AnalyseResponse < TResult > (
720+ private static Task < T > AnalyseResponse < T > (
703721 Func < HttpStatusCode , bool > success ,
704722 HttpResponseMessage responseMessage )
705723 {
706724 var httpStatusCode = responseMessage . StatusCode ;
707725 try
708726 {
709727 if ( success ( httpStatusCode ) )
710- return DeserializeResponseAsync < TResult > ( responseMessage ) ;
728+ return DeserializeResponseAsync < T > ( responseMessage ) ;
711729 throw new UnexpectedHttpStatusCodeException ( httpStatusCode ) ;
712730 }
713731 finally
@@ -744,44 +762,31 @@ private static Task<T> DeserializeResponseAsync<T>(HttpResponseMessage response)
744762 . ContinueWith ( _ => JsonConvert . DeserializeObject < T > ( _ . Result , Settings ) ) ;
745763 }
746764
747- private HttpRequestMessage CreateRequestForPath ( string path , HttpMethod httpMethod ,
748- IReadOnlyCollection < object > queryObjects = null )
765+ private HttpRequestMessage CreateRequestForPath ( HttpMethod httpMethod , string path , string query )
749766 {
750- var queryString = BuildQueryString ( queryObjects ) ;
751-
752- var uri = new Uri ( $ "{ HostUrl } :{ PortNumber } /api/{ path } { queryString } ") ;
767+ var uri = new Uri ( $ "{ HostUrl } :{ PortNumber } /api/{ path } { query ?? string . Empty } ") ;
753768 var request = new HttpRequestMessage ( httpMethod , uri ) ;
754-
755769 configureRequest ( request ) ;
756-
757770 return request ;
758771 }
759772
760- // Very simple query-string builder.
761- private static string BuildQueryString ( IReadOnlyCollection < object > queryObjects )
773+ private static string BuildQueryString ( IReadOnlyDictionary < string , string > queryParameters )
762774 {
763- if ( queryObjects == null || queryObjects . Count == 0 )
775+ if ( queryParameters == null || queryParameters . Count == 0 )
764776 return string . Empty ;
765777
766778 var queryStringBuilder = new StringBuilder ( "?" ) ;
767779 var first = true ;
768- // One or more query objects can be used to build the query
769- foreach ( var query in queryObjects )
780+ foreach ( var parameter in queryParameters )
770781 {
771- if ( query == null )
772- continue ;
773- // All public properties are added to the query on the format property_name=value
774- var type = query . GetType ( ) ;
775- foreach ( var prop in type . GetProperties ( ) )
776- {
777- var name = Regex . Replace ( prop . Name , "([a-z])([A-Z])" , "$1_$2" ) . ToLower ( ) ;
778- var value = prop . GetValue ( query , null ) ;
779- if ( ! first )
780- queryStringBuilder . Append ( "&" ) ;
781- queryStringBuilder . AppendFormat ( "{0}={1}" , name , value ?? string . Empty ) ;
782- first = false ;
783- }
782+ if ( ! first )
783+ queryStringBuilder . Append ( "&" ) ;
784+ var name = ParameterNameRegex . Replace ( parameter . Key , "$1_$2" ) . ToLower ( ) ;
785+ var value = parameter . Value ?? "" ;
786+ queryStringBuilder . AppendFormat ( "{0}={1}" , name , value ) ;
787+ first = false ;
784788 }
789+
785790 return queryStringBuilder . ToString ( ) ;
786791 }
787792
@@ -805,9 +810,27 @@ private static string RecodeBindingPropertiesKey(string propertiesKey)
805810 return propertiesKey . Replace ( "%5F" , "%255F" ) ;
806811 }
807812
813+ private static IReadOnlyDictionary < string , string > MergeQueryParameters ( params IReadOnlyDictionary < string , string > [ ] multipleQueryParameters )
814+ {
815+ if ( multipleQueryParameters == null || multipleQueryParameters . Length == 0 )
816+ return null ;
817+
818+ var mergedQueryParameters = new Dictionary < string , string > ( ) ;
819+ foreach ( var queryParameters in multipleQueryParameters )
820+ {
821+ if ( queryParameters == null )
822+ continue ;
823+
824+ foreach ( var kvp in queryParameters )
825+ mergedQueryParameters [ kvp . Key ] = kvp . Value ;
826+ }
827+
828+ return mergedQueryParameters ;
829+ }
830+
808831 public void Dispose ( )
809832 {
810- httpClient ? . Dispose ( ) ;
833+ httpClient . Dispose ( ) ;
811834 }
812835 }
813- }
836+ }
0 commit comments