1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-11 18:06:27 +00:00
This commit is contained in:
Jan Korf 2019-10-11 14:48:30 +02:00
parent 715fe378d5
commit 8ec902951d
33 changed files with 725 additions and 655 deletions

View File

@ -14,17 +14,17 @@ namespace CryptoExchange.Net.Authentication
/// <summary> /// <summary>
/// The api key to authenticate requests /// The api key to authenticate requests
/// </summary> /// </summary>
public SecureString Key { get; } public SecureString? Key { get; }
/// <summary> /// <summary>
/// The api secret to authenticate requests /// The api secret to authenticate requests
/// </summary> /// </summary>
public SecureString Secret { get; } public SecureString? Secret { get; }
/// <summary> /// <summary>
/// The private key to authenticate requests /// The private key to authenticate requests
/// </summary> /// </summary>
public PrivateKey PrivateKey { get; } public PrivateKey? PrivateKey { get; }
/// <summary> /// <summary>
/// Create Api credentials providing a private key for authentication /// Create Api credentials providing a private key for authentication
@ -56,8 +56,20 @@ namespace CryptoExchange.Net.Authentication
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret)) if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
throw new ArgumentException("Key and secret can't be null/empty"); throw new ArgumentException("Key and secret can't be null/empty");
Key = CreateSecureString(key); Key = key.ToSecureString();
Secret = CreateSecureString(secret); Secret = secret.ToSecureString();
}
/// <summary>
/// Copy the credentials
/// </summary>
/// <returns></returns>
public ApiCredentials Copy()
{
if (PrivateKey == null)
return new ApiCredentials(Key!.GetString(), Secret!.GetString());
else
return new ApiCredentials(PrivateKey!.Copy());
} }
/// <summary> /// <summary>
@ -66,24 +78,23 @@ namespace CryptoExchange.Net.Authentication
/// <param name="inputStream">The stream containing the json data</param> /// <param name="inputStream">The stream containing the json data</param>
/// <param name="identifierKey">A key to identify the credentials for the API. For example, when set to `binanceKey` the json data should contain a value for the property `binanceKey`. Defaults to 'apiKey'.</param> /// <param name="identifierKey">A key to identify the credentials for the API. For example, when set to `binanceKey` the json data should contain a value for the property `binanceKey`. Defaults to 'apiKey'.</param>
/// <param name="identifierSecret">A key to identify the credentials for the API. For example, when set to `binanceSecret` the json data should contain a value for the property `binanceSecret`. Defaults to 'apiSecret'.</param> /// <param name="identifierSecret">A key to identify the credentials for the API. For example, when set to `binanceSecret` the json data should contain a value for the property `binanceSecret`. Defaults to 'apiSecret'.</param>
public ApiCredentials(Stream inputStream, string identifierKey = null, string identifierSecret = null) public ApiCredentials(Stream inputStream, string? identifierKey = null, string? identifierSecret = null)
{ {
using (var reader = new StreamReader(inputStream, Encoding.ASCII, false, 512, true)) using var reader = new StreamReader(inputStream, Encoding.ASCII, false, 512, true);
{
var stringData = reader.ReadToEnd(); var stringData = reader.ReadToEnd();
var jsonData = stringData.ToJToken(); var jsonData = stringData.ToJToken();
if(jsonData == null) if(jsonData == null)
throw new ArgumentException("Input stream not valid json data"); throw new ArgumentException("Input stream not valid json data");
var key = TryGetValue(jsonData, identifierKey ?? "apiKey"); var key = TryGetValue(jsonData, identifierKey ?? "apiKey");
var secret = TryGetValue(jsonData, identifierSecret ?? "apiSecret"); var secret = TryGetValue(jsonData, identifierSecret ?? "apiSecret");
if (key == null || secret == null) if (key == null || secret == null)
throw new ArgumentException("apiKey or apiSecret value not found in Json credential file"); throw new ArgumentException("apiKey or apiSecret value not found in Json credential file");
Key = CreateSecureString(key); Key = key.ToSecureString();
Secret = CreateSecureString(secret); Secret = secret.ToSecureString();
}
inputStream.Seek(0, SeekOrigin.Begin); inputStream.Seek(0, SeekOrigin.Begin);
} }
@ -94,26 +105,12 @@ namespace CryptoExchange.Net.Authentication
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="key"></param> /// <param name="key"></param>
/// <returns></returns> /// <returns></returns>
protected string TryGetValue(JToken data, string key) protected string? TryGetValue(JToken data, string key)
{ {
if (data[key] == null) if (data[key] == null)
return null; return null;
return (string) data[key]; return (string) data[key];
} }
/// <summary>
/// Create a secure string from a string
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
protected SecureString CreateSecureString(string source)
{
var secureString = new SecureString();
foreach (var c in source)
secureString.AppendChar(c);
secureString.MakeReadOnly();
return secureString;
}
/// <summary> /// <summary>
/// Dispose /// Dispose

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
namespace CryptoExchange.Net.Authentication namespace CryptoExchange.Net.Authentication
{ {
@ -29,7 +30,7 @@ namespace CryptoExchange.Net.Authentication
/// <param name="parameters"></param> /// <param name="parameters"></param>
/// <param name="signed"></param> /// <param name="signed"></param>
/// <returns></returns> /// <returns></returns>
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, string method, Dictionary<string, object> parameters, bool signed) public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
{ {
return parameters; return parameters;
} }
@ -42,7 +43,7 @@ namespace CryptoExchange.Net.Authentication
/// <param name="parameters"></param> /// <param name="parameters"></param>
/// <param name="signed"></param> /// <param name="signed"></param>
/// <returns></returns> /// <returns></returns>
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, string method, Dictionary<string, object> parameters, bool signed) public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
{ {
return new Dictionary<string, string>(); return new Dictionary<string, string>();
} }

View File

@ -16,7 +16,7 @@ namespace CryptoExchange.Net.Authentication
/// <summary> /// <summary>
/// The private key's pass phrase /// The private key's pass phrase
/// </summary> /// </summary>
public SecureString Passphrase { get; } public SecureString? Passphrase { get; }
/// <summary> /// <summary>
/// Indicates if the private key is encrypted or not /// Indicates if the private key is encrypted or not
@ -81,15 +81,23 @@ namespace CryptoExchange.Net.Authentication
if (string.IsNullOrEmpty(key)) if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key can't be null/empty"); throw new ArgumentException("Key can't be null/empty");
var secureKey = new SecureString(); Key = key.ToSecureString();
foreach (var c in key)
secureKey.AppendChar(c);
secureKey.MakeReadOnly();
Key = secureKey;
IsEncrypted = false; IsEncrypted = false;
} }
/// <summary>
/// Copy the private key
/// </summary>
/// <returns></returns>
public PrivateKey Copy()
{
if (Passphrase == null)
return new PrivateKey(Key.GetString());
else
return new PrivateKey(Key.GetString(), Passphrase.GetString());
}
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>

View File

@ -7,15 +7,17 @@ using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
namespace CryptoExchange.Net namespace CryptoExchange.Net
{ {
/// <summary> /// <summary>
/// The base for all clients /// The base for all clients
/// </summary> /// </summary>
public abstract class BaseClient: IDisposable public abstract class BaseClient : IDisposable
{ {
/// <summary> /// <summary>
/// The address of the client /// The address of the client
@ -28,11 +30,11 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// The api proxy /// The api proxy
/// </summary> /// </summary>
protected ApiProxy apiProxy; protected ApiProxy? apiProxy;
/// <summary> /// <summary>
/// The auth provider /// The auth provider
/// </summary> /// </summary>
protected internal AuthenticationProvider authProvider; protected internal AuthenticationProvider? authProvider;
/// <summary> /// <summary>
/// The last used id /// The last used id
@ -59,26 +61,17 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
/// <param name="options"></param> /// <param name="options"></param>
/// <param name="authenticationProvider"></param> /// <param name="authenticationProvider"></param>
protected BaseClient(ClientOptions options, AuthenticationProvider authenticationProvider) protected BaseClient(ClientOptions options, AuthenticationProvider? authenticationProvider)
{ {
log = new Log(); log = new Log();
authProvider = authenticationProvider; authProvider = authenticationProvider;
Configure(options); log.UpdateWriters(options.LogWriters);
} log.Level = options.LogVerbosity;
/// <summary> BaseAddress = options.BaseAddress;
/// Configure the client using the provided options apiProxy = options.Proxy;
/// </summary>
/// <param name="clientOptions">Options</param>
protected void Configure(ClientOptions clientOptions)
{
log.UpdateWriters(clientOptions.LogWriters);
log.Level = clientOptions.LogVerbosity;
BaseAddress = clientOptions.BaseAddress; log.Write(LogVerbosity.Debug, $"Client configuration: {options}");
apiProxy = clientOptions.Proxy;
if (apiProxy != null)
log.Write(LogVerbosity.Info, $"Setting api proxy to {clientOptions.Proxy.Host}:{clientOptions.Proxy.Port}");
} }
/// <summary> /// <summary>
@ -137,10 +130,10 @@ namespace CryptoExchange.Net
/// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param> /// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
/// <param name="serializer">A specific serializer to use</param> /// <param name="serializer">A specific serializer to use</param>
/// <returns></returns> /// <returns></returns>
protected CallResult<T> Deserialize<T>(string data, bool checkObject = true, JsonSerializer serializer = null) protected CallResult<T> Deserialize<T>(string data, bool checkObject = true, JsonSerializer? serializer = null)
{ {
var tokenResult = ValidateJson(data); var tokenResult = ValidateJson(data);
return !tokenResult.Success ? new CallResult<T>(default, tokenResult.Error) : Deserialize<T>(tokenResult.Data, checkObject, serializer); return !tokenResult ? new CallResult<T>(default, tokenResult.Error) : Deserialize<T>(tokenResult.Data, checkObject, serializer);
} }
/// <summary> /// <summary>
@ -151,7 +144,7 @@ namespace CryptoExchange.Net
/// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param> /// <param name="checkObject">Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug)</param>
/// <param name="serializer">A specific serializer to use</param> /// <param name="serializer">A specific serializer to use</param>
/// <returns></returns> /// <returns></returns>
protected CallResult<T> Deserialize<T>(JToken obj, bool checkObject = true, JsonSerializer serializer = null) protected CallResult<T> Deserialize<T>(JToken obj, bool checkObject = true, JsonSerializer? serializer = null)
{ {
if (serializer == null) if (serializer == null)
serializer = defaultSerializer; serializer = defaultSerializer;
@ -200,6 +193,53 @@ namespace CryptoExchange.Net
} }
} }
/// <summary>
/// Deserialize a stream into an object
/// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="stream">The stream to deserialize</param>
/// <param name="serializer">A specific serializer to use</param>
/// <returns></returns>
protected async Task<CallResult<T>> Deserialize<T>(Stream stream, JsonSerializer? serializer = null)
{
if (serializer == null)
serializer = defaultSerializer;
try
{
using var reader = new StreamReader(stream);
using var jsonReader = new JsonTextReader(reader);
return new CallResult<T>(serializer.Deserialize<T>(jsonReader), null);
}
catch (JsonReaderException jre)
{
var data = await ReadStream(stream).ConfigureAwait(false);
var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
}
catch (JsonSerializationException jse)
{
var data = await ReadStream(stream).ConfigureAwait(false);
var info = $"Deserialize JsonSerializationException: {jse.Message}, data: {data}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
}
catch (Exception ex)
{
var data = await ReadStream(stream).ConfigureAwait(false);
var info = $"Deserialize Unknown Exception: {ex.Message}, data: {data}";
log.Write(LogVerbosity.Error, info);
return new CallResult<T>(default, new DeserializeError(info));
}
}
private async Task<string> ReadStream(Stream stream)
{
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync().ConfigureAwait(false);
}
private void CheckObject(Type type, JObject obj) private void CheckObject(Type type, JObject obj)
{ {
if (type.GetCustomAttribute<JsonConverterAttribute>(true) != null) if (type.GetCustomAttribute<JsonConverterAttribute>(true) != null)
@ -233,13 +273,17 @@ namespace CryptoExchange.Net
if (d == null) if (d == null)
{ {
d = properties.SingleOrDefault(p => string.Equals(p, token.Key, StringComparison.CurrentCultureIgnoreCase)); d = properties.SingleOrDefault(p => string.Equals(p, token.Key, StringComparison.CurrentCultureIgnoreCase));
if (d == null && !(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))) if (d == null)
{ {
log.Write(LogVerbosity.Warning, $"Local object doesn't have property `{token.Key}` expected in type `{type.Name}`"); if (!(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)))
isDif = true; {
log.Write(LogVerbosity.Warning, $"Local object doesn't have property `{token.Key}` expected in type `{type.Name}`");
isDif = true;
}
continue; continue;
} }
} }
properties.Remove(d); properties.Remove(d);
var propType = GetProperty(d, props)?.PropertyType; var propType = GetProperty(d, props)?.PropertyType;
@ -270,7 +314,7 @@ namespace CryptoExchange.Net
log.Write(LogVerbosity.Debug, "Returned data: " + obj); log.Write(LogVerbosity.Debug, "Returned data: " + obj);
} }
private static PropertyInfo GetProperty(string name, IEnumerable<PropertyInfo> props) private static PropertyInfo? GetProperty(string name, IEnumerable<PropertyInfo> props)
{ {
foreach (var prop in props) foreach (var prop in props)
{ {

View File

@ -21,7 +21,7 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (objectType == typeof(JToken)) if (objectType == typeof(JToken))
return JToken.Load(reader); return JToken.Load(reader);
@ -31,7 +31,7 @@ namespace CryptoExchange.Net.Converters
return ParseObject(arr, result, objectType); return ParseObject(arr, result, objectType);
} }
private static object ParseObject(JArray arr, object result, Type objectType) private static object? ParseObject(JArray arr, object result, Type objectType)
{ {
foreach (var property in objectType.GetProperties()) foreach (var property in objectType.GetProperties())
{ {
@ -76,7 +76,7 @@ namespace CryptoExchange.Net.Converters
var converterAttribute = (JsonConverterAttribute)property.GetCustomAttribute(typeof(JsonConverterAttribute)) ?? (JsonConverterAttribute)property.PropertyType.GetCustomAttribute(typeof(JsonConverterAttribute)); var converterAttribute = (JsonConverterAttribute)property.GetCustomAttribute(typeof(JsonConverterAttribute)) ?? (JsonConverterAttribute)property.PropertyType.GetCustomAttribute(typeof(JsonConverterAttribute));
var conversionAttribute = (JsonConversionAttribute)property.GetCustomAttribute(typeof(JsonConversionAttribute)) ?? (JsonConversionAttribute)property.PropertyType.GetCustomAttribute(typeof(JsonConversionAttribute)); var conversionAttribute = (JsonConversionAttribute)property.GetCustomAttribute(typeof(JsonConversionAttribute)) ?? (JsonConversionAttribute)property.PropertyType.GetCustomAttribute(typeof(JsonConversionAttribute));
object value; object? value;
if (converterAttribute != null) if (converterAttribute != null)
{ {
value = arr[attribute.Index].ToObject(property.PropertyType, new JsonSerializer {Converters = {(JsonConverter) Activator.CreateInstance(converterAttribute.ConverterType)}}); value = arr[attribute.Index].ToObject(property.PropertyType, new JsonSerializer {Converters = {(JsonConverter) Activator.CreateInstance(converterAttribute.ConverterType)}});
@ -133,7 +133,7 @@ namespace CryptoExchange.Net.Converters
while (arrayProp.Index != last + 1) while (arrayProp.Index != last + 1)
{ {
writer.WriteValue((string)null); writer.WriteValue((string?)null);
last += 1; last += 1;
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -10,7 +11,7 @@ namespace CryptoExchange.Net.Converters
/// Base class for enum converters /// Base class for enum converters
/// </summary> /// </summary>
/// <typeparam name="T">Type of enum to convert</typeparam> /// <typeparam name="T">Type of enum to convert</typeparam>
public abstract class BaseConverter<T>: JsonConverter public abstract class BaseConverter<T>: JsonConverter where T: struct
{ {
/// <summary> /// <summary>
/// The enum->string mapping /// The enum->string mapping
@ -38,7 +39,7 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.Value == null) if (reader.Value == null)
return null; return null;
@ -69,7 +70,7 @@ namespace CryptoExchange.Net.Converters
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T); return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
} }
private bool GetValue(string value, out T result) private bool GetValue(string value, [NotNullWhen(false)]out T result)
{ {
var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase)); var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
if (!mapping.Equals(default(KeyValuePair<T, string>))) if (!mapping.Equals(default(KeyValuePair<T, string>)))

View File

@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.Value == null) if (reader.Value == null)
return null; return null;

View File

@ -17,7 +17,7 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.Value == null) if (reader.Value == null)
return null; return null;

View File

@ -16,8 +16,11 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.Value == null)
return null;
if (reader.Value is double d) if (reader.Value is double d)
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(d); return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(d);

View File

@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Converters
} }
/// <inheritdoc /> /// <inheritdoc />
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object? ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
if (reader.Value == null) if (reader.Value == null)
return null; return null;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
@ -12,7 +12,8 @@
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>2.1.8 - Added array serialization options for implementations</PackageReleaseNotes> <PackageReleaseNotes>2.1.8 - Added array serialization options for implementations</PackageReleaseNotes>
<LangVersion>7.1</LangVersion> <Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>CryptoExchange.Net.xml</DocumentationFile> <DocumentationFile>CryptoExchange.Net.xml</DocumentationFile>

View File

@ -54,6 +54,12 @@
<param name="key">The api key used for identification</param> <param name="key">The api key used for identification</param>
<param name="secret">The api secret used for signing</param> <param name="secret">The api secret used for signing</param>
</member> </member>
<member name="M:CryptoExchange.Net.Authentication.ApiCredentials.Copy">
<summary>
Copy the credentials
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Authentication.ApiCredentials.#ctor(System.IO.Stream,System.String,System.String)"> <member name="M:CryptoExchange.Net.Authentication.ApiCredentials.#ctor(System.IO.Stream,System.String,System.String)">
<summary> <summary>
Create Api credentials providing a stream containing json data. The json data should include two values: apiKey and apiSecret Create Api credentials providing a stream containing json data. The json data should include two values: apiKey and apiSecret
@ -70,13 +76,6 @@
<param name="key"></param> <param name="key"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Authentication.ApiCredentials.CreateSecureString(System.String)">
<summary>
Create a secure string from a string
</summary>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Authentication.ApiCredentials.Dispose"> <member name="M:CryptoExchange.Net.Authentication.ApiCredentials.Dispose">
<summary> <summary>
Dispose Dispose
@ -98,7 +97,7 @@
</summary> </summary>
<param name="credentials"></param> <param name="credentials"></param>
</member> </member>
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.String,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)"> <member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
<summary> <summary>
Add authentication to the parameter list Add authentication to the parameter list
</summary> </summary>
@ -108,7 +107,7 @@
<param name="signed"></param> <param name="signed"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.String,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)"> <member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
<summary> <summary>
Add authentication to the header dictionary Add authentication to the header dictionary
</summary> </summary>
@ -185,6 +184,12 @@
</summary> </summary>
<param name="key">The private key used for signing</param> <param name="key">The private key used for signing</param>
</member> </member>
<member name="M:CryptoExchange.Net.Authentication.PrivateKey.Copy">
<summary>
Copy the private key
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Authentication.PrivateKey.Dispose"> <member name="M:CryptoExchange.Net.Authentication.PrivateKey.Dispose">
<summary> <summary>
Dispose Dispose
@ -237,12 +242,6 @@
<param name="options"></param> <param name="options"></param>
<param name="authenticationProvider"></param> <param name="authenticationProvider"></param>
</member> </member>
<member name="M:CryptoExchange.Net.BaseClient.Configure(CryptoExchange.Net.Objects.ClientOptions)">
<summary>
Configure the client using the provided options
</summary>
<param name="clientOptions">Options</param>
</member>
<member name="M:CryptoExchange.Net.BaseClient.SetAuthenticationProvider(CryptoExchange.Net.Authentication.AuthenticationProvider)"> <member name="M:CryptoExchange.Net.BaseClient.SetAuthenticationProvider(CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary> <summary>
Set the authentication provider Set the authentication provider
@ -276,6 +275,15 @@
<param name="serializer">A specific serializer to use</param> <param name="serializer">A specific serializer to use</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.BaseClient.Deserialize``1(System.IO.Stream,Newtonsoft.Json.JsonSerializer)">
<summary>
Deserialize a stream into an object
</summary>
<typeparam name="T">The type to deserialize into</typeparam>
<param name="stream">The stream to deserialize</param>
<param name="serializer">A specific serializer to use</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.BaseClient.NextId"> <member name="M:CryptoExchange.Net.BaseClient.NextId">
<summary> <summary>
Generate a unique id Generate a unique id
@ -467,11 +475,11 @@
<param name="source">The source secure string</param> <param name="source">The source secure string</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.ExtensionMethods.ToIEnumerable(System.Net.WebHeaderCollection)"> <member name="M:CryptoExchange.Net.ExtensionMethods.ToSecureString(System.String)">
<summary> <summary>
Header collection to IEnumerable Create a secure string from a string
</summary> </summary>
<param name="headers"></param> <param name="source"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.ExtensionMethods.WaitOneAsync(System.Threading.WaitHandle,System.Int32,System.Threading.CancellationToken)"> <member name="M:CryptoExchange.Net.ExtensionMethods.WaitOneAsync(System.Threading.WaitHandle,System.Int32,System.Threading.CancellationToken)">
@ -518,65 +526,49 @@
Request interface Request interface
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Uri"> <member name="P:CryptoExchange.Net.Interfaces.IRequest.Accept">
<summary> <summary>
The uri of the request Accept header
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Headers">
<summary>
The headers of the request
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Method">
<summary>
The method of the request
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Timeout">
<summary>
The timeout of the request
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.SetProxy(System.String,System.Int32,System.String,System.String)">
<summary>
Set a proxy
</summary>
<param name="host"></param>
<param name="port"></param>
<param name="login"></param>
<param name="password"></param>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.ContentType">
<summary>
Content type
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Content"> <member name="P:CryptoExchange.Net.Interfaces.IRequest.Content">
<summary> <summary>
String content Content
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.Accept"> <member name="P:CryptoExchange.Net.Interfaces.IRequest.Headers">
<summary> <summary>
Accept Headers
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IRequest.ContentLength"> <member name="P:CryptoExchange.Net.Interfaces.IRequest.Method">
<summary> <summary>
Content length Method
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.GetRequestStream"> <member name="P:CryptoExchange.Net.Interfaces.IRequest.Uri">
<summary> <summary>
Get the request stream Uri
</summary> </summary>
<returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.GetResponse"> <member name="M:CryptoExchange.Net.Interfaces.IRequest.SetContent(System.Byte[])">
<summary> <summary>
Get the response object Set byte content
</summary> </summary>
<param name="data"></param>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.SetContent(System.String,System.String)">
<summary>
Set string content
</summary>
<param name="data"></param>
<param name="contentType"></param>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.GetResponse(System.Threading.CancellationToken)">
<summary>
Get the response
</summary>
<param name="cancellationToken"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="T:CryptoExchange.Net.Interfaces.IRequestFactory"> <member name="T:CryptoExchange.Net.Interfaces.IRequestFactory">
@ -584,13 +576,21 @@
Request factory interface Request factory interface
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IRequestFactory.Create(System.String)"> <member name="M:CryptoExchange.Net.Interfaces.IRequestFactory.Create(System.Net.Http.HttpMethod,System.String)">
<summary> <summary>
Create a request for an uri Create a request for an uri
</summary> </summary>
<param name="method"></param>
<param name="uri"></param> <param name="uri"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IRequestFactory.Configure(System.TimeSpan,CryptoExchange.Net.Objects.ApiProxy)">
<summary>
Configure the requests created by this factory
</summary>
<param name="requestTimeout">Request timeout to use</param>
<param name="proxy">Proxy settings to use</param>
</member>
<member name="T:CryptoExchange.Net.Interfaces.IResponse"> <member name="T:CryptoExchange.Net.Interfaces.IResponse">
<summary> <summary>
Response object interface Response object interface
@ -601,18 +601,22 @@
The response status code The response status code
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IResponse.IsSuccessStatusCode">
<summary>
Whether the status code indicates a success status
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IResponse.ResponseHeaders">
<summary>
The response headers
</summary>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IResponse.GetResponseStream"> <member name="M:CryptoExchange.Net.Interfaces.IResponse.GetResponseStream">
<summary> <summary>
Get the response stream Get the response stream
</summary> </summary>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Interfaces.IResponse.GetResponseHeaders">
<summary>
Get the response headers
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IResponse.Close"> <member name="M:CryptoExchange.Net.Interfaces.IResponse.Close">
<summary> <summary>
Close the response Close the response
@ -883,16 +887,6 @@
Is open Is open
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.PingConnection">
<summary>
Should ping connecting
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.PingInterval">
<summary>
Interval of pinging
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.SSLProtocols"> <member name="P:CryptoExchange.Net.Interfaces.IWebsocket.SSLProtocols">
<summary> <summary>
Supported ssl protocols Supported ssl protocols
@ -1090,6 +1084,16 @@
<param name="login">The proxy login</param> <param name="login">The proxy login</param>
<param name="password">The proxy password</param> <param name="password">The proxy password</param>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.Security.SecureString)">
<inheritdoc />
<summary>
Create new settings for a proxy
</summary>
<param name="host">The proxy hostname/ip</param>
<param name="port">The proxy port</param>
<param name="login">The proxy login</param>
<param name="password">The proxy password</param>
</member>
<member name="T:CryptoExchange.Net.Objects.ByteOrderComparer"> <member name="T:CryptoExchange.Net.Objects.ByteOrderComparer">
<summary> <summary>
Comparer for byte order Comparer for byte order
@ -1147,7 +1151,7 @@
The response headers The response headers
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.#ctor(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Tuple{System.String,System.String}},`0,CryptoExchange.Net.Objects.Error)"> <member name="M:CryptoExchange.Net.Objects.WebCallResult`1.#ctor(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.Collections.Generic.IEnumerable{System.String}}},`0,CryptoExchange.Net.Objects.Error)">
<summary> <summary>
ctor ctor
</summary> </summary>
@ -1163,7 +1167,7 @@
<param name="error"></param> <param name="error"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.WebCallResult`1.CreateErrorResult(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Tuple{System.String,System.String}},CryptoExchange.Net.Objects.Error)"> <member name="M:CryptoExchange.Net.Objects.WebCallResult`1.CreateErrorResult(System.Nullable{System.Net.HttpStatusCode},System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.Collections.Generic.IEnumerable{System.String}}},CryptoExchange.Net.Objects.Error)">
<summary> <summary>
Create an error result Create an error result
</summary> </summary>
@ -1177,26 +1181,6 @@
Constants Constants
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.Objects.Constants.GetMethod">
<summary>
GET Http method
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.Constants.PostMethod">
<summary>
POST Http method
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.Constants.DeleteMethod">
<summary>
DELETE Http method
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.Constants.PutMethod">
<summary>
PUT Http method
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.Constants.JsonContentHeader"> <member name="F:CryptoExchange.Net.Objects.Constants.JsonContentHeader">
<summary> <summary>
Json content type header Json content type header
@ -1428,6 +1412,16 @@
</summary> </summary>
<param name="message"></param> <param name="message"></param>
</member> </member>
<member name="T:CryptoExchange.Net.Objects.CancellationRequestedError">
<summary>
Cancellation requested
</summary>
</member>
<member name="M:CryptoExchange.Net.Objects.CancellationRequestedError.#ctor">
<summary>
ctor
</summary>
</member>
<member name="T:CryptoExchange.Net.Objects.BaseOptions"> <member name="T:CryptoExchange.Net.Objects.BaseOptions">
<summary> <summary>
Base options Base options
@ -1443,6 +1437,9 @@
The log writers The log writers
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.BaseOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Objects.OrderBookOptions"> <member name="T:CryptoExchange.Net.Objects.OrderBookOptions">
<summary> <summary>
Base for order book options Base for order book options
@ -1464,26 +1461,38 @@
<param name="name">The name of the order book implementation</param> <param name="name">The name of the order book implementation</param>
<param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param> <param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Objects.ClientOptions"> <member name="T:CryptoExchange.Net.Objects.ClientOptions">
<summary> <summary>
Base client options Base client options
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.ApiCredentials">
<summary>
The api credentials
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.BaseAddress"> <member name="P:CryptoExchange.Net.Objects.ClientOptions.BaseAddress">
<summary> <summary>
The base address of the client The base address of the client
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.ApiCredentials">
<summary>
The api credentials
</summary>
</member>
<member name="P:CryptoExchange.Net.Objects.ClientOptions.Proxy"> <member name="P:CryptoExchange.Net.Objects.ClientOptions.Proxy">
<summary> <summary>
Proxy to use Proxy to use
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.#ctor(System.String)">
<summary>
ctor
</summary>
<param name="baseAddress"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.ClientOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Objects.RestClientOptions"> <member name="T:CryptoExchange.Net.Objects.RestClientOptions">
<summary> <summary>
Base for rest client options Base for rest client options
@ -1504,6 +1513,12 @@
The time the server has to respond to a request before timing out The time the server has to respond to a request before timing out
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.#ctor(System.String)">
<summary>
ctor
</summary>
<param name="baseAddress"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.Copy``1"> <member name="M:CryptoExchange.Net.Objects.RestClientOptions.Copy``1">
<summary> <summary>
Create a copy of the options Create a copy of the options
@ -1511,6 +1526,9 @@
<typeparam name="T"></typeparam> <typeparam name="T"></typeparam>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.RestClientOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.Objects.SocketClientOptions"> <member name="T:CryptoExchange.Net.Objects.SocketClientOptions">
<summary> <summary>
Base for socket client options Base for socket client options
@ -1542,6 +1560,12 @@
Setting this to a higher number increases subscription speed, but having more subscriptions on a single connection will also increase the amount of traffic on that single connection. Setting this to a higher number increases subscription speed, but having more subscriptions on a single connection will also increase the amount of traffic on that single connection.
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.#ctor(System.String)">
<summary>
ctor
</summary>
<param name="baseAddress"></param>
</member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.Copy``1"> <member name="M:CryptoExchange.Net.Objects.SocketClientOptions.Copy``1">
<summary> <summary>
Create a copy of the options Create a copy of the options
@ -1549,6 +1573,9 @@
<typeparam name="T"></typeparam> <typeparam name="T"></typeparam>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
<inheritdoc />
</member>
<member name="T:CryptoExchange.Net.OrderBook.OrderBookEntry"> <member name="T:CryptoExchange.Net.OrderBook.OrderBookEntry">
<summary> <summary>
Order book entry Order book entry
@ -1876,43 +1903,35 @@
Request object Request object
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Request.#ctor(System.Net.WebRequest)"> <member name="M:CryptoExchange.Net.Requests.Request.#ctor(System.Net.Http.HttpRequestMessage,System.Net.Http.HttpClient)">
<summary> <summary>
Create request object for web request Create request object for web request
</summary> </summary>
<param name="request"></param> <param name="request"></param>
<param name="client"></param>
</member> </member>
<member name="P:CryptoExchange.Net.Requests.Request.Headers"> <member name="P:CryptoExchange.Net.Requests.Request.Headers">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:CryptoExchange.Net.Requests.Request.ContentType">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Requests.Request.Content"> <member name="P:CryptoExchange.Net.Requests.Request.Content">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:CryptoExchange.Net.Requests.Request.Accept"> <member name="P:CryptoExchange.Net.Requests.Request.Accept">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:CryptoExchange.Net.Requests.Request.ContentLength">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Requests.Request.Method"> <member name="P:CryptoExchange.Net.Requests.Request.Method">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="P:CryptoExchange.Net.Requests.Request.Timeout">
<inheritdoc />
</member>
<member name="P:CryptoExchange.Net.Requests.Request.Uri"> <member name="P:CryptoExchange.Net.Requests.Request.Uri">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Request.SetProxy(System.String,System.Int32,System.String,System.String)"> <member name="M:CryptoExchange.Net.Requests.Request.SetContent(System.String,System.String)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Request.GetRequestStream"> <member name="M:CryptoExchange.Net.Requests.Request.SetContent(System.Byte[])">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Request.GetResponse"> <member name="M:CryptoExchange.Net.Requests.Request.GetResponse(System.Threading.CancellationToken)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="T:CryptoExchange.Net.Requests.RequestFactory"> <member name="T:CryptoExchange.Net.Requests.RequestFactory">
@ -1920,7 +1939,10 @@
WebRequest factory WebRequest factory
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Requests.RequestFactory.Create(System.String)"> <member name="M:CryptoExchange.Net.Requests.RequestFactory.Configure(System.TimeSpan,CryptoExchange.Net.Objects.ApiProxy)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Requests.RequestFactory.Create(System.Net.Http.HttpMethod,System.String)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="T:CryptoExchange.Net.Requests.Response"> <member name="T:CryptoExchange.Net.Requests.Response">
@ -1931,16 +1953,19 @@
<member name="P:CryptoExchange.Net.Requests.Response.StatusCode"> <member name="P:CryptoExchange.Net.Requests.Response.StatusCode">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Response.#ctor(System.Net.HttpWebResponse)"> <member name="P:CryptoExchange.Net.Requests.Response.IsSuccessStatusCode">
<summary>
Create response for http web response
</summary>
<param name="response"></param>
</member>
<member name="M:CryptoExchange.Net.Requests.Response.GetResponseStream">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Response.GetResponseHeaders"> <member name="P:CryptoExchange.Net.Requests.Response.ResponseHeaders">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Requests.Response.#ctor(System.Net.Http.HttpResponseMessage)">
<summary>
Create response for a http response message
</summary>
<param name="response">The actual response</param>
</member>
<member name="M:CryptoExchange.Net.Requests.Response.GetResponseStream">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Response.Close"> <member name="M:CryptoExchange.Net.Requests.Response.Close">
@ -1998,12 +2023,6 @@
<param name="exchangeOptions"></param> <param name="exchangeOptions"></param>
<param name="authenticationProvider"></param> <param name="authenticationProvider"></param>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.Configure(CryptoExchange.Net.Objects.RestClientOptions)">
<summary>
Configure the client using the provided options
</summary>
<param name="exchangeOptions">Options</param>
</member>
<member name="M:CryptoExchange.Net.RestClient.AddRateLimiter(CryptoExchange.Net.Interfaces.IRateLimiter)"> <member name="M:CryptoExchange.Net.RestClient.AddRateLimiter(CryptoExchange.Net.Interfaces.IRateLimiter)">
<summary> <summary>
Adds a rate limiter to the client. There are 2 choices, the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterTotal"/> and the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterPerEndpoint"/>. Adds a rate limiter to the client. There are 2 choices, the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterTotal"/> and the <see cref="T:CryptoExchange.Net.RateLimiter.RateLimiterPerEndpoint"/>.
@ -2027,26 +2046,28 @@
</summary> </summary>
<returns>The roundtrip time of the ping request</returns> <returns>The roundtrip time of the ping request</returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.ExecuteRequest``1(System.Uri,System.String,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean)"> <member name="M:CryptoExchange.Net.RestClient.SendRequest``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean)">
<summary> <summary>
Execute a request Execute a request
</summary> </summary>
<typeparam name="T">The expected result type</typeparam> <typeparam name="T">The expected result type</typeparam>
<param name="uri">The uri to send the request to</param> <param name="uri">The uri to send the request to</param>
<param name="method">The method of the request</param> <param name="method">The method of the request</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="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param> <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.IsErrorResponse(Newtonsoft.Json.Linq.JToken)"> <member name="M:CryptoExchange.Net.RestClient.GetResponse``1(CryptoExchange.Net.Interfaces.IRequest,System.Threading.CancellationToken)">
<summary> <summary>
Can be overridden to indicate if a response is an error response Executes the request and returns the string result
</summary> </summary>
<param name="data">The received data</param> <param name="request">The request object to execute</param>
<returns>True if error response</returns> <param name="cancellationToken">Cancellation token</param>
<returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.String,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)"> <member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
<summary> <summary>
Creates a request object Creates a request object
</summary> </summary>
@ -2056,26 +2077,13 @@
<param name="signed">Whether or not the request should be authenticated</param> <param name="signed">Whether or not the request should be authenticated</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.String)"> <member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object},System.String)">
<summary>
Writes the string data of the parameters to the request body stream
</summary>
<param name="request"></param>
<param name="stringData"></param>
</member>
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object})">
<summary> <summary>
Writes the parameters of the request to the request object, either in the query string or the request body Writes the parameters of the request to the request object, either in the query string or the request body
</summary> </summary>
<param name="request"></param> <param name="request"></param>
<param name="parameters"></param> <param name="parameters"></param>
</member> <param name="contentType"></param>
<member name="M:CryptoExchange.Net.RestClient.ExecuteRequest(CryptoExchange.Net.Interfaces.IRequest)">
<summary>
Executes the request and returns the string result
</summary>
<param name="request">The request object to execute</param>
<returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.ParseErrorResponse(Newtonsoft.Json.Linq.JToken)"> <member name="M:CryptoExchange.Net.RestClient.ParseErrorResponse(Newtonsoft.Json.Linq.JToken)">
<summary> <summary>
@ -2166,12 +2174,6 @@
<param name="exchangeOptions">Client options</param> <param name="exchangeOptions">Client options</param>
<param name="authenticationProvider">Authentication provider</param> <param name="authenticationProvider">Authentication provider</param>
</member> </member>
<member name="M:CryptoExchange.Net.SocketClient.Configure(CryptoExchange.Net.Objects.SocketClientOptions)">
<summary>
Configure the client using the provided options
</summary>
<param name="exchangeOptions">Options</param>
</member>
<member name="M:CryptoExchange.Net.SocketClient.SetDataInterpreter(System.Func{System.Byte[],System.String},System.Func{System.String,System.String})"> <member name="M:CryptoExchange.Net.SocketClient.SetDataInterpreter(System.Func{System.Byte[],System.String},System.Func{System.String,System.String})">
<summary> <summary>
Set a function to interpret the data, used when the data is received as bytes instead of a string Set a function to interpret the data, used when the data is received as bytes instead of a string
@ -2536,12 +2538,19 @@
If the subscription has been confirmed If the subscription has been confirmed
</summary> </summary>
</member> </member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.#ctor(System.String,System.Object,System.Boolean,System.Action{CryptoExchange.Net.Sockets.SocketConnection,Newtonsoft.Json.Linq.JToken})"> <member name="M:CryptoExchange.Net.Sockets.SocketSubscription.#ctor(System.Object,System.Boolean,System.Action{CryptoExchange.Net.Sockets.SocketConnection,Newtonsoft.Json.Linq.JToken})">
<summary>
ctor
</summary>
<param name="request"></param>
<param name="userSubscription"></param>
<param name="dataHandler"></param>
</member>
<member name="M:CryptoExchange.Net.Sockets.SocketSubscription.#ctor(System.String,System.Boolean,System.Action{CryptoExchange.Net.Sockets.SocketConnection,Newtonsoft.Json.Linq.JToken})">
<summary> <summary>
ctor ctor
</summary> </summary>
<param name="identifier"></param> <param name="identifier"></param>
<param name="request"></param>
<param name="userSubscription"></param> <param name="userSubscription"></param>
<param name="dataHandler"></param> <param name="dataHandler"></param>
</member> </member>

View File

@ -47,7 +47,7 @@ namespace CryptoExchange.Net
/// <param name="parameters"></param> /// <param name="parameters"></param>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="value"></param> /// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object value) public static void AddOptionalParameter(this Dictionary<string, object> parameters, string key, object? value)
{ {
if(value != null) if(value != null)
parameters.Add(key, value); parameters.Add(key, value);
@ -59,7 +59,7 @@ namespace CryptoExchange.Net
/// <param name="parameters"></param> /// <param name="parameters"></param>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="value"></param> /// <param name="value"></param>
public static void AddOptionalParameter(this Dictionary<string, string> parameters, string key, string value) public static void AddOptionalParameter(this Dictionary<string, string> parameters, string key, string? value)
{ {
if (value != null) if (value != null)
parameters.Add(key, value); parameters.Add(key, value);
@ -127,20 +127,17 @@ namespace CryptoExchange.Net
} }
/// <summary> /// <summary>
/// Header collection to IEnumerable /// Create a secure string from a string
/// </summary> /// </summary>
/// <param name="headers"></param> /// <param name="source"></param>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<Tuple<string, string>> ToIEnumerable(this WebHeaderCollection headers) internal static SecureString ToSecureString(this string source)
{ {
if (headers == null) var secureString = new SecureString();
return null; foreach (var c in source)
secureString.AppendChar(c);
return Enumerable secureString.MakeReadOnly();
.Range(0, headers.Count) return secureString;
.SelectMany(i => headers.GetValues(i)
.Select(v => Tuple.Create(headers.GetKey(i), v))
);
} }
/// <summary> /// <summary>
@ -152,7 +149,7 @@ namespace CryptoExchange.Net
/// <returns></returns> /// <returns></returns>
public static async Task<bool> WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken) public static async Task<bool> WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken)
{ {
RegisteredWaitHandle registeredHandle = null; RegisteredWaitHandle? registeredHandle = null;
CancellationTokenRegistration tokenRegistration = default; CancellationTokenRegistration tokenRegistration = default;
try try
{ {
@ -192,7 +189,7 @@ namespace CryptoExchange.Net
/// <param name="stringData"></param> /// <param name="stringData"></param>
/// <param name="log"></param> /// <param name="log"></param>
/// <returns></returns> /// <returns></returns>
public static JToken ToJToken(this string stringData, Log log = null) public static JToken? ToJToken(this string stringData, Log? log = null)
{ {
if (string.IsNullOrEmpty(stringData)) if (string.IsNullOrEmpty(stringData))
return null; return null;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.IO; using System.Net.Http;
using System.Net; using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
@ -11,56 +12,41 @@ namespace CryptoExchange.Net.Interfaces
public interface IRequest public interface IRequest
{ {
/// <summary> /// <summary>
/// The uri of the request /// Accept header
/// </summary>
string Accept { set; }
/// <summary>
/// Content
/// </summary>
string? Content { get; }
/// <summary>
/// Headers
/// </summary>
HttpRequestHeaders Headers { get; }
/// <summary>
/// Method
/// </summary>
HttpMethod Method { get; set; }
/// <summary>
/// Uri
/// </summary> /// </summary>
Uri Uri { get; } Uri Uri { get; }
/// <summary> /// <summary>
/// The headers of the request /// Set byte content
/// </summary> /// </summary>
WebHeaderCollection Headers { get; set; } /// <param name="data"></param>
void SetContent(byte[] data);
/// <summary> /// <summary>
/// The method of the request /// Set string content
/// </summary> /// </summary>
string Method { get; set; } /// <param name="data"></param>
/// <param name="contentType"></param>
void SetContent(string data, string contentType);
/// <summary> /// <summary>
/// The timeout of the request /// Get the response
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// Set a proxy
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
/// <param name="login"></param>
/// <param name="password"></param>
void SetProxy(string host, int port, string login, string password);
/// <summary>
/// Content type
/// </summary>
string ContentType { get; set; }
/// <summary>
/// String content
/// </summary>
string Content { get; set; }
/// <summary>
/// Accept
/// </summary>
string Accept { get; set; }
/// <summary>
/// Content length
/// </summary>
long ContentLength { get; set; }
/// <summary>
/// Get the request stream
/// </summary> /// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<Stream> GetRequestStream(); Task<IResponse> GetResponse(CancellationToken cancellationToken);
/// <summary>
/// Get the response object
/// </summary>
/// <returns></returns>
Task<IResponse> GetResponse();
} }
} }

View File

@ -1,4 +1,8 @@
namespace CryptoExchange.Net.Interfaces using CryptoExchange.Net.Objects;
using System;
using System.Net.Http;
namespace CryptoExchange.Net.Interfaces
{ {
/// <summary> /// <summary>
/// Request factory interface /// Request factory interface
@ -8,8 +12,16 @@
/// <summary> /// <summary>
/// Create a request for an uri /// Create a request for an uri
/// </summary> /// </summary>
/// <param name="method"></param>
/// <param name="uri"></param> /// <param name="uri"></param>
/// <returns></returns> /// <returns></returns>
IRequest Create(string uri); IRequest Create(HttpMethod method, string uri);
/// <summary>
/// Configure the requests created by this factory
/// </summary>
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="proxy">Proxy settings to use</param>
void Configure(TimeSpan requestTimeout, ApiProxy? proxy);
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces namespace CryptoExchange.Net.Interfaces
{ {
@ -14,16 +14,23 @@ namespace CryptoExchange.Net.Interfaces
/// The response status code /// The response status code
/// </summary> /// </summary>
HttpStatusCode StatusCode { get; } HttpStatusCode StatusCode { get; }
/// <summary>
/// Whether the status code indicates a success status
/// </summary>
bool IsSuccessStatusCode { get; }
/// <summary>
/// The response headers
/// </summary>
IEnumerable<KeyValuePair<string, IEnumerable<string>>> ResponseHeaders { get; }
/// <summary> /// <summary>
/// Get the response stream /// Get the response stream
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Stream GetResponseStream(); Task<Stream> GetResponseStream();
/// <summary>
/// Get the response headers
/// </summary>
/// <returns></returns>
IEnumerable<Tuple<string, string>> GetResponseHeaders();
/// <summary> /// <summary>
/// Close the response /// Close the response
/// </summary> /// </summary>

View File

@ -34,7 +34,7 @@ namespace CryptoExchange.Net.Interfaces
/// <summary> /// <summary>
/// Origin /// Origin
/// </summary> /// </summary>
string Origin { get; set; } string? Origin { get; set; }
/// <summary> /// <summary>
/// Reconnecting /// Reconnecting
/// </summary> /// </summary>
@ -42,11 +42,11 @@ namespace CryptoExchange.Net.Interfaces
/// <summary> /// <summary>
/// Handler for byte data /// Handler for byte data
/// </summary> /// </summary>
Func<byte[], string> DataInterpreterBytes { get; set; } Func<byte[], string>? DataInterpreterBytes { get; set; }
/// <summary> /// <summary>
/// Handler for string data /// Handler for string data
/// </summary> /// </summary>
Func<string, string> DataInterpreterString { get; set; } Func<string, string>? DataInterpreterString { get; set; }
/// <summary> /// <summary>
/// Socket url /// Socket url
/// </summary> /// </summary>
@ -64,14 +64,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
bool IsOpen { get; } bool IsOpen { get; }
/// <summary> /// <summary>
/// Should ping connecting
/// </summary>
bool PingConnection { get; set; }
/// <summary>
/// Interval of pinging
/// </summary>
TimeSpan PingInterval { get; set; }
/// <summary>
/// Supported ssl protocols /// Supported ssl protocols
/// </summary> /// </summary>
SslProtocols SSLProtocols { get; set; } SslProtocols SSLProtocols { get; set; }

View File

@ -49,11 +49,8 @@ namespace CryptoExchange.Net.Logging
/// <param name="disposing"></param> /// <param name="disposing"></param>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
lock (writeLock) lock (writeLock)
{ logWriter.Close();
logWriter.Close();
logWriter = null;
}
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Security;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
@ -19,25 +20,20 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// The login of the proxy /// The login of the proxy
/// </summary> /// </summary>
public string Login { get; } public string? Login { get; }
/// <summary> /// <summary>
/// The password of the proxy /// The password of the proxy
/// </summary> /// </summary>
public string Password { get; } public SecureString? Password { get; }
/// <summary> /// <summary>
/// Create new settings for a proxy /// Create new settings for a proxy
/// </summary> /// </summary>
/// <param name="host">The proxy hostname/ip</param> /// <param name="host">The proxy hostname/ip</param>
/// <param name="port">The proxy port</param> /// <param name="port">The proxy port</param>
public ApiProxy(string host, int port) public ApiProxy(string host, int port): this(host, port, null, (SecureString?)null)
{ {
if(string.IsNullOrEmpty(host) || port <= 0)
throw new ArgumentException("Proxy host or port not filled");
Host = host;
Port = port;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -48,11 +44,28 @@ namespace CryptoExchange.Net.Objects
/// <param name="port">The proxy port</param> /// <param name="port">The proxy port</param>
/// <param name="login">The proxy login</param> /// <param name="login">The proxy login</param>
/// <param name="password">The proxy password</param> /// <param name="password">The proxy password</param>
public ApiProxy(string host, int port, string login, string password) : this(host, port) public ApiProxy(string host, int port, string? login, string? password) : this(host, port, login, password?.ToSecureString())
{ {
if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password)) }
throw new ArgumentException("Proxy login or password not filled");
/// <inheritdoc />
/// <summary>
/// Create new settings for a proxy
/// </summary>
/// <param name="host">The proxy hostname/ip</param>
/// <param name="port">The proxy port</param>
/// <param name="login">The proxy login</param>
/// <param name="password">The proxy password</param>
public ApiProxy(string host, int port, string? login, SecureString? password)
{
if (string.IsNullOrEmpty(login))
throw new ArgumentException("Proxy login not provided");
if (!host.StartsWith("http"))
throw new ArgumentException("Proxy host should start with either http:// or https://");
Host = host;
Port = port;
Login = login; Login = login;
Password = password; Password = password;
} }

View File

@ -1,5 +1,5 @@
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Diagnostics.CodeAnalysis;
using System.Net; using System.Net;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
@ -17,7 +17,7 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// An error if the call didn't succeed /// An error if the call didn't succeed
/// </summary> /// </summary>
public Error Error { get; internal set; } public Error? Error { get; internal set; }
/// <summary> /// <summary>
/// Whether the call was successful /// Whether the call was successful
/// </summary> /// </summary>
@ -28,11 +28,20 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="error"></param> /// <param name="error"></param>
public CallResult(T data, Error error) public CallResult([AllowNull]T data, Error? error)
{ {
Data = data; Data = data;
Error = error; Error = error;
} }
/// <summary>
/// Overwrite bool check so we can use if(callResult) instead of if(callResult.Success)
/// </summary>
/// <param name="obj"></param>
public static implicit operator bool(CallResult<T> obj)
{
return !ReferenceEquals(obj, null) && obj.Success;
}
} }
/// <summary> /// <summary>
@ -49,7 +58,7 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// The response headers /// The response headers
/// </summary> /// </summary>
public IEnumerable<Tuple<string, string>> ResponseHeaders { get; set; } public IEnumerable<KeyValuePair<string, IEnumerable<string>>>? ResponseHeaders { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
@ -58,7 +67,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="responseHeaders"></param> /// <param name="responseHeaders"></param>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="error"></param> /// <param name="error"></param>
public WebCallResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, T data, Error error): base(data, error) public WebCallResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? responseHeaders, [AllowNull] T data, Error? error): base(data, error)
{ {
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseStatusCode = code; ResponseStatusCode = code;
@ -81,7 +90,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="responseHeaders"></param> /// <param name="responseHeaders"></param>
/// <param name="error"></param> /// <param name="error"></param>
/// <returns></returns> /// <returns></returns>
public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<Tuple<string, string>> responseHeaders, Error error) public static WebCallResult<T> CreateErrorResult(HttpStatusCode? code, IEnumerable<KeyValuePair<string, IEnumerable<string>>> responseHeaders, Error error)
{ {
return new WebCallResult<T>(code, responseHeaders, default, error); return new WebCallResult<T>(code, responseHeaders, default, error);
} }

View File

@ -5,23 +5,6 @@
/// </summary> /// </summary>
public class Constants public class Constants
{ {
/// <summary>
/// GET Http method
/// </summary>
public const string GetMethod = "GET";
/// <summary>
/// POST Http method
/// </summary>
public const string PostMethod = "POST";
/// <summary>
/// DELETE Http method
/// </summary>
public const string DeleteMethod = "DELETE";
/// <summary>
/// PUT Http method
/// </summary>
public const string PutMethod = "PUT";
/// <summary> /// <summary>
/// Json content type header /// Json content type header
/// </summary> /// </summary>

View File

@ -137,4 +137,15 @@
/// <param name="message"></param> /// <param name="message"></param>
public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { } public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { }
} }
/// <summary>
/// Cancellation requested
/// </summary>
public class CancellationRequestedError : Error
{
/// <summary>
/// ctor
/// </summary>
public CancellationRequestedError() : base(9, "Cancellation requested") { }
}
} }

View File

@ -21,6 +21,12 @@ namespace CryptoExchange.Net.Objects
/// The log writers /// The log writers
/// </summary> /// </summary>
public List<TextWriter> LogWriters { get; set; } = new List<TextWriter> { new DebugTextWriter() }; public List<TextWriter> LogWriters { get; set; } = new List<TextWriter> { new DebugTextWriter() };
/// <inheritdoc />
public override string ToString()
{
return $"LogVerbosity: {LogVerbosity}, Writers: {LogWriters.Count}";
}
} }
/// <summary> /// <summary>
@ -47,6 +53,12 @@ namespace CryptoExchange.Net.Objects
OrderBookName = name; OrderBookName = name;
SequenceNumbersAreConsecutive = sequencesAreConsecutive; SequenceNumbersAreConsecutive = sequencesAreConsecutive;
} }
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}, OrderBookName: {OrderBookName}, SequenceNumbersAreConsequtive: {SequenceNumbersAreConsecutive}";
}
} }
/// <summary> /// <summary>
@ -54,21 +66,36 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public class ClientOptions : BaseOptions public class ClientOptions : BaseOptions
{ {
/// <summary>
/// The api credentials
/// </summary>
public ApiCredentials ApiCredentials { get; set; }
/// <summary> /// <summary>
/// The base address of the client /// The base address of the client
/// </summary> /// </summary>
public string BaseAddress { get; set; } public string BaseAddress { get; set; }
/// <summary>
/// The api credentials
/// </summary>
public ApiCredentials? ApiCredentials { get; set; }
/// <summary> /// <summary>
/// Proxy to use /// Proxy to use
/// </summary> /// </summary>
public ApiProxy Proxy { get; set; } public ApiProxy? Proxy { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress"></param>
public ClientOptions(string baseAddress)
{
BaseAddress = baseAddress;
}
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}, Credentials: {(ApiCredentials == null ? "-": "Set")}, BaseAddress: {BaseAddress}, Proxy: {(Proxy == null? "-": Proxy.Host)}";
}
} }
/// <summary> /// <summary>
@ -91,6 +118,14 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30); public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress"></param>
public RestClientOptions(string baseAddress): base(baseAddress)
{
}
/// <summary> /// <summary>
/// Create a copy of the options /// Create a copy of the options
/// </summary> /// </summary>
@ -110,10 +145,16 @@ namespace CryptoExchange.Net.Objects
}; };
if (ApiCredentials != null) if (ApiCredentials != null)
copy.ApiCredentials = new ApiCredentials(ApiCredentials.Key.GetString(), ApiCredentials.Secret.GetString()); copy.ApiCredentials = ApiCredentials.Copy();
return copy; return copy;
} }
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}, RateLimitters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, RequestTimeout: {RequestTimeout.ToString("c")}";
}
} }
/// <summary> /// <summary>
@ -146,6 +187,14 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public int? SocketSubscriptionsCombineTarget { get; set; } public int? SocketSubscriptionsCombineTarget { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="baseAddress"></param>
public SocketClientOptions(string baseAddress) : base(baseAddress)
{
}
/// <summary> /// <summary>
/// Create a copy of the options /// Create a copy of the options
/// </summary> /// </summary>
@ -166,9 +215,15 @@ namespace CryptoExchange.Net.Objects
}; };
if (ApiCredentials != null) if (ApiCredentials != null)
copy.ApiCredentials = new ApiCredentials(ApiCredentials.Key.GetString(), ApiCredentials.Secret.GetString()); copy.ApiCredentials = ApiCredentials.Copy();
return copy; return copy;
} }
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, SocketResponseTimeout: {SocketResponseTimeout.ToString("c")}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
}
} }
} }

View File

@ -31,7 +31,7 @@ namespace CryptoExchange.Net.OrderBook
protected SortedList<decimal, ISymbolOrderBookEntry> bids; protected SortedList<decimal, ISymbolOrderBookEntry> bids;
private OrderBookStatus status; private OrderBookStatus status;
private UpdateSubscription subscription; private UpdateSubscription? subscription;
private readonly bool sequencesAreConsecutive; private readonly bool sequencesAreConsecutive;
private readonly string id; private readonly string id;
/// <summary> /// <summary>
@ -71,11 +71,11 @@ namespace CryptoExchange.Net.OrderBook
/// <summary> /// <summary>
/// Event when the state changes /// Event when the state changes
/// </summary> /// </summary>
public event Action<OrderBookStatus, OrderBookStatus> OnStatusChange; public event Action<OrderBookStatus, OrderBookStatus>? OnStatusChange;
/// <summary> /// <summary>
/// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets /// Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets
/// </summary> /// </summary>
public event Action OnOrderBookUpdate; public event Action? OnOrderBookUpdate;
/// <summary> /// <summary>
/// Timestamp of the last update /// Timestamp of the last update
/// </summary> /// </summary>
@ -145,6 +145,12 @@ namespace CryptoExchange.Net.OrderBook
/// <param name="options"></param> /// <param name="options"></param>
protected SymbolOrderBook(string symbol, OrderBookOptions options) protected SymbolOrderBook(string symbol, OrderBookOptions options)
{ {
if (symbol == null)
throw new ArgumentNullException("symbol");
if (options == null)
throw new ArgumentNullException("options");
id = options.OrderBookName; id = options.OrderBookName;
processBuffer = new List<ProcessBufferEntry>(); processBuffer = new List<ProcessBufferEntry>();
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive; sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
@ -173,7 +179,7 @@ namespace CryptoExchange.Net.OrderBook
{ {
Status = OrderBookStatus.Connecting; Status = OrderBookStatus.Connecting;
var startResult = await DoStart().ConfigureAwait(false); var startResult = await DoStart().ConfigureAwait(false);
if (!startResult.Success) if (!startResult)
return new CallResult<bool>(false, startResult.Error); return new CallResult<bool>(false, startResult.Error);
subscription = startResult.Data; subscription = startResult.Data;
@ -202,7 +208,7 @@ namespace CryptoExchange.Net.OrderBook
return; return;
var resyncResult = DoResync().Result; var resyncResult = DoResync().Result;
success = resyncResult.Success; success = resyncResult;
} }
log.Write(LogVerbosity.Info, $"{id} order book {Symbol} successfully resynchronized"); log.Write(LogVerbosity.Info, $"{id} order book {Symbol} successfully resynchronized");
@ -222,7 +228,8 @@ namespace CryptoExchange.Net.OrderBook
public async Task StopAsync() public async Task StopAsync()
{ {
Status = OrderBookStatus.Disconnected; Status = OrderBookStatus.Disconnected;
await subscription.Close().ConfigureAwait(false); if(subscription != null)
await subscription.Close().ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -303,7 +310,7 @@ namespace CryptoExchange.Net.OrderBook
{ {
// Out of sync // Out of sync
log.Write(LogVerbosity.Warning, $"{id} order book {Symbol} out of sync, reconnecting"); log.Write(LogVerbosity.Warning, $"{id} order book {Symbol} out of sync, reconnecting");
subscription.Reconnect().Wait(); subscription!.Reconnect().Wait();
} }
else else
{ {

View File

@ -32,7 +32,7 @@ namespace CryptoExchange.Net.RateLimiter
/// <inheritdoc /> /// <inheritdoc />
public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour) public CallResult<double> LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour)
{ {
if(client.authProvider?.Credentials == null) if(client.authProvider?.Credentials?.Key == null)
return new CallResult<double>(0, null); return new CallResult<double>(0, null);
var key = client.authProvider.Credentials.Key.GetString(); var key = client.authProvider.Credentials.Key.GetString();

View File

@ -1,6 +1,8 @@
using System; using System;
using System.IO; using System.Net.Http;
using System.Net; using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
@ -9,84 +11,61 @@ namespace CryptoExchange.Net.Requests
/// <summary> /// <summary>
/// Request object /// Request object
/// </summary> /// </summary>
public class Request : IRequest internal class Request : IRequest
{ {
private readonly WebRequest request; private readonly HttpRequestMessage request;
private readonly HttpClient httpClient;
/// <summary> /// <summary>
/// Create request object for web request /// Create request object for web request
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
public Request(WebRequest request) /// <param name="client"></param>
public Request(HttpRequestMessage request, HttpClient client)
{ {
httpClient = client;
this.request = request; this.request = request;
} }
/// <inheritdoc /> /// <inheritdoc />
public WebHeaderCollection Headers public HttpRequestHeaders Headers => request.Headers;
{
get => request.Headers;
set => request.Headers = value;
}
/// <inheritdoc /> /// <inheritdoc />
public string ContentType public string? Content { get; private set; }
{
get => request.ContentType;
set => request.ContentType = value;
}
/// <inheritdoc />
public string Content { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string Accept public string Accept
{ {
get => ((HttpWebRequest)request).Accept; set => request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(value));
set => ((HttpWebRequest)request).Accept = value;
} }
/// <inheritdoc /> /// <inheritdoc />
public long ContentLength public HttpMethod Method
{
get => ((HttpWebRequest)request).ContentLength;
set => ((HttpWebRequest)request).ContentLength = value;
}
/// <inheritdoc />
public string Method
{ {
get => request.Method; get => request.Method;
set => request.Method = value; set => request.Method = value;
} }
/// <inheritdoc />
public TimeSpan Timeout
{
get => TimeSpan.FromMilliseconds(request.Timeout);
set => request.Timeout = (int)Math.Round(value.TotalMilliseconds);
}
/// <inheritdoc /> /// <inheritdoc />
public Uri Uri => request.RequestUri; public Uri Uri => request.RequestUri;
/// <inheritdoc /> /// <inheritdoc />
public void SetProxy(string host, int port, string login, string password) public void SetContent(string data, string contentType)
{ {
request.Proxy = new WebProxy(host, port); Content = data;
if(!string.IsNullOrEmpty(login) && !string.IsNullOrEmpty(password)) request.Proxy.Credentials = new NetworkCredential(login, password); request.Content = new StringContent(data, Encoding.UTF8, contentType);
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Stream> GetRequestStream() public void SetContent(byte[] data)
{ {
return await request.GetRequestStreamAsync().ConfigureAwait(false); request.Content = new ByteArrayContent(data);
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<IResponse> GetResponse() public async Task<IResponse> GetResponse(CancellationToken cancellationToken)
{ {
return new Response((HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false)); return new Response(await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false));
} }
} }
} }

View File

@ -1,5 +1,8 @@
using System.Net; using System;
using System.Net;
using System.Net.Http;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
{ {
@ -8,10 +11,31 @@ namespace CryptoExchange.Net.Requests
/// </summary> /// </summary>
public class RequestFactory : IRequestFactory public class RequestFactory : IRequestFactory
{ {
private HttpClient? httpClient;
/// <inheritdoc /> /// <inheritdoc />
public IRequest Create(string uri) public void Configure(TimeSpan requestTimeout, ApiProxy? proxy)
{ {
return new Request(WebRequest.Create(uri)); HttpMessageHandler handler = new HttpClientHandler()
{
Proxy = proxy == null ? null : new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
}
};
httpClient = new HttpClient(handler);
httpClient.Timeout = requestTimeout;
}
/// <inheritdoc />
public IRequest Create(HttpMethod method, string uri)
{
if (httpClient == null)
throw new InvalidOperationException("Cant create request before configuring http client");
return new Request(new HttpRequestMessage(method, uri), httpClient);
} }
} }
} }

View File

@ -1,7 +1,8 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
@ -9,38 +10,38 @@ namespace CryptoExchange.Net.Requests
/// <summary> /// <summary>
/// HttpWebResponse response object /// HttpWebResponse response object
/// </summary> /// </summary>
public class Response : IResponse internal class Response : IResponse
{ {
private readonly HttpWebResponse response; private readonly HttpResponseMessage response;
/// <inheritdoc /> /// <inheritdoc />
public HttpStatusCode StatusCode => response.StatusCode; public HttpStatusCode StatusCode => response.StatusCode;
/// <inheritdoc />
public bool IsSuccessStatusCode => response.IsSuccessStatusCode;
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> ResponseHeaders => response.Headers;
/// <summary> /// <summary>
/// Create response for http web response /// Create response for a http response message
/// </summary> /// </summary>
/// <param name="response"></param> /// <param name="response">The actual response</param>
public Response(HttpWebResponse response) public Response(HttpResponseMessage response)
{ {
this.response = response; this.response = response;
} }
/// <inheritdoc /> /// <inheritdoc />
public Stream GetResponseStream() public async Task<Stream> GetResponseStream()
{ {
return response.GetResponseStream(); return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
/// <inheritdoc />
public IEnumerable<Tuple<string, string>> GetResponseHeaders()
{
return response.Headers.ToIEnumerable();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Close() public void Close()
{ {
response.Close(); response.Dispose();
} }
} }
} }

View File

@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net.Http;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
@ -29,7 +29,6 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
/// <summary> /// <summary>
/// Where to place post parameters /// Where to place post parameters
/// </summary> /// </summary>
@ -66,18 +65,13 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
/// <param name="exchangeOptions"></param> /// <param name="exchangeOptions"></param>
/// <param name="authenticationProvider"></param> /// <param name="authenticationProvider"></param>
protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider): base(exchangeOptions, authenticationProvider)
{ {
Configure(exchangeOptions); if (exchangeOptions == null)
} throw new ArgumentNullException("Options");
/// <summary>
/// Configure the client using the provided options
/// </summary>
/// <param name="exchangeOptions">Options</param>
protected void Configure(RestClientOptions exchangeOptions)
{
RequestTimeout = exchangeOptions.RequestTimeout; RequestTimeout = exchangeOptions.RequestTimeout;
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy);
RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
var rateLimiters = new List<IRateLimiter>(); var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in exchangeOptions.RateLimiters) foreach (var rateLimiter in exchangeOptions.RateLimiters)
@ -91,6 +85,9 @@ namespace CryptoExchange.Net
/// <param name="limiter">The limiter to add</param> /// <param name="limiter">The limiter to add</param>
public void AddRateLimiter(IRateLimiter limiter) public void AddRateLimiter(IRateLimiter limiter)
{ {
if (limiter == null)
throw new ArgumentNullException("limiter");
var rateLimiters = RateLimiters.ToList(); var rateLimiters = RateLimiters.ToList();
rateLimiters.Add(limiter); rateLimiters.Add(limiter);
RateLimiters = rateLimiters; RateLimiters = rateLimiters;
@ -132,6 +129,10 @@ namespace CryptoExchange.Net
return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + exception.SocketErrorCode }); return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + exception.SocketErrorCode });
return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + e.InnerException.Message }); return new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + e.InnerException.Message });
} }
finally
{
ping.Dispose();
}
return reply.Status == IPStatus.Success ? new CallResult<long>(reply.RoundtripTime, null) : new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + reply.Status }); return reply.Status == IPStatus.Success ? new CallResult<long>(reply.RoundtripTime, null) : new CallResult<long>(0, new CantConnectError { Message = "Ping failed: " + reply.Status });
} }
@ -142,11 +143,13 @@ namespace CryptoExchange.Net
/// <typeparam name="T">The expected result type</typeparam> /// <typeparam name="T">The expected result type</typeparam>
/// <param name="uri">The uri to send the request to</param> /// <param name="uri">The uri to send the request to</param>
/// <param name="method">The method of the request</param> /// <param name="method">The method of the request</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="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param> /// <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<WebCallResult<T>> ExecuteRequest<T>(Uri uri, string method = Constants.GetMethod, Dictionary<string, object> parameters = null, bool signed = false, bool checkResult = true) where T : class protected virtual async Task<WebCallResult<T>> SendRequest<T>(Uri uri, HttpMethod method, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, bool signed = false, bool checkResult = true) where T : class
{ {
log.Write(LogVerbosity.Debug, "Creating request for " + uri); log.Write(LogVerbosity.Debug, "Creating request for " + uri);
if (signed && authProvider == null) if (signed && authProvider == null)
@ -156,13 +159,6 @@ namespace CryptoExchange.Net
} }
var request = ConstructRequest(uri, method, parameters, signed); var request = ConstructRequest(uri, method, parameters, signed);
if (apiProxy != null)
{
log.Write(LogVerbosity.Debug, "Setting proxy");
request.SetProxy(apiProxy.Host, apiProxy.Port, apiProxy.Login, apiProxy.Password);
}
foreach (var limiter in RateLimiters) foreach (var limiter in RateLimiters)
{ {
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour); var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour);
@ -176,37 +172,64 @@ namespace CryptoExchange.Net
log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}"); log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
} }
string paramString = null; string? paramString = null;
if (parameters != null && method == Constants.PostMethod) if (parameters != null && method == HttpMethod.Post)
paramString = "with request body " + request.Content; paramString = " with request body " + request.Content;
log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri} {paramString ?? ""}"); log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null? "": $" via proxy {apiProxy.Host}")}");
var result = await ExecuteRequest(request).ConfigureAwait(false); return await GetResponse<T>(request, cancellationToken).ConfigureAwait(false);
if(!result.Success)
return new WebCallResult<T>(result.ResponseStatusCode, result.ResponseHeaders, null, result.Error);
var jsonResult = ValidateJson(result.Data);
if(!jsonResult.Success)
return new WebCallResult<T>(result.ResponseStatusCode, result.ResponseHeaders, null, jsonResult.Error);
if (IsErrorResponse(jsonResult.Data))
return new WebCallResult<T>(result.ResponseStatusCode, result.ResponseHeaders, null, ParseErrorResponse(jsonResult.Data));
var desResult = Deserialize<T>(jsonResult.Data, checkResult);
if (!desResult.Success)
return new WebCallResult<T>(result.ResponseStatusCode, result.ResponseHeaders, null, desResult.Error);
return new WebCallResult<T>(result.ResponseStatusCode, result.ResponseHeaders, desResult.Data, null);
} }
/// <summary> /// <summary>
/// Can be overridden to indicate if a response is an error response /// Executes the request and returns the string result
/// </summary> /// </summary>
/// <param name="data">The received data</param> /// <param name="request">The request object to execute</param>
/// <returns>True if error response</returns> /// <param name="cancellationToken">Cancellation token</param>
protected virtual bool IsErrorResponse(JToken data) /// <returns></returns>
private async Task<WebCallResult<T>> GetResponse<T>(IRequest request, CancellationToken cancellationToken)
{ {
return false; try
{
TotalRequestsMade++;
var response = await request.GetResponse(cancellationToken).ConfigureAwait(false);
var statusCode = response.StatusCode;
var headers = response.ResponseHeaders;
var responseStream = await response.GetResponseStream().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var desResult = await Deserialize<T>(responseStream).ConfigureAwait(false);
response.Close();
return new WebCallResult<T>(statusCode, headers, desResult.Data, desResult.Error);
}
else
{
using var reader = new StreamReader(responseStream);
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
response.Close();
var parseResult = ValidateJson(data);
return new WebCallResult<T>(statusCode, headers, default, parseResult.Success ? ParseErrorResponse(parseResult.Data) :new ServerError(data));
}
}
catch (HttpRequestException requestException)
{
log.Write(LogVerbosity.Warning, "Request exception: " + requestException.Message);
return new WebCallResult<T>(null, null, default, new ServerError(requestException.Message));
}
catch (TaskCanceledException canceledException)
{
if(canceledException.CancellationToken == cancellationToken)
{
// Cancellation token cancelled
log.Write(LogVerbosity.Warning, "Request cancel requested");
return new WebCallResult<T>(null, null, default, new CancellationRequestedError());
}
else
{
// Request timed out
log.Write(LogVerbosity.Warning, "Request timed out");
return new WebCallResult<T>(null, null, default, new WebError("Request timed out"));
}
}
} }
/// <summary> /// <summary>
@ -217,7 +240,7 @@ namespace CryptoExchange.Net
/// <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>
/// <returns></returns> /// <returns></returns>
protected virtual IRequest ConstructRequest(Uri uri, string method, Dictionary<string, object> parameters, bool signed) protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary<string, object>? parameters, bool signed)
{ {
if (parameters == null) if (parameters == null)
parameters = new Dictionary<string, object>(); parameters = new Dictionary<string, object>();
@ -226,57 +249,44 @@ namespace CryptoExchange.Net
if(authProvider != null) if(authProvider != null)
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed); parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed);
if((method == Constants.GetMethod || method == Constants.DeleteMethod || postParametersPosition == PostParameters.InUri) && parameters?.Any() == true) if((method == HttpMethod.Get || method == HttpMethod.Delete || postParametersPosition == PostParameters.InUri) && parameters?.Any() == true)
uriString += "?" + parameters.CreateParamString(true, arraySerialization); uriString += "?" + parameters.CreateParamString(true, arraySerialization);
var request = RequestFactory.Create(uriString); var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
request.ContentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; var request = RequestFactory.Create(method, uriString);
request.Accept = Constants.JsonContentHeader; request.Accept = Constants.JsonContentHeader;
request.Method = method;
var headers = new Dictionary<string, string>(); var headers = new Dictionary<string, string>();
if (authProvider != null) if (authProvider != null)
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters, signed); headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed);
foreach (var header in headers) foreach (var header in headers)
request.Headers.Add(header.Key, header.Value); request.Headers.Add(header.Key, header.Value);
if ((method == Constants.PostMethod || method == Constants.PutMethod) && postParametersPosition != PostParameters.InUri) if ((method == HttpMethod.Post || method == HttpMethod.Put) && postParametersPosition != PostParameters.InUri)
{ {
if(parameters?.Any() == true) if(parameters?.Any() == true)
WriteParamBody(request, parameters); WriteParamBody(request, parameters, contentType);
else else
WriteParamBody(request, "{}"); request.SetContent("{}", contentType);
} }
return request; return request;
} }
/// <summary>
/// Writes the string data of the parameters to the request body stream
/// </summary>
/// <param name="request"></param>
/// <param name="stringData"></param>
protected virtual void WriteParamBody(IRequest request, string stringData)
{
var data = Encoding.UTF8.GetBytes(stringData);
request.ContentLength = data.Length;
request.Content = stringData;
using (var stream = request.GetRequestStream().Result)
stream.Write(data, 0, data.Length);
}
/// <summary> /// <summary>
/// Writes the parameters of the request to the request object, either in the query string or the request body /// Writes the parameters of the request to the request object, either in the query string or the request body
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="parameters"></param> /// <param name="parameters"></param>
protected virtual void WriteParamBody(IRequest request, Dictionary<string, object> parameters) /// <param name="contentType"></param>
protected virtual void WriteParamBody(IRequest request, Dictionary<string, object> parameters, string contentType)
{ {
if (requestBodyFormat == RequestBodyFormat.Json) if (requestBodyFormat == RequestBodyFormat.Json)
{ {
var stringData = JsonConvert.SerializeObject(parameters.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value)); var stringData = JsonConvert.SerializeObject(parameters.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value));
WriteParamBody(request, stringData); request.SetContent(stringData, contentType);
} }
else if(requestBodyFormat == RequestBodyFormat.FormData) else if(requestBodyFormat == RequestBodyFormat.FormData)
{ {
@ -284,81 +294,9 @@ namespace CryptoExchange.Net
foreach (var kvp in parameters.OrderBy(p => p.Key)) foreach (var kvp in parameters.OrderBy(p => p.Key))
formData.Add(kvp.Key, kvp.Value.ToString()); formData.Add(kvp.Key, kvp.Value.ToString());
var stringData = formData.ToString(); var stringData = formData.ToString();
WriteParamBody(request, stringData); request.SetContent(stringData, contentType);
} }
} }
/// <summary>
/// Executes the request and returns the string result
/// </summary>
/// <param name="request">The request object to execute</param>
/// <returns></returns>
private async Task<WebCallResult<string>> ExecuteRequest(IRequest request)
{
var returnedData = "";
try
{
request.Timeout = RequestTimeout;
TotalRequestsMade++;
var response = await request.GetResponse().ConfigureAwait(false);
using (var reader = new StreamReader(response.GetResponseStream()))
{
returnedData = await reader.ReadToEndAsync().ConfigureAwait(false);
log.Write(LogVerbosity.Debug, "Data returned: " + returnedData);
}
var statusCode = response.StatusCode;
var returnHeaders = response.GetResponseHeaders();
response.Close();
return new WebCallResult<string>(statusCode, returnHeaders, returnedData, null);
}
catch (WebException we)
{
var response = (HttpWebResponse)we.Response;
var statusCode = response?.StatusCode;
var returnHeaders = response?.Headers.ToIEnumerable();
try
{
var responseStream = response?.GetResponseStream();
if (response != null && responseStream != null)
{
using (var reader = new StreamReader(responseStream))
{
returnedData = await reader.ReadToEndAsync().ConfigureAwait(false);
log.Write(LogVerbosity.Warning, "Server returned an error: " + returnedData);
}
response.Close();
var jsonResult = ValidateJson(returnedData);
return !jsonResult.Success ? new WebCallResult<string>(statusCode, returnHeaders, null, jsonResult.Error) : new WebCallResult<string>(statusCode, returnHeaders, null, ParseErrorResponse(jsonResult.Data));
}
}
catch (Exception e)
{
log.Write(LogVerbosity.Debug, "Not able to read server response: " + e.Message);
}
var infoMessage = "No response from server";
if (response == null)
{
infoMessage += $" | {we.Status} - {we.Message}";
log.Write(LogVerbosity.Warning, infoMessage);
return new WebCallResult<string>(0, null, null, new WebError(infoMessage));
}
infoMessage = $"Status: {response.StatusCode}-{response.StatusDescription}, Message: {we.Message}";
log.Write(LogVerbosity.Warning, infoMessage);
response.Close();
return new WebCallResult<string>(statusCode, returnHeaders, null, new ServerError(infoMessage));
}
catch (Exception e)
{
log.Write(LogVerbosity.Error, $"Unknown error occured: {e.GetType()}, {e.Message}, {e.StackTrace}");
return new WebCallResult<string>(null, null, null, new UnknownError(e.Message + ", data: " + returnedData));
}
}
/// <summary> /// <summary>
/// Parse an error response from the server. Only used when server returns a status other than Success(200) /// Parse an error response from the server. Only used when server returns a status other than Success(200)

View File

@ -50,11 +50,11 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// Handler for byte data /// Handler for byte data
/// </summary> /// </summary>
protected Func<byte[], string> dataInterpreterBytes; protected Func<byte[], string>? dataInterpreterBytes;
/// <summary> /// <summary>
/// Handler for string data /// Handler for string data
/// </summary> /// </summary>
protected Func<string, string> dataInterpreterString; protected Func<string, string>? dataInterpreterString;
/// <summary> /// <summary>
/// Generic handlers /// Generic handlers
/// </summary> /// </summary>
@ -62,11 +62,11 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// Periodic task /// Periodic task
/// </summary> /// </summary>
protected Task periodicTask; protected Task? periodicTask;
/// <summary> /// <summary>
/// Periodic task event /// Periodic task event
/// </summary> /// </summary>
protected AutoResetEvent periodicEvent; protected AutoResetEvent? periodicEvent;
/// <summary> /// <summary>
/// Is disposing /// Is disposing
/// </summary> /// </summary>
@ -84,17 +84,11 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
/// <param name="exchangeOptions">Client options</param> /// <param name="exchangeOptions">Client options</param>
/// <param name="authenticationProvider">Authentication provider</param> /// <param name="authenticationProvider">Authentication provider</param>
protected SocketClient(SocketClientOptions exchangeOptions, AuthenticationProvider authenticationProvider): base(exchangeOptions, authenticationProvider) protected SocketClient(SocketClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider): base(exchangeOptions, authenticationProvider)
{ {
Configure(exchangeOptions); if (exchangeOptions == null)
} throw new ArgumentNullException("Options");
/// <summary>
/// Configure the client using the provided options
/// </summary>
/// <param name="exchangeOptions">Options</param>
protected void Configure(SocketClientOptions exchangeOptions)
{
AutoReconnect = exchangeOptions.AutoReconnect; AutoReconnect = exchangeOptions.AutoReconnect;
ReconnectInterval = exchangeOptions.ReconnectInterval; ReconnectInterval = exchangeOptions.ReconnectInterval;
ResponseTimeout = exchangeOptions.SocketResponseTimeout; ResponseTimeout = exchangeOptions.SocketResponseTimeout;
@ -137,7 +131,7 @@ namespace CryptoExchange.Net
/// <param name="authenticated">If the subscription should be authenticated</param> /// <param name="authenticated">If the subscription should be authenticated</param>
/// <param name="dataHandler">The handler of update data</param> /// <param name="dataHandler">The handler of update data</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<UpdateSubscription>> Subscribe<T>(string url, object request, string identifier, bool authenticated, Action<T> dataHandler) protected virtual async Task<CallResult<UpdateSubscription>> Subscribe<T>(string url, object? request, string? identifier, bool authenticated, Action<T> dataHandler)
{ {
SocketConnection socket; SocketConnection socket;
SocketSubscription handler; SocketSubscription handler;
@ -155,7 +149,7 @@ namespace CryptoExchange.Net
} }
var connectResult = await ConnectIfNeeded(socket, authenticated).ConfigureAwait(false); var connectResult = await ConnectIfNeeded(socket, authenticated).ConfigureAwait(false);
if (!connectResult.Success) if (!connectResult)
return new CallResult<UpdateSubscription>(null, connectResult.Error); return new CallResult<UpdateSubscription>(null, connectResult.Error);
} }
finally finally
@ -170,7 +164,7 @@ namespace CryptoExchange.Net
if (request != null) if (request != null)
{ {
var subResult = await SubscribeAndWait(socket, request, handler).ConfigureAwait(false); var subResult = await SubscribeAndWait(socket, request, handler).ConfigureAwait(false);
if (!subResult.Success) if (!subResult)
{ {
await socket.Close(handler).ConfigureAwait(false); await socket.Close(handler).ConfigureAwait(false);
return new CallResult<UpdateSubscription>(null, subResult.Error); return new CallResult<UpdateSubscription>(null, subResult.Error);
@ -192,8 +186,8 @@ namespace CryptoExchange.Net
/// <returns></returns> /// <returns></returns>
protected internal virtual async Task<CallResult<bool>> SubscribeAndWait(SocketConnection socket, object request, SocketSubscription subscription) protected internal virtual async Task<CallResult<bool>> SubscribeAndWait(SocketConnection socket, object request, SocketSubscription subscription)
{ {
CallResult<object> callResult = null; CallResult<object>? callResult = null;
await socket.SendAndWait(request, ResponseTimeout, data => HandleSubscriptionResponse(socket, subscription, request, data, out callResult)).ConfigureAwait(false); await socket.SendAndWait(request, ResponseTimeout, data => HandleSubscriptionResponse(socket, subscription, request, data, out var callResult)).ConfigureAwait(false);
if (callResult?.Success == true) if (callResult?.Success == true)
subscription.Confirmed = true; subscription.Confirmed = true;
@ -237,7 +231,7 @@ namespace CryptoExchange.Net
} }
var connectResult = await ConnectIfNeeded(socket, authenticated).ConfigureAwait(false); var connectResult = await ConnectIfNeeded(socket, authenticated).ConfigureAwait(false);
if (!connectResult.Success) if (!connectResult)
return new CallResult<T>(default, connectResult.Error); return new CallResult<T>(default, connectResult.Error);
} }
finally finally
@ -291,17 +285,17 @@ namespace CryptoExchange.Net
return new CallResult<bool>(true, null); return new CallResult<bool>(true, null);
var connectResult = await ConnectSocket(socket).ConfigureAwait(false); var connectResult = await ConnectSocket(socket).ConfigureAwait(false);
if (!connectResult.Success) if (!connectResult)
return new CallResult<bool>(false, new CantConnectError()); return new CallResult<bool>(false, new CantConnectError());
if (!authenticated || socket.Authenticated) if (!authenticated || socket.Authenticated)
return new CallResult<bool>(true, null); return new CallResult<bool>(true, null);
var result = await AuthenticateSocket(socket).ConfigureAwait(false); var result = await AuthenticateSocket(socket).ConfigureAwait(false);
if (!result.Success) if (!result)
{ {
log.Write(LogVerbosity.Warning, "Socket authentication failed"); log.Write(LogVerbosity.Warning, "Socket authentication failed");
result.Error.Message = "Authentication failed: " + result.Error.Message; result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult<bool>(false, result.Error); return new CallResult<bool>(false, result.Error);
} }
@ -377,7 +371,7 @@ namespace CryptoExchange.Net
/// <param name="connection">The socket connection the handler is on</param> /// <param name="connection">The socket connection the handler is on</param>
/// <param name="dataHandler">The handler of the data received</param> /// <param name="dataHandler">The handler of the data received</param>
/// <returns></returns> /// <returns></returns>
protected virtual SocketSubscription AddHandler<T>(object request, string identifier, bool userSubscription, SocketConnection connection, Action<T> dataHandler) protected virtual SocketSubscription AddHandler<T>(object? request, string? identifier, bool userSubscription, SocketConnection connection, Action<T> dataHandler)
{ {
void InternalHandler(SocketConnection socketWrapper, JToken data) void InternalHandler(SocketConnection socketWrapper, JToken data)
{ {
@ -388,7 +382,7 @@ namespace CryptoExchange.Net
} }
var desResult = Deserialize<T>(data, false); var desResult = Deserialize<T>(data, false);
if (!desResult.Success) if (!desResult)
{ {
log.Write(LogVerbosity.Warning, $"Failed to deserialize data into type {typeof(T)}: {desResult.Error}"); log.Write(LogVerbosity.Warning, $"Failed to deserialize data into type {typeof(T)}: {desResult.Error}");
return; return;
@ -397,7 +391,7 @@ namespace CryptoExchange.Net
dataHandler(desResult.Data); dataHandler(desResult.Data);
} }
return connection.AddHandler(request ?? identifier, userSubscription, InternalHandler); return connection.AddHandler(request ?? identifier!, userSubscription, InternalHandler);
} }
/// <summary> /// <summary>
@ -486,6 +480,9 @@ namespace CryptoExchange.Net
/// <param name="objGetter">Method returning the object to send</param> /// <param name="objGetter">Method returning the object to send</param>
public virtual void SendPeriodic(TimeSpan interval, Func<SocketConnection, object> objGetter) public virtual void SendPeriodic(TimeSpan interval, Func<SocketConnection, object> objGetter)
{ {
if (objGetter == null)
throw new ArgumentNullException("objGetter");
periodicEvent = new AutoResetEvent(false); periodicEvent = new AutoResetEvent(false);
periodicTask = Task.Run(async () => periodicTask = Task.Run(async () =>
{ {
@ -517,7 +514,6 @@ namespace CryptoExchange.Net
} }
} }
} }
}); });
} }
@ -530,7 +526,7 @@ namespace CryptoExchange.Net
public virtual async Task Unsubscribe(UpdateSubscription subscription) public virtual async Task Unsubscribe(UpdateSubscription subscription)
{ {
if (subscription == null) if (subscription == null)
return; throw new ArgumentNullException("subscription");
log.Write(LogVerbosity.Info, "Closing subscription"); log.Write(LogVerbosity.Info, "Closing subscription");
await subscription.Close().ConfigureAwait(false); await subscription.Close().ConfigureAwait(false);
@ -564,9 +560,10 @@ namespace CryptoExchange.Net
{ {
disposing = true; disposing = true;
periodicEvent?.Set(); periodicEvent?.Set();
periodicEvent?.Dispose();
log.Write(LogVerbosity.Debug, "Disposing socket client, closing all subscriptions"); log.Write(LogVerbosity.Debug, "Disposing socket client, closing all subscriptions");
UnsubscribeAll().Wait(); UnsubscribeAll().Wait();
semaphoreSlim?.Dispose();
base.Dispose(); base.Dispose();
} }
} }

View File

@ -21,7 +21,7 @@ namespace CryptoExchange.Net.Sockets
internal static int lastStreamId; internal static int lastStreamId;
private static readonly object streamIdLock = new object(); private static readonly object streamIdLock = new object();
protected WebSocket socket; protected WebSocket? socket;
protected Log log; protected Log log;
protected object socketLock = new object(); protected object socketLock = new object();
@ -32,34 +32,22 @@ namespace CryptoExchange.Net.Sockets
protected IDictionary<string, string> cookies; protected IDictionary<string, string> cookies;
protected IDictionary<string, string> headers; protected IDictionary<string, string> headers;
protected HttpConnectProxy proxy; protected HttpConnectProxy? proxy;
public int Id { get; } public int Id { get; }
public bool Reconnecting { get; set; } public bool Reconnecting { get; set; }
public string Origin { get; set; } public string? Origin { get; set; }
public string Url { get; } public string Url { get; }
public bool IsClosed => socket.State == WebSocketState.Closed; public bool IsClosed => socket?.State == null ? true: socket.State == WebSocketState.Closed;
public bool IsOpen => socket.State == WebSocketState.Open; public bool IsOpen => socket?.State == WebSocketState.Open;
public SslProtocols SSLProtocols { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; public SslProtocols SSLProtocols { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
public Func<byte[], string> DataInterpreterBytes { get; set; } public Func<byte[], string>? DataInterpreterBytes { get; set; }
public Func<string, string> DataInterpreterString { get; set; } public Func<string, string>? DataInterpreterString { get; set; }
public DateTime LastActionTime { get; private set; } public DateTime LastActionTime { get; private set; }
public TimeSpan Timeout { get; set; } public TimeSpan Timeout { get; set; }
private Task timeoutTask; private Task? timeoutTask;
public bool PingConnection
{
get => socket.EnableAutoSendPing;
set => socket.EnableAutoSendPing = value;
}
public TimeSpan PingInterval
{
get => TimeSpan.FromSeconds(socket.AutoSendPingInterval);
set => socket.AutoSendPingInterval = (int) Math.Round(value.TotalSeconds);
}
public WebSocketState SocketState => socket?.State ?? WebSocketState.None; public WebSocketState SocketState => socket?.State ?? WebSocketState.None;
@ -176,7 +164,7 @@ namespace CryptoExchange.Net.Sockets
var waitLock = new object(); var waitLock = new object();
log?.Write(LogVerbosity.Debug, $"Socket {Id} closing"); log?.Write(LogVerbosity.Debug, $"Socket {Id} closing");
var evnt = new ManualResetEvent(false); ManualResetEvent? evnt = new ManualResetEvent(false);
var handler = new EventHandler((o, a) => var handler = new EventHandler((o, a) =>
{ {
lock(waitLock) lock(waitLock)
@ -208,7 +196,7 @@ namespace CryptoExchange.Net.Sockets
public virtual void Send(string data) public virtual void Send(string data)
{ {
socket.Send(data); socket?.Send(data);
} }
public virtual Task<bool> Connect() public virtual Task<bool> Connect()
@ -239,7 +227,7 @@ namespace CryptoExchange.Net.Sockets
{ {
log?.Write(LogVerbosity.Debug, $"Socket {Id} connecting"); log?.Write(LogVerbosity.Debug, $"Socket {Id} connecting");
var waitLock = new object(); var waitLock = new object();
var evnt = new ManualResetEvent(false); ManualResetEvent? evnt = new ManualResetEvent(false);
var handler = new EventHandler((o, a) => var handler = new EventHandler((o, a) =>
{ {
lock (waitLock) lock (waitLock)

View File

@ -19,15 +19,15 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Connection lost event /// Connection lost event
/// </summary> /// </summary>
public event Action ConnectionLost; public event Action? ConnectionLost;
/// <summary> /// <summary>
/// Connecting restored event /// Connecting restored event
/// </summary> /// </summary>
public event Action<TimeSpan> ConnectionRestored; public event Action<TimeSpan>? ConnectionRestored;
/// <summary> /// <summary>
/// Connecting closed event /// Connecting closed event
/// </summary> /// </summary>
public event Action Closed; public event Action? Closed;
/// <summary> /// <summary>
/// The amount of handlers /// The amount of handlers
@ -119,7 +119,7 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public SocketSubscription AddHandler(object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription AddHandler(object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
var handler = new SocketSubscription(null, request, userSubscription, dataHandler); var handler = new SocketSubscription(request, userSubscription, dataHandler);
lock (handlersLock) lock (handlersLock)
handlers.Add(handler); handlers.Add(handler);
return handler; return handler;
@ -135,7 +135,7 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns> /// <returns></returns>
public SocketSubscription AddHandler(string identifier, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription AddHandler(string identifier, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
var handler = new SocketSubscription(identifier, null, userSubscription, dataHandler); var handler = new SocketSubscription(identifier, userSubscription, dataHandler);
lock (handlersLock) lock (handlersLock)
handlers.Add(handler); handlers.Add(handler);
return handler; return handler;
@ -169,7 +169,7 @@ namespace CryptoExchange.Net.Sockets
private bool HandleData(JToken tokenData) private bool HandleData(JToken tokenData)
{ {
SocketSubscription currentSubscription = null; SocketSubscription? currentSubscription = null;
try try
{ {
var handled = false; var handled = false;
@ -181,7 +181,7 @@ namespace CryptoExchange.Net.Sockets
currentSubscription = handler; currentSubscription = handler;
if (handler.Request == null) if (handler.Request == null)
{ {
if (socketClient.MessageMatchesHandler(tokenData, handler.Identifier)) if (socketClient.MessageMatchesHandler(tokenData, handler.Identifier!))
{ {
handled = true; handled = true;
handler.MessageHandler(this, tokenData); handler.MessageHandler(this, tokenData);
@ -326,7 +326,7 @@ namespace CryptoExchange.Net.Sockets
if (Authenticated) if (Authenticated)
{ {
var authResult = await socketClient.AuthenticateSocket(this).ConfigureAwait(false); var authResult = await socketClient.AuthenticateSocket(this).ConfigureAwait(false);
if (!authResult.Success) if (!authResult)
{ {
log.Write(LogVerbosity.Info, "Authentication failed on reconnected socket. Disconnecting and reconnecting."); log.Write(LogVerbosity.Info, "Authentication failed on reconnected socket. Disconnecting and reconnecting.");
return false; return false;
@ -343,9 +343,9 @@ namespace CryptoExchange.Net.Sockets
var taskList = new List<Task>(); var taskList = new List<Task>();
foreach (var handler in handlerList) foreach (var handler in handlerList)
{ {
var task = socketClient.SubscribeAndWait(this, handler.Request, handler).ContinueWith(t => var task = socketClient.SubscribeAndWait(this, handler.Request!, handler).ContinueWith(t =>
{ {
if (!t.Result.Success) if (!t.Result)
success = false; success = false;
}); });
taskList.Add(task); taskList.Add(task);
@ -403,7 +403,7 @@ namespace CryptoExchange.Net.Sockets
internal class PendingRequest internal class PendingRequest
{ {
public Func<JToken, bool> Handler { get; } public Func<JToken, bool> Handler { get; }
public JToken Result { get; private set; } public JToken? Result { get; private set; }
public ManualResetEvent Event { get; } public ManualResetEvent Event { get; }
public TimeSpan Timeout { get; } public TimeSpan Timeout { get; }

View File

@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Exception event /// Exception event
/// </summary> /// </summary>
public event Action<Exception> Exception; public event Action<Exception>? Exception;
/// <summary> /// <summary>
/// Message handlers for this subscription. Should return true if the message is handled and should not be distributed to the other handlers /// Message handlers for this subscription. Should return true if the message is handled and should not be distributed to the other handlers
@ -21,11 +21,11 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Request object /// Request object
/// </summary> /// </summary>
public object Request { get; set; } public object? Request { get; set; }
/// <summary> /// <summary>
/// Subscription identifier /// Subscription identifier
/// </summary> /// </summary>
public string Identifier { get; set; } public string? Identifier { get; set; }
/// <summary> /// <summary>
/// Is user subscription or generic /// Is user subscription or generic
/// </summary> /// </summary>
@ -36,22 +36,32 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public bool Confirmed { get; set; } public bool Confirmed { get; set; }
/// <summary>
/// ctor
/// </summary>
/// <param name="request"></param>
/// <param name="userSubscription"></param>
/// <param name="dataHandler"></param>
public SocketSubscription(object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{
UserSubscription = userSubscription;
MessageHandler = dataHandler;
Request = request;
}
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="identifier"></param> /// <param name="identifier"></param>
/// <param name="request"></param>
/// <param name="userSubscription"></param> /// <param name="userSubscription"></param>
/// <param name="dataHandler"></param> /// <param name="dataHandler"></param>
public SocketSubscription(string identifier, object request, bool userSubscription, Action<SocketConnection, JToken> dataHandler) public SocketSubscription(string identifier, bool userSubscription, Action<SocketConnection, JToken> dataHandler)
{ {
UserSubscription = userSubscription; UserSubscription = userSubscription;
MessageHandler = dataHandler; MessageHandler = dataHandler;
Identifier = identifier; Identifier = identifier;
Request = request;
} }
/// <summary> /// <summary>
/// Invoke the exception event /// Invoke the exception event
/// </summary> /// </summary>