diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs index 0fc9953..f725c61 100644 --- a/CryptoExchange.Net/BaseClient.cs +++ b/CryptoExchange.Net/BaseClient.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; namespace CryptoExchange.Net { @@ -21,13 +20,14 @@ namespace CryptoExchange.Net protected static int lastId; protected static object idLock = new object(); - public static int LastId { get => lastId; } private static readonly JsonSerializer defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Utc }); + public static int LastId { get => lastId; } + public BaseClient(ExchangeOptions options, AuthenticationProvider authenticationProvider) { log = new Log(); @@ -60,12 +60,27 @@ namespace CryptoExchange.Net authProvider = authentictationProvider; } + /// + /// Deserialize a string into an object + /// + /// The type to deserialize into + /// The data to deserialize + /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) + /// A specific serializer to use + /// protected CallResult Deserialize(string data, bool checkObject = true, JsonSerializer serializer = null) { - var obj = JToken.Parse(data); - return Deserialize(obj, checkObject, serializer); + return Deserialize(JToken.Parse(data), checkObject, serializer); } + /// + /// Deserialize a JToken into an object + /// + /// The type to deserialize into + /// The data to deserialize + /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) + /// A specific serializer to use + /// protected CallResult Deserialize(JToken obj, bool checkObject = true, JsonSerializer serializer = null) { if (serializer == null) @@ -218,6 +233,10 @@ namespace CryptoExchange.Net || type == typeof(decimal); } + /// + /// Generate a unique id + /// + /// protected int NextId() { lock (idLock) @@ -227,18 +246,24 @@ namespace CryptoExchange.Net } } - protected static string FillPathParameter(string endpoint, params string[] values) + /// + /// Fill parameters in a path. Parameters are specified by '{}' and should be specified in occuring sequence + /// + /// The total path string + /// The values to fill + /// + protected static string FillPathParameter(string path, params string[] values) { foreach (var value in values) { - int index = endpoint.IndexOf("{}", StringComparison.Ordinal); + int index = path.IndexOf("{}", StringComparison.Ordinal); if (index >= 0) { - endpoint = endpoint.Remove(index, 2); - endpoint = endpoint.Insert(index, value); + path = path.Remove(index, 2); + path = path.Insert(index, value); } } - return endpoint; + return path; } public virtual void Dispose() diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index b66bd70..3b407f3 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -31,6 +31,12 @@ namespace CryptoExchange.Net parameters.Add(key, value); } + /// + /// Create a query string of the specified parameters + /// + /// The parameters to use + /// Whether or not the values should be url encoded + /// public static string CreateParamString(this Dictionary parameters, bool urlEncodeValues) { var uriString = ""; @@ -45,6 +51,11 @@ namespace CryptoExchange.Net return uriString; } + /// + /// Get the string the secure string is representing + /// + /// The source secure string + /// public static string GetString(this SecureString source) { lock (source) diff --git a/CryptoExchange.Net/Objects/Error.cs b/CryptoExchange.Net/Objects/Error.cs index 7a59bb1..f692762 100644 --- a/CryptoExchange.Net/Objects/Error.cs +++ b/CryptoExchange.Net/Objects/Error.cs @@ -2,7 +2,13 @@ { public abstract class Error { + /// + /// The error code + /// public int Code { get; set; } + /// + /// The message for the error that occured + /// public string Message { get; set; } protected Error(int code, string message) diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 55565aa..3a8a46d 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -21,6 +21,9 @@ namespace CryptoExchange.Net { public abstract class RestClient: BaseClient { + /// + /// The factory for creating requests. Used for unit testing + /// public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); protected RateLimitingBehaviour rateLimitBehaviour; @@ -30,11 +33,6 @@ namespace CryptoExchange.Net private List rateLimiters; - private static readonly JsonSerializer defaultSerializer = JsonSerializer.Create(new JsonSerializerSettings() - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc - }); - protected RestClient(ClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) { Configure(exchangeOptions); @@ -103,6 +101,16 @@ namespace CryptoExchange.Net return new CallResult(0, new CantConnectError() { Message = "Ping failed: " + reply.Status }); } + /// + /// Execute a request + /// + /// The expected result type + /// The uri to send the request to + /// The method of the request + /// 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) + /// protected virtual async Task> ExecuteRequest(Uri uri, string method = Constants.GetMethod, Dictionary parameters = null, bool signed = false, bool checkResult = true) where T : class { log.Write(LogVerbosity.Debug, $"Creating request for " + uri); @@ -149,6 +157,14 @@ namespace CryptoExchange.Net return result.Error != null ? new CallResult(null, result.Error) : Deserialize(result.Data, checkResult); } + /// + /// Creates a request object + /// + /// The uri to send the request to + /// The method of the request + /// The parameters of the request + /// Whether or not the request should be authenticated + /// protected virtual IRequest ConstructRequest(Uri uri, string method, Dictionary parameters, bool signed) { if (parameters == null) @@ -184,6 +200,11 @@ namespace CryptoExchange.Net return request; } + /// + /// Writes the string data of the paramters to the request body stream + /// + /// + /// protected virtual void WriteParamBody(IRequest request, string stringData) { var data = Encoding.UTF8.GetBytes(stringData); @@ -193,6 +214,11 @@ namespace CryptoExchange.Net stream.Write(data, 0, data.Length); } + /// + /// Writes the parameters of the request to the request object, either in the query string or the request body + /// + /// + /// protected virtual void WriteParamBody(IRequest request, Dictionary parameters) { if (requestBodyFormat == RequestBodyFormat.Json) @@ -210,6 +236,11 @@ namespace CryptoExchange.Net } } + /// + /// Executes the request and returns the string result + /// + /// The request object to execute + /// private async Task> ExecuteRequest(IRequest request) { var returnedData = ""; @@ -258,6 +289,11 @@ namespace CryptoExchange.Net } } + /// + /// Parse an error response from the server. Only used when server returns a status other than Success(200) + /// + /// The string the request returned + /// protected virtual Error ParseErrorResponse(string error) { return new ServerError(error); diff --git a/CryptoExchange.Net/SocketClient.cs b/CryptoExchange.Net/SocketClient.cs index 487bb14..fe9ffb2 100644 --- a/CryptoExchange.Net/SocketClient.cs +++ b/CryptoExchange.Net/SocketClient.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Authentication; -using System.Text; using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; @@ -18,6 +17,9 @@ namespace CryptoExchange.Net public abstract class SocketClient: BaseClient { #region fields + /// + /// The factory for creating sockets. Used for unit testing + /// public IWebsocketFactory SocketFactory { get; set; } = new WebsocketFactory(); private const SslProtocols protocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; @@ -51,6 +53,11 @@ namespace CryptoExchange.Net dataInterpreter = handler; } + /// + /// Create a socket for an address + /// + /// The address the socket should connect to + /// protected virtual IWebsocket CreateSocket(string address) { var socket = SocketFactory.CreateWebsocket(log, address); @@ -80,8 +87,20 @@ namespace CryptoExchange.Net protected virtual void SocketOpened(IWebsocket socket) { } protected virtual void SocketClosed(IWebsocket socket) { } protected virtual void SocketError(IWebsocket socket, Exception ex) { } - protected abstract bool SocketReconnect(SocketSubscription socket, TimeSpan disconnectedTime); + /// + /// Handler for when a socket reconnects. Should return true if reconnection handling was successful or false if not ( will try to reconnect again ). The handler should + /// handle functionality like resubscribing and re-authenticating the socket. + /// + /// The socket subscription that was reconnected + /// The time the socket was disconnected + /// + protected abstract bool SocketReconnect(SocketSubscription subscription, TimeSpan disconnectedTime); + /// + /// Connect a socket + /// + /// The subscription to connect + /// protected virtual async Task> ConnectSocket(SocketSubscription socketSubscription) { socketSubscription.Socket.OnMessage += data => ProcessMessage(socketSubscription, data); @@ -97,13 +116,22 @@ namespace CryptoExchange.Net return new CallResult(false, new CantConnectError()); } - protected virtual void ProcessMessage(SocketSubscription sub, string data) + /// + /// The message handler. Normally distributes the received data to all data handlers + /// + /// The subscription that received the data + /// The data received + protected virtual void ProcessMessage(SocketSubscription subscription, string data) { - log.Write(LogVerbosity.Debug, $"Socket {sub.Socket.Id} received data: " + data); - foreach (var handler in sub.DataHandlers) - handler(sub, JToken.Parse(data)); + log.Write(LogVerbosity.Debug, $"Socket {subscription.Socket.Id} received data: " + data); + foreach (var handler in subscription.DataHandlers) + handler(subscription, JToken.Parse(data)); } + /// + /// Handler for a socket closing. Reconnects the socket if needed, or removes it from the active socket list if not + /// + /// The socket that was closed protected virtual void SocketOnClose(IWebsocket socket) { if (socket.ShouldReconnect) @@ -148,22 +176,43 @@ namespace CryptoExchange.Net } } + /// + /// Send data to the websocket + /// + /// The type of the object to send + /// The socket to send to + /// The object to send + /// How null values should be serialized protected virtual void Send(IWebsocket socket, T obj, NullValueHandling nullValueHandling = NullValueHandling.Ignore) { Send(socket, JsonConvert.SerializeObject(obj, Formatting.None, new JsonSerializerSettings { NullValueHandling = nullValueHandling })); } + /// + /// Send string data to the websocket + /// + /// The socket to send to + /// The data to send protected virtual void Send(IWebsocket socket, string data) { log.Write(LogVerbosity.Debug, $"Socket {socket.Id} sending data: {data}"); socket.Send(data); } - public virtual async Task Unsubscribe(UpdateSubscription sub) + /// + /// Unsubscribe from a stream + /// + /// The subscription to unsubscribe + /// + public virtual async Task Unsubscribe(UpdateSubscription subscription) { - await sub.Close(); + await subscription.Close(); } + /// + /// Unsubscribe all subscriptions + /// + /// public virtual async Task UnsubscribeAll() { await Task.Run(() =>