1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-12 17:03:10 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
JKorf 0987c0f9d1 Updated version 2023-12-02 14:22:12 +01:00
JKorf e2dde77023 Added DecimalStringWriter json converter, added support for specifying body content type on a per request basis 2023-12-02 14:20:41 +01:00
JKorf 104ac7caad Updated response logging, added RequestId to WebCallResult 2023-11-30 14:19:58 +01:00
JKorf 8788dd3deb Updated version 2023-10-28 15:19:11 +02:00
JKorf f64cc5e9cf Added additional helpers methods 2023-10-28 15:17:10 +02:00
JKorf 75d1bbc6e8 Updated examples package versions 2023-10-24 21:11:50 +02:00
10 changed files with 163 additions and 55 deletions
@@ -115,6 +115,7 @@ namespace CryptoExchange.Net.UnitTests
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,
"{}", "{}",
1,
"https://test.com/api", "https://test.com/api",
null, null,
HttpMethod.Get, HttpMethod.Get,
@@ -143,6 +144,7 @@ namespace CryptoExchange.Net.UnitTests
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,
"{}", "{}",
1,
"https://test.com/api", "https://test.com/api",
null, null,
HttpMethod.Get, HttpMethod.Get,
+37 -24
View File
@@ -83,6 +83,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -97,6 +98,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -108,11 +110,16 @@ namespace CryptoExchange.Net
while (true) while (true)
{ {
currentTry++; currentTry++;
var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false); var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, requestBodyFormat, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request) if (!request)
return new WebCallResult(request.Error!); return new WebCallResult(request.Error!);
var result = await GetResponseAsync<object>(request.Data, deserializer, cancellationToken, true).ConfigureAwait(false); var result = await GetResponseAsync<object>(request.Data, deserializer, cancellationToken, true).ConfigureAwait(false);
if (!result)
_logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}");
else
_logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}");
if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false))
continue; continue;
@@ -129,6 +136,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -143,6 +151,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -155,11 +164,16 @@ namespace CryptoExchange.Net
while (true) while (true)
{ {
currentTry++; currentTry++;
var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false); var request = await PrepareRequestAsync(uri, method, cancellationToken, parameters, signed, requestBodyFormat, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request) if (!request)
return new WebCallResult<T>(request.Error!); return new WebCallResult<T>(request.Error!);
var result = await GetResponseAsync<T>(request.Data, deserializer, cancellationToken, false).ConfigureAwait(false); var result = await GetResponseAsync<T>(request.Data, deserializer, cancellationToken, false).ConfigureAwait(false);
if (!result)
_logger.Log(LogLevel.Warning, $"[{result.RequestId}] Error received in {result.ResponseTime!.Value.TotalMilliseconds}ms: {result.Error}");
else
_logger.Log(LogLevel.Debug, $"[{result.RequestId}] Response received in {result.ResponseTime!.Value.TotalMilliseconds}ms{(OutputOriginalData ? (": " + result.OriginalData) : "")}");
if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false)) if (await ShouldRetryRequestAsync(result, currentTry).ConfigureAwait(false))
continue; continue;
@@ -175,6 +189,7 @@ namespace CryptoExchange.Net
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="requestBodyFormat">The format of the body content</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param> /// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param> /// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param> /// <param name="requestWeight">Credits used for the request</param>
@@ -188,6 +203,7 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, Dictionary<string, object>? parameters = null,
bool signed = false, bool signed = false,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1, int requestWeight = 1,
@@ -232,7 +248,7 @@ namespace CryptoExchange.Net
_logger.Log(LogLevel.Information, $"[{requestId}] Creating request for " + uri); _logger.Log(LogLevel.Information, $"[{requestId}] Creating request for " + uri);
var paramsPosition = parameterPosition ?? ParameterPositions[method]; var paramsPosition = parameterPosition ?? ParameterPositions[method];
var request = ConstructRequest(uri, method, parameters?.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value), signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders); var request = ConstructRequest(uri, method, parameters?.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value), signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestBodyFormat ?? this.requestBodyFormat, requestId, additionalHeaders);
string? paramString = ""; string? paramString = "";
if (paramsPosition == HttpMethodParameterPosition.InBody) if (paramsPosition == HttpMethodParameterPosition.InBody)
@@ -261,9 +277,9 @@ namespace CryptoExchange.Net
CancellationToken cancellationToken, CancellationToken cancellationToken,
bool expectedEmptyResponse) bool expectedEmptyResponse)
{ {
var sw = Stopwatch.StartNew();
try try
{ {
var sw = Stopwatch.StartNew();
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop(); sw.Stop();
var statusCode = response.StatusCode; var statusCode = response.StatusCode;
@@ -281,23 +297,22 @@ namespace CryptoExchange.Net
responseLength ??= data.Length; responseLength ??= data.Length;
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
_logger.Log(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(OutputOriginalData ? (": " + data) : "")}");
if (!expectedEmptyResponse) if (!expectedEmptyResponse)
{ {
// Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example // Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example
var parseResult = ValidateJson(data); var parseResult = ValidateJson(data);
if (!parseResult.Success) if (!parseResult.Success)
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
// Let the library implementation see if it is an error response, and if so parse the error // Let the library implementation see if it is an error response, and if so parse the error
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null) if (error != null)
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
// Not an error, so continue deserializing // Not an error, so continue deserializing
var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId); var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error);
} }
else else
{ {
@@ -306,16 +321,16 @@ namespace CryptoExchange.Net
var parseResult = ValidateJson(data); var parseResult = ValidateJson(data);
if (!parseResult.Success) if (!parseResult.Success)
// Not empty, and not json // Not empty, and not json
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false); var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null) if (error != null)
// Error response // Error response
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
} }
// Empty success response; okay // Empty success response; okay
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default); return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default);
} }
} }
else else
@@ -326,7 +341,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);
} }
// Success status code, and we don't have to check for errors. Continue deserializing directly from the stream // Success status code, and we don't have to check for errors. Continue deserializing directly from the stream
@@ -334,7 +349,7 @@ namespace CryptoExchange.Net
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error);
} }
} }
else else
@@ -342,7 +357,6 @@ namespace CryptoExchange.Net
// Http status code indicates error // Http status code indicates error
using var reader = new StreamReader(responseStream); using var reader = new StreamReader(responseStream);
var data = await reader.ReadToEndAsync().ConfigureAwait(false); var data = await reader.ReadToEndAsync().ConfigureAwait(false);
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Error received in {sw.ElapsedMilliseconds}ms: {data}");
responseStream.Close(); responseStream.Close();
response.Close(); response.Close();
@@ -354,29 +368,26 @@ namespace CryptoExchange.Net
if (error.Code == null || error.Code == 0) if (error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode; error.Code = (int)response.StatusCode;
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, data.Length, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, data.Length, data, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error);
} }
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {
// Request exception, can't reach server for instance // Request exception, can't reach server for instance
var exceptionInfo = requestException.ToLogString(); var exceptionInfo = requestException.ToLogString();
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
} }
catch (OperationCanceledException canceledException) catch (OperationCanceledException canceledException)
{ {
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{ {
// Cancellation token canceled by caller // Cancellation token canceled by caller
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token"); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
} }
else else
{ {
// Request timed out // Request timed out
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString()); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"Request timed out"));
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out"));
} }
} }
} }
@@ -412,6 +423,7 @@ namespace CryptoExchange.Net
/// <param name="signed">Whether or not the request should be authenticated</param> /// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="parameterPosition">Where the parameters should be placed</param> /// <param name="parameterPosition">Where the parameters should be placed</param>
/// <param name="arraySerialization">How array parameters should be serialized</param> /// <param name="arraySerialization">How array parameters should be serialized</param>
/// <param name="bodyFormat">Format of the body content</param>
/// <param name="requestId">Unique id of a request</param> /// <param name="requestId">Unique id of a request</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param> /// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns> /// <returns></returns>
@@ -422,6 +434,7 @@ namespace CryptoExchange.Net
bool signed, bool signed,
HttpMethodParameterPosition parameterPosition, HttpMethodParameterPosition parameterPosition,
ArrayParametersSerialization arraySerialization, ArrayParametersSerialization arraySerialization,
RequestBodyFormat bodyFormat,
int requestId, int requestId,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
@@ -580,14 +593,14 @@ namespace CryptoExchange.Net
{ {
var timeSyncParams = GetTimeSyncInfo(); var timeSyncParams = GetTimeSyncInfo();
if (timeSyncParams == null) if (timeSyncParams == null)
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false)) if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{ {
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)) if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval))
{ {
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
} }
var localTime = DateTime.UtcNow; var localTime = DateTime.UtcNow;
@@ -616,7 +629,7 @@ namespace CryptoExchange.Net
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
} }
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, true, null);
} }
} }
} }
@@ -0,0 +1,27 @@
using Newtonsoft.Json;
using System;
using System.Globalization;
namespace Kraken.Net.Converters
{
/// <summary>
/// Converter for serializing decimal values as string
/// </summary>
public class DecimalStringWriterConverter : JsonConverter
{
/// <inheritdoc />
public override bool CanRead => false;
/// <inheritdoc />
public override bool CanConvert(Type objectType) => objectType == typeof(decimal) || objectType == typeof(decimal?);
/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => writer.WriteValue(((decimal?)value)?.ToString(CultureInfo.InvariantCulture) ?? null);
}
}
+4 -4
View File
@@ -6,16 +6,16 @@
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>A base package for implementing cryptocurrency API's</Description> <Description>A base package for implementing cryptocurrency API's</Description>
<PackageVersion>6.2.0</PackageVersion> <PackageVersion>6.2.2</PackageVersion>
<AssemblyVersion>6.2.0</AssemblyVersion> <AssemblyVersion>6.2.2</AssemblyVersion>
<FileVersion>6.2.0</FileVersion> <FileVersion>6.2.2</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl> <RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>6.2.0 - Added SerializerOptions helper class for setting a default serializer, Added ParameterCollection helper class for easier parameter definition, Added extra helper methods AuthenticationProvider, Remove interface entries meant for internal use, Added support for writing int values to the EnumConverter</PackageReleaseNotes> <PackageReleaseNotes>6.2.2 - Added support for specifying the request body content type on a per request basis, Added DecimalStringWriterConverter, Added RequestId to WebCallResult model, Updated response logging</PackageReleaseNotes>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
+41
View File
@@ -1,5 +1,6 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System; using System;
using System.Security.Cryptography;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
@@ -8,6 +9,8 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public static class ExchangeHelpers public static class ExchangeHelpers
{ {
private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789";
/// <summary> /// <summary>
/// The last used id, use NextId() to get the next id and up this /// The last used id, use NextId() to get the next id and up this
/// </summary> /// </summary>
@@ -140,5 +143,43 @@ namespace CryptoExchange.Net
return _lastId; return _lastId;
} }
} }
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="length">Length of the random string</param>
/// <returns></returns>
public static string RandomString(int length)
{
var randomChars = new char[length];
#if NETSTANDARD2_1_OR_GREATER
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
#else
var random = new Random();
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)];
#endif
return new string(randomChars);
}
/// <summary>
/// Generate a random string of specified length
/// </summary>
/// <param name="source">The initial string</param>
/// <param name="totalLength">Total length of the resulting string</param>
/// <returns></returns>
public static string AppendRandomString(string source, int totalLength)
{
if (totalLength < source.Length)
throw new ArgumentException("Total length smaller than source string length", nameof(totalLength));
if (totalLength == source.Length)
return source;
return source + RandomString(totalLength - source.Length);
}
} }
} }
+22 -6
View File
@@ -186,6 +186,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; }
/// <summary>
/// The request id
/// </summary>
public int? RequestId { get; set; }
/// <summary> /// <summary>
/// The url which was requested /// The url which was requested
/// </summary> /// </summary>
@@ -217,6 +222,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="code"></param> /// <param name="code"></param>
/// <param name="responseHeaders"></param> /// <param name="responseHeaders"></param>
/// <param name="responseTime"></param> /// <param name="responseTime"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param> /// <param name="requestUrl"></param>
/// <param name="requestBody"></param> /// <param name="requestBody"></param>
/// <param name="requestMethod"></param> /// <param name="requestMethod"></param>
@@ -226,6 +232,7 @@ namespace CryptoExchange.Net.Objects
HttpStatusCode? code, HttpStatusCode? code,
IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders,
TimeSpan? responseTime, TimeSpan? responseTime,
int? requestId,
string? requestUrl, string? requestUrl,
string? requestBody, string? requestBody,
HttpMethod? requestMethod, HttpMethod? requestMethod,
@@ -235,6 +242,7 @@ namespace CryptoExchange.Net.Objects
ResponseStatusCode = code; ResponseStatusCode = code;
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseTime = responseTime; ResponseTime = responseTime;
RequestId = requestId;
RequestUrl = requestUrl; RequestUrl = requestUrl;
RequestBody = requestBody; RequestBody = requestBody;
@@ -255,7 +263,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public WebCallResult AsError(Error error) public WebCallResult AsError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -281,6 +289,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? RequestHeaders { get; set; }
/// <summary>
/// The request id
/// </summary>
public int? RequestId { get; set; }
/// <summary> /// <summary>
/// The url which was requested /// The url which was requested
/// </summary> /// </summary>
@@ -319,6 +332,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="responseTime"></param> /// <param name="responseTime"></param>
/// <param name="responseLength"></param> /// <param name="responseLength"></param>
/// <param name="originalData"></param> /// <param name="originalData"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param> /// <param name="requestUrl"></param>
/// <param name="requestBody"></param> /// <param name="requestBody"></param>
/// <param name="requestMethod"></param> /// <param name="requestMethod"></param>
@@ -331,6 +345,7 @@ namespace CryptoExchange.Net.Objects
TimeSpan? responseTime, TimeSpan? responseTime,
long? responseLength, long? responseLength,
string? originalData, string? originalData,
int? requestId,
string? requestUrl, string? requestUrl,
string? requestBody, string? requestBody,
HttpMethod? requestMethod, HttpMethod? requestMethod,
@@ -343,6 +358,7 @@ namespace CryptoExchange.Net.Objects
ResponseTime = responseTime; ResponseTime = responseTime;
ResponseLength = responseLength; ResponseLength = responseLength;
RequestId = requestId;
RequestUrl = requestUrl; RequestUrl = requestUrl;
RequestBody = requestBody; RequestBody = requestBody;
RequestHeaders = requestHeaders; RequestHeaders = requestHeaders;
@@ -355,7 +371,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDataless() public new WebCallResult AsDataless()
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
} }
/// <summary> /// <summary>
/// Copy as a dataless result /// Copy as a dataless result
@@ -363,14 +379,14 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDatalessError(Error error) public new WebCallResult AsDatalessError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <summary> /// <summary>
/// Create a new error result /// Create a new error result
/// </summary> /// </summary>
/// <param name="error">The error</param> /// <param name="error">The error</param>
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, default, error) { } public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, default, error) { }
/// <summary> /// <summary>
/// Copy the WebCallResult to a new data type /// Copy the WebCallResult to a new data type
@@ -380,7 +396,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> As<K>([AllowNull] K data) public new WebCallResult<K> As<K>([AllowNull] K data)
{ {
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, data, Error);
} }
/// <summary> /// <summary>
@@ -391,7 +407,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> AsError<K>(Error error) public new WebCallResult<K> AsError<K>(Error error)
{ {
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, default, error);
} }
/// <inheritdoc /> /// <inheritdoc />
+1 -1
View File
@@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return Code != null ? $"{Code}: {Message} {Data}" : $"{Message} {Data}"; return Code != null ? $"[{GetType().Name}] {Code}: {Message} {Data}" : $"[{GetType().Name}] {Message} {Data}";
} }
} }
+10 -10
View File
@@ -5,16 +5,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Binance.Net" Version="9.1.4" /> <PackageReference Include="Binance.Net" Version="9.1.5" />
<PackageReference Include="Bitfinex.Net" Version="7.0.3" /> <PackageReference Include="Bitfinex.Net" Version="7.0.4" />
<PackageReference Include="Bittrex.Net" Version="8.0.2" /> <PackageReference Include="Bittrex.Net" Version="8.0.3" />
<PackageReference Include="Bybit.Net" Version="3.1.3" /> <PackageReference Include="Bybit.Net" Version="3.2.1" />
<PackageReference Include="CoinEx.Net" Version="6.0.2" /> <PackageReference Include="CoinEx.Net" Version="6.0.3" />
<PackageReference Include="Huobi.Net" Version="5.0.2" /> <PackageReference Include="Huobi.Net" Version="5.0.3" />
<PackageReference Include="JK.Bitget.Net" Version="0.0.1" /> <PackageReference Include="JK.Bitget.Net" Version="1.0.0" />
<PackageReference Include="JK.OKX.Net" Version="1.4.1" /> <PackageReference Include="JK.OKX.Net" Version="1.4.2" />
<PackageReference Include="KrakenExchange.Net" Version="4.1.4" /> <PackageReference Include="KrakenExchange.Net" Version="4.1.5" />
<PackageReference Include="Kucoin.Net" Version="5.0.4" /> <PackageReference Include="Kucoin.Net" Version="5.0.5" />
<PackageReference Include="Serilog.AspNetCore" Version="6.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="6.0.0" />
</ItemGroup> </ItemGroup>
+10 -10
View File
@@ -6,16 +6,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Binance.Net" Version="9.1.3" /> <PackageReference Include="Binance.Net" Version="9.1.5" />
<PackageReference Include="Bitfinex.Net" Version="7.0.2" /> <PackageReference Include="Bitfinex.Net" Version="7.0.4" />
<PackageReference Include="Bittrex.Net" Version="8.0.1" /> <PackageReference Include="Bittrex.Net" Version="8.0.3" />
<PackageReference Include="Bybit.Net" Version="3.1.2" /> <PackageReference Include="Bybit.Net" Version="3.2.1" />
<PackageReference Include="CoinEx.Net" Version="6.0.1" /> <PackageReference Include="CoinEx.Net" Version="6.0.3" />
<PackageReference Include="Huobi.Net" Version="5.0.1" /> <PackageReference Include="Huobi.Net" Version="5.0.3" />
<PackageReference Include="JK.Bitget.Net" Version="0.0.1" /> <PackageReference Include="JK.Bitget.Net" Version="1.0.0" />
<PackageReference Include="JK.OKX.Net" Version="1.4.0" /> <PackageReference Include="JK.OKX.Net" Version="1.4.2" />
<PackageReference Include="KrakenExchange.Net" Version="4.1.3" /> <PackageReference Include="KrakenExchange.Net" Version="4.1.5" />
<PackageReference Include="Kucoin.Net" Version="5.0.3" /> <PackageReference Include="Kucoin.Net" Version="5.0.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+9
View File
@@ -31,6 +31,15 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf). Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes ## Release notes
* Version 6.2.2 - 02 Dec 2023
* Added support for specifying the request body content type on a per request basis
* Added DecimalStringWriterConverter
* Added RequestId to WebCallResult model
* Updated response logging
* Version 6.2.1 - 28 Oct 2023
* Utility methods
* Version 6.2.0 - 24 Oct 2023 * Version 6.2.0 - 24 Oct 2023
* Added SerializerOptions helper class for setting a default serializer * Added SerializerOptions helper class for setting a default serializer
* Added ParameterCollection helper class for easier parameter definition * Added ParameterCollection helper class for easier parameter definition