diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs index 9b36f42..d9e2e91 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Net.Http; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Logging; @@ -38,14 +39,14 @@ namespace CryptoExchange.Net.UnitTests { } - public override Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed) + public override Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization) { - return base.AddAuthenticationToHeaders(uri, method, parameters, signed); + return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization); } - public override Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed) + public override Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization) { - return base.AddAuthenticationToParameters(uri, method, parameters, signed); + return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization); } public override string Sign(string toSign) diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs index 8739fed..21857b1 100644 --- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs +++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using CryptoExchange.Net.Objects; +using System.Collections.Generic; using System.Net.Http; namespace CryptoExchange.Net.Authentication @@ -29,8 +30,11 @@ namespace CryptoExchange.Net.Authentication /// /// /// + /// + /// /// - public virtual Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed) + public virtual Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, + PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization) { return parameters; } @@ -42,8 +46,11 @@ namespace CryptoExchange.Net.Authentication /// /// /// + /// + /// /// - public virtual Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed) + public virtual Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, + PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization) { return new Dictionary(); } diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index 279d669..b061108 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -6,12 +6,12 @@ CryptoExchange.Net JKorf A base package for implementing cryptocurrency exchange API's - 3.0.8 + 3.0.9 false https://github.com/JKorf/CryptoExchange.Net en true - 3.0.8 - Added empty body content setting, added TryParseError virtual method + 3.0.9 - Added arraySerialization and postParameterPosition to AuthenticationProvider interface, fixed array serialization in request body enable 8.0 MIT diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index d674aa2..4f03bd5 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -97,7 +97,7 @@ - + Add authentication to the parameter list @@ -105,9 +105,11 @@ + + - + Add authentication to the header dictionary @@ -115,6 +117,8 @@ + + @@ -1671,41 +1675,6 @@ - - - Buffer entry for order book - - - - - List of asks - - - - - List of bids - - - - - Buffer entry with a single update id per update - - - - - First update id - - - - - List of asks - - - - - List of bids - - Buffer entry with a first and last update id @@ -2191,7 +2160,7 @@ The roundtrip time of the ping request - + Execute a request @@ -2201,7 +2170,9 @@ Cancellation token The parameters of the request Whether or not the request should be authenticated - Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) + Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) + Where the post parameters should be placed + How array paramters should be serialized @@ -2220,7 +2191,7 @@ Received data Null if not an error, Error otherwise - + Creates a request object @@ -2228,6 +2199,8 @@ The method of the request The parameters of the request Whether or not the request should be authenticated + Where the post parameters should be placed + How array paramters should be serialized diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index 1090cf9..1ac235b 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Threading; using System.Threading.Tasks; +using System.Web; using CryptoExchange.Net.Logging; using CryptoExchange.Net.Objects; using Newtonsoft.Json; @@ -79,16 +80,16 @@ namespace CryptoExchange.Net foreach (var arrayEntry in arraysParameters) { if(serializationType == ArrayParametersSerialization.Array) - uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? WebUtility.UrlEncode(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={v}"))}&"; + uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={v}"))}&"; else { var array = (Array)arrayEntry.Value; - uriString += string.Join("&", array.OfType().Select(a => $"{arrayEntry.Key}={WebUtility.UrlEncode(a.ToString())}")); + uriString += string.Join("&", array.OfType().Select(a => $"{arrayEntry.Key}={Uri.EscapeDataString(a.ToString())}")); uriString += "&"; } } - uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? WebUtility.UrlEncode(s.Value.ToString()) : s.Value)}"))}"; + uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? Uri.EscapeDataString(s.Value.ToString()) : s.Value)}"))}"; uriString = uriString.TrimEnd('&'); return uriString; } diff --git a/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs b/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs index 3cf64bc..1e7c919 100644 --- a/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs +++ b/CryptoExchange.Net/OrderBook/ProcessBufferEntry.cs @@ -3,40 +3,6 @@ using System.Collections.Generic; namespace CryptoExchange.Net.OrderBook { - /// - /// Buffer entry for order book - /// - public class ProcessBufferEntry - { - /// - /// List of asks - /// - public IEnumerable Asks { get; set; } = new List(); - /// - /// List of bids - /// - public IEnumerable Bids { get; set; } = new List(); - } - - /// - /// Buffer entry with a single update id per update - /// - public class ProcessBufferSingleSequenceEntry - { - /// - /// First update id - /// - public long UpdateId { get; set; } - /// - /// List of asks - /// - public IEnumerable Asks { get; set; } = new List(); - /// - /// List of bids - /// - public IEnumerable Bids { get; set; } = new List(); - } - /// /// Buffer entry with a first and last update id /// diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs index b8674f5..471dc45 100644 --- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs +++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs @@ -22,7 +22,6 @@ namespace CryptoExchange.Net.OrderBook /// The process buffer, used while syncing /// protected readonly List processBuffer; - private readonly object bookLock = new object(); /// /// The ask list /// @@ -30,8 +29,10 @@ namespace CryptoExchange.Net.OrderBook /// /// The bid list /// - protected SortedList bids; + + private readonly object bookLock = new object(); + private OrderBookStatus status; private UpdateSubscription? subscription; private readonly bool sequencesAreConsecutive; @@ -224,6 +225,7 @@ namespace CryptoExchange.Net.OrderBook /// public async Task> StartAsync() { + log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} starting"); Status = OrderBookStatus.Connecting; _processTask = Task.Run(ProcessQueue); @@ -279,6 +281,7 @@ namespace CryptoExchange.Net.OrderBook /// public async Task StopAsync() { + log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} stopping"); Status = OrderBookStatus.Disconnected; _queueEvent.Set(); _processTask.Wait(); diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 42043c4..d3a0f55 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -164,11 +164,13 @@ namespace CryptoExchange.Net /// Cancellation token /// The parameters of the request /// Whether or not the request should be authenticated - /// Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) + /// Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) + /// Where the post parameters should be placed + /// How array paramters should be serialized /// [return: NotNull] protected virtual async Task> SendRequest(Uri uri, HttpMethod method, CancellationToken cancellationToken, - Dictionary? parameters = null, bool signed = false, bool checkResult = true) where T : class + Dictionary? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null) where T : class { log.Write(LogVerbosity.Debug, "Creating request for " + uri); if (signed && authProvider == null) @@ -177,7 +179,7 @@ namespace CryptoExchange.Net return new WebCallResult(null, null, null, new NoApiCredentialsError()); } - var request = ConstructRequest(uri, method, parameters, signed); + var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization); foreach (var limiter in RateLimiters) { var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour); @@ -294,17 +296,19 @@ namespace CryptoExchange.Net /// The method of the request /// The parameters of the request /// Whether or not the request should be authenticated + /// Where the post parameters should be placed + /// How array paramters should be serialized /// - protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed) + protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization) { if (parameters == null) parameters = new Dictionary(); var uriString = uri.ToString(); if(authProvider != null) - parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed); + parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, postPosition, arraySerialization); - if((method == HttpMethod.Get || method == HttpMethod.Delete || postParametersPosition == PostParameters.InUri) && parameters?.Any() == true) + if((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true) uriString += "?" + parameters.CreateParamString(true, arraySerialization); var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; @@ -313,12 +317,12 @@ namespace CryptoExchange.Net var headers = new Dictionary(); if (authProvider != null) - headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed); + headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, postPosition, arraySerialization); foreach (var header in headers) request.AddHeader(header.Key, header.Value); - if ((method == HttpMethod.Post || method == HttpMethod.Put) && postParametersPosition != PostParameters.InUri) + if ((method == HttpMethod.Post || method == HttpMethod.Put) && postPosition != PostParameters.InUri) { if(parameters?.Any() == true) WriteParamBody(request, parameters, contentType); @@ -346,7 +350,16 @@ namespace CryptoExchange.Net { var formData = HttpUtility.ParseQueryString(string.Empty); foreach (var kvp in parameters.OrderBy(p => p.Key)) - formData.Add(kvp.Key, kvp.Value.ToString()); + { + if (kvp.Value.GetType().IsArray) + { + var array = (Array)kvp.Value; + foreach(var value in array) + formData.Add(kvp.Key, value.ToString()); + } + else + formData.Add(kvp.Key, kvp.Value.ToString()); + } var stringData = formData.ToString(); request.SetContent(stringData, contentType); } diff --git a/README.md b/README.md index 2726beb..c3b538e 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,10 @@ The order book will automatically reconnect when the connection is lost and resy To stop synchronizing an order book use the `Stop` method. ## Release notes +* Version 3.0.9 - 07 Jun 2020 + * Added arraySerialization and postParameterPosition to AuthenticationProvider interface + * Fixed array serialization in request body + * Version 3.0.8 - 02 Jun 2020 * Added requestBodyEmptyContent setting for rest client * Added TryParseError for rest implementations to check for error with success status code