diff --git a/CryptoExchange.Net.UnitTests/CryptoExchange.Net.UnitTests.csproj b/CryptoExchange.Net.UnitTests/CryptoExchange.Net.UnitTests.csproj
index 3072a08..59f0095 100644
--- a/CryptoExchange.Net.UnitTests/CryptoExchange.Net.UnitTests.csproj
+++ b/CryptoExchange.Net.UnitTests/CryptoExchange.Net.UnitTests.csproj
@@ -5,6 +5,10 @@
false
+
+
+
+
diff --git a/CryptoExchange.Net/.editorconfig b/CryptoExchange.Net/.editorconfig
new file mode 100644
index 0000000..3f12941
--- /dev/null
+++ b/CryptoExchange.Net/.editorconfig
@@ -0,0 +1,182 @@
+root = true
+
+[*]
+
+# Indentation and spacing
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+charset = utf-8
+insert_final_newline = true
+
+# ReSharper code style properties
+resharper_csharp_keep_existing_embedded_arrangement = false
+resharper_csharp_place_accessorholder_attribute_on_same_line = false
+resharper_csharp_wrap_after_declaration_lpar = true
+resharper_csharp_wrap_parameters_style = chop_if_long
+resharper_csharp_blank_lines_around_single_line_auto_property = 1
+resharper_csharp_keep_blank_lines_in_declarations = 1
+resharper_trailing_comma_in_multiline_lists = true
+
+[*.cs]
+
+indent_size = 4
+
+# Code style conventions
+dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_namespace_declarations = file_scoped:warning
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+csharp_prefer_braces = when_multiline:warning
+
+# Analyzer preferences
+dotnet_diagnostic.CA2007.severity = warning # Call ConfigureAwait on the awaited Task.
+dotnet_code_quality.CA2007.exclude_async_void_methods = true
+dotnet_code_quality.CA2007.output_kind = DynamicallyLinkedLibrary
+
+dotnet_diagnostic.CA1000.severity = none # Do not declare static members on generic types
+dotnet_diagnostic.CA1051.severity = none # Do not declare visible instance fields
+dotnet_diagnostic.CA1510.severity = none # Use ArgumentNullException throw helper
+dotnet_diagnostic.CA1720.severity = none # Identifiers should not contain type names
+dotnet_diagnostic.CA1716.severity = none # Identifiers should not match keywords
+dotnet_diagnostic.CA1835.severity = none # Use ArgumentNullException throw helper
+dotnet_diagnostic.CA1846.severity = none # Prefer AsSpan over Substring
+dotnet_diagnostic.CA1848.severity = none # Use the LoggerMessage delegates
+dotnet_diagnostic.CA1850.severity = none # Prefer static HashData method over ComputeHash
+dotnet_diagnostic.CA1866.severity = none # Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
+dotnet_diagnostic.CA2201.severity = none # Do not raise reserved exception types
+dotnet_diagnostic.CA2208.severity = none # Do not raise reserved exception types
+dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary
+
+[*.xml]
+ij_xml_space_inside_empty_tag = true
+[*.cs]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.severity = warning
+dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.symbols = private_or_internal_field
+dotnet_naming_rule.private_or_internal_field_should_be_fields_start_with__.style = fields_start_with__
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
+dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
+dotnet_naming_symbols.private_or_internal_field.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.fields_start_with__.required_prefix = _
+dotnet_naming_style.fields_start_with__.required_suffix =
+dotnet_naming_style.fields_start_with__.word_separator =
+dotnet_naming_style.fields_start_with__.capitalization = camel_case
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:suggestion
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_prefer_system_threading_lock = true:suggestion
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = true:silent
+
+[*.vb]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
diff --git a/CryptoExchange.Net/AssemblyInfo.cs b/CryptoExchange.Net/AssemblyInfo.cs
index 6a54bd0..0227759 100644
--- a/CryptoExchange.Net/AssemblyInfo.cs
+++ b/CryptoExchange.Net/AssemblyInfo.cs
@@ -1,6 +1,5 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CryptoExchange.Net.UnitTests")]
-namespace System.Runtime.CompilerServices
-{
- internal static class IsExternalInit { }
-}
\ No newline at end of file
+namespace System.Runtime.CompilerServices;
+
+internal static class IsExternalInit { }
\ No newline at end of file
diff --git a/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs b/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
index e3fdb29..173cdd4 100644
--- a/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
+++ b/CryptoExchange.Net/Attributes/JsonConversionAttribute.cs
@@ -1,12 +1,11 @@
-using System;
+using System;
-namespace CryptoExchange.Net.Attributes
+namespace CryptoExchange.Net.Attributes;
+
+///
+/// Used for conversion in ArrayConverter
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class JsonConversionAttribute: Attribute
{
- ///
- /// Used for conversion in ArrayConverter
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class JsonConversionAttribute: Attribute
- {
- }
}
diff --git a/CryptoExchange.Net/Attributes/MapAttribute.cs b/CryptoExchange.Net/Attributes/MapAttribute.cs
index f824ea4..d63c84f 100644
--- a/CryptoExchange.Net/Attributes/MapAttribute.cs
+++ b/CryptoExchange.Net/Attributes/MapAttribute.cs
@@ -1,25 +1,24 @@
-using System;
+using System;
-namespace CryptoExchange.Net.Attributes
+namespace CryptoExchange.Net.Attributes;
+
+///
+/// Map a enum entry to string values
+///
+[AttributeUsage(AttributeTargets.Field)]
+public class MapAttribute : Attribute
{
///
- /// Map a enum entry to string values
+ /// Values mapping to the enum entry
///
- [AttributeUsage(AttributeTargets.Field)]
- public class MapAttribute : Attribute
- {
- ///
- /// Values mapping to the enum entry
- ///
- public string[] Values { get; set; }
+ public string[] Values { get; set; }
- ///
- /// ctor
- ///
- ///
- public MapAttribute(params string[] maps)
- {
- Values = maps;
- }
+ ///
+ /// ctor
+ ///
+ ///
+ public MapAttribute(params string[] maps)
+ {
+ Values = maps;
}
}
diff --git a/CryptoExchange.Net/Authentication/ApiCredentials.cs b/CryptoExchange.Net/Authentication/ApiCredentials.cs
index 3542b3b..fae466d 100644
--- a/CryptoExchange.Net/Authentication/ApiCredentials.cs
+++ b/CryptoExchange.Net/Authentication/ApiCredentials.cs
@@ -1,60 +1,56 @@
-using System;
-using System.IO;
-using CryptoExchange.Net.Converters.SystemTextJson;
-using CryptoExchange.Net.Converters.MessageParsing;
+using System;
-namespace CryptoExchange.Net.Authentication
+namespace CryptoExchange.Net.Authentication;
+
+///
+/// Api credentials, used to sign requests accessing private endpoints
+///
+public class ApiCredentials
{
///
- /// Api credentials, used to sign requests accessing private endpoints
+ /// The api key / label to authenticate requests
///
- public class ApiCredentials
+ public string Key { get; set; }
+
+ ///
+ /// The api secret or private key to authenticate requests
+ ///
+ public string Secret { get; set; }
+
+ ///
+ /// The api passphrase. Not needed on all exchanges
+ ///
+ public string? Pass { get; set; }
+
+ ///
+ /// Type of the credentials
+ ///
+ public ApiCredentialsType CredentialType { get; set; }
+
+ ///
+ /// Create Api credentials providing an api key and secret for authentication
+ ///
+ /// The api key / label used for identification
+ /// The api secret or private key used for signing
+ /// The api pass for the key. Not always needed
+ /// The type of credentials
+ public ApiCredentials(string key, string secret, string? pass = null, ApiCredentialsType credentialType = ApiCredentialsType.Hmac)
{
- ///
- /// The api key / label to authenticate requests
- ///
- public string Key { get; set; }
+ if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
+ throw new ArgumentException("Key and secret can't be null/empty");
- ///
- /// The api secret or private key to authenticate requests
- ///
- public string Secret { get; set; }
+ CredentialType = credentialType;
+ Key = key;
+ Secret = secret;
+ Pass = pass;
+ }
- ///
- /// The api passphrase. Not needed on all exchanges
- ///
- public string? Pass { get; set; }
-
- ///
- /// Type of the credentials
- ///
- public ApiCredentialsType CredentialType { get; set; }
-
- ///
- /// Create Api credentials providing an api key and secret for authentication
- ///
- /// The api key / label used for identification
- /// The api secret or private key used for signing
- /// The api pass for the key. Not always needed
- /// The type of credentials
- public ApiCredentials(string key, string secret, string? pass = null, ApiCredentialsType credentialType = ApiCredentialsType.Hmac)
- {
- if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret))
- throw new ArgumentException("Key and secret can't be null/empty");
-
- CredentialType = credentialType;
- Key = key;
- Secret = secret;
- Pass = pass;
- }
-
- ///
- /// Copy the credentials
- ///
- ///
- public virtual ApiCredentials Copy()
- {
- return new ApiCredentials(Key, Secret, Pass, CredentialType);
- }
+ ///
+ /// Copy the credentials
+ ///
+ ///
+ public virtual ApiCredentials Copy()
+ {
+ return new ApiCredentials(Key, Secret, Pass, CredentialType);
}
}
diff --git a/CryptoExchange.Net/Authentication/ApiCredentialsType.cs b/CryptoExchange.Net/Authentication/ApiCredentialsType.cs
index 2da474f..ae4b57c 100644
--- a/CryptoExchange.Net/Authentication/ApiCredentialsType.cs
+++ b/CryptoExchange.Net/Authentication/ApiCredentialsType.cs
@@ -1,21 +1,20 @@
-namespace CryptoExchange.Net.Authentication
+namespace CryptoExchange.Net.Authentication;
+
+///
+/// Credentials type
+///
+public enum ApiCredentialsType
{
///
- /// Credentials type
+ /// Hmac keys credentials
///
- public enum ApiCredentialsType
- {
- ///
- /// Hmac keys credentials
- ///
- Hmac,
- ///
- /// Rsa keys credentials in xml format
- ///
- RsaXml,
- ///
- /// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
- ///
- RsaPem
- }
+ Hmac,
+ ///
+ /// Rsa keys credentials in xml format
+ ///
+ RsaXml,
+ ///
+ /// Rsa keys credentials in pem/base64 format. Only available for .NetStandard 2.1 and up, use xml format for lower.
+ ///
+ RsaPem
}
diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
index 1c49f1d..56c8ec0 100644
--- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
+++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs
@@ -1,473 +1,477 @@
-using CryptoExchange.Net.Clients;
+using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
-namespace CryptoExchange.Net.Authentication
+namespace CryptoExchange.Net.Authentication;
+
+///
+/// Base class for authentication providers
+///
+public abstract class AuthenticationProvider
{
+ internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
+
///
- /// Base class for authentication providers
+ /// Provided credentials
///
- public abstract class AuthenticationProvider
+ protected internal readonly ApiCredentials _credentials;
+
+ ///
+ /// Byte representation of the secret
+ ///
+ protected byte[] _sBytes;
+
+ ///
+ /// Get the API key of the current credentials
+ ///
+ public string ApiKey => _credentials.Key!;
+ ///
+ /// Get the Passphrase of the current credentials
+ ///
+ public string? Pass => _credentials.Pass;
+
+ ///
+ /// ctor
+ ///
+ ///
+ protected AuthenticationProvider(ApiCredentials credentials)
{
- internal IAuthTimeProvider TimeProvider { get; set; } = new AuthTimeProvider();
+ if (credentials.Key == null || credentials.Secret == null)
+ throw new ArgumentException("ApiKey/Secret needed");
- ///
- /// Provided credentials
- ///
- protected internal readonly ApiCredentials _credentials;
-
- ///
- /// Byte representation of the secret
- ///
- protected byte[] _sBytes;
-
- ///
- /// Get the API key of the current credentials
- ///
- public string ApiKey => _credentials.Key!;
- ///
- /// Get the Passphrase of the current credentials
- ///
- public string? Pass => _credentials.Pass;
-
- ///
- /// ctor
- ///
- ///
- protected AuthenticationProvider(ApiCredentials credentials)
- {
- if (credentials.Key == null || credentials.Secret == null)
- throw new ArgumentException("ApiKey/Secret needed");
-
- _credentials = credentials;
- _sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
- }
-
- ///
- /// Authenticate a request
- ///
- /// The Api client sending the request
- /// The request configuration
- public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
-
- ///
- /// SHA256 sign the data and return the bytes
- ///
- ///
- ///
- protected static byte[] SignSHA256Bytes(string data)
- {
- using var encryptor = SHA256.Create();
- return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- }
-
- ///
- /// SHA256 sign the data and return the bytes
- ///
- ///
- ///
- protected static byte[] SignSHA256Bytes(byte[] data)
- {
- using var encryptor = SHA256.Create();
- return encryptor.ComputeHash(data);
- }
-
- ///
- /// SHA256 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA256(string data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA256.Create();
- var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA256 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA256(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA256.Create();
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA384 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA384(string data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA384.Create();
- var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA384 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA384(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA384.Create();
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA384 sign the data and return the hash
- ///
- /// Data to sign
- ///
- protected static byte[] SignSHA384Bytes(string data)
- {
- using var encryptor = SHA384.Create();
- return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- }
-
- ///
- /// SHA384 sign the data and return the hash
- ///
- /// Data to sign
- ///
- protected static byte[] SignSHA384Bytes(byte[] data)
- {
- using var encryptor = SHA384.Create();
- return encryptor.ComputeHash(data);
- }
-
- ///
- /// SHA512 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA512(string data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA512.Create();
- var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA512 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignSHA512(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = SHA512.Create();
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA512 sign the data and return the hash
- ///
- /// Data to sign
- ///
- protected static byte[] SignSHA512Bytes(string data)
- {
- using var encryptor = SHA512.Create();
- return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- }
-
- ///
- /// SHA512 sign the data and return the hash
- ///
- /// Data to sign
- ///
- protected static byte[] SignSHA512Bytes(byte[] data)
- {
- using var encryptor = SHA512.Create();
- return encryptor.ComputeHash(data);
- }
-
- ///
- /// MD5 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignMD5(string data, SignOutputType? outputType = null)
- {
- using var encryptor = MD5.Create();
- var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// MD5 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected static string SignMD5(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = MD5.Create();
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// MD5 sign the data and return the hash
- ///
- /// Data to sign
- ///
- protected static byte[] SignMD5Bytes(string data)
- {
- using var encryptor = MD5.Create();
- return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
- }
-
- ///
- /// HMACSHA256 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA256(string data, SignOutputType? outputType = null)
- => SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType);
-
- ///
- /// HMACSHA256 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = new HMACSHA256(_sBytes);
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// HMACSHA384 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA384(string data, SignOutputType? outputType = null)
- => SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType);
-
- ///
- /// HMACSHA384 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = new HMACSHA384(_sBytes);
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// HMACSHA512 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
- => SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
-
- ///
- /// HMACSHA512 sign the data and return the hash
- ///
- /// Data to sign
- /// String type
- ///
- protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
- {
- using var encryptor = new HMACSHA512(_sBytes);
- var resultBytes = encryptor.ComputeHash(data);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA256 sign the data
- ///
- ///
- ///
- ///
- protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null)
- {
- using var rsa = CreateRSA();
- using var sha256 = SHA256.Create();
- var hash = sha256.ComputeHash(data);
- var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
- return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA384 sign the data
- ///
- ///
- ///
- ///
- protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null)
- {
- using var rsa = CreateRSA();
- using var sha384 = SHA384.Create();
- var hash = sha384.ComputeHash(data);
- var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- ///
- /// SHA512 sign the data
- ///
- ///
- ///
- ///
- protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null)
- {
- using var rsa = CreateRSA();
- using var sha512 = SHA512.Create();
- var hash = sha512.ComputeHash(data);
- var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
- return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
- }
-
- private RSA CreateRSA()
- {
- var rsa = RSA.Create();
- if (_credentials.CredentialType == ApiCredentialsType.RsaPem)
- {
-#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
- // Read from pem private key
- var key = _credentials.Secret!
- .Replace("\n", "")
- .Replace("-----BEGIN PRIVATE KEY-----", "")
- .Replace("-----END PRIVATE KEY-----", "")
- .Trim();
- rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
- key)
- , out _);
-#else
- throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
-#endif
- }
- else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
- {
- // Read from xml private key format
- rsa.FromXmlString(_credentials.Secret!);
- }
- else
- {
- throw new Exception("Invalid credentials type");
- }
-
- return rsa;
- }
-
- ///
- /// Convert byte array to hex string
- ///
- ///
- ///
- protected static string BytesToHexString(byte[] buff)
- {
-#if NET9_0_OR_GREATER
- return Convert.ToHexString(buff);
-#else
- var result = string.Empty;
- foreach (var t in buff)
- result += t.ToString("X2");
- return result;
-#endif
- }
-
- ///
- /// Convert byte array to base64 string
- ///
- ///
- ///
- protected static string BytesToBase64String(byte[] buff)
- {
- return Convert.ToBase64String(buff);
- }
-
- ///
- /// Get current timestamp including the time sync offset from the api client
- ///
- ///
- ///
- protected DateTime GetTimestamp(RestApiClient apiClient)
- {
- return TimeProvider.GetTime().Add(apiClient.GetTimeOffset() ?? TimeSpan.Zero)!;
- }
-
- ///
- /// Get millisecond timestamp as a string including the time sync offset from the api client
- ///
- ///
- ///
- protected string GetMillisecondTimestamp(RestApiClient apiClient)
- {
- return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
- }
-
- ///
- /// Get millisecond timestamp as a long including the time sync offset from the api client
- ///
- ///
- ///
- protected long GetMillisecondTimestampLong(RestApiClient apiClient)
- {
- return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
- }
-
- ///
- /// Return the serialized request body
- ///
- ///
- ///
- ///
- protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary parameters)
- {
- if (serializer is not IStringMessageSerializer stringSerializer)
- throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
-
- if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
- return stringSerializer.Serialize(value);
- else
- return stringSerializer.Serialize(parameters);
- }
+ _credentials = credentials;
+ _sBytes = Encoding.UTF8.GetBytes(credentials.Secret);
}
- ///
- public abstract class AuthenticationProvider : AuthenticationProvider where TApiCredentials : ApiCredentials
- {
- ///
- protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
+ ///
+ /// Authenticate a request
+ ///
+ /// The Api client sending the request
+ /// The request configuration
+ public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
- ///
- /// ctor
- ///
- ///
- protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
+ ///
+ /// SHA256 sign the data and return the bytes
+ ///
+ ///
+ ///
+ protected static byte[] SignSHA256Bytes(string data)
+ {
+ using var encryptor = SHA256.Create();
+ return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ }
+
+ ///
+ /// SHA256 sign the data and return the bytes
+ ///
+ ///
+ ///
+ protected static byte[] SignSHA256Bytes(byte[] data)
+ {
+ using var encryptor = SHA256.Create();
+ return encryptor.ComputeHash(data);
+ }
+
+ ///
+ /// SHA256 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA256(string data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA256.Create();
+ var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA256 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA256(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA256.Create();
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA384(string data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA384.Create();
+ var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA384(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA384.Create();
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ ///
+ protected static byte[] SignSHA384Bytes(string data)
+ {
+ using var encryptor = SHA384.Create();
+ return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ }
+
+ ///
+ /// SHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ ///
+ protected static byte[] SignSHA384Bytes(byte[] data)
+ {
+ using var encryptor = SHA384.Create();
+ return encryptor.ComputeHash(data);
+ }
+
+ ///
+ /// SHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA512(string data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA512.Create();
+ var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignSHA512(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = SHA512.Create();
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ ///
+ protected static byte[] SignSHA512Bytes(string data)
+ {
+ using var encryptor = SHA512.Create();
+ return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ }
+
+ ///
+ /// SHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ ///
+ protected static byte[] SignSHA512Bytes(byte[] data)
+ {
+ using var encryptor = SHA512.Create();
+ return encryptor.ComputeHash(data);
+ }
+
+ ///
+ /// MD5 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignMD5(string data, SignOutputType? outputType = null)
+ {
+#pragma warning disable CA5351
+ using var encryptor = MD5.Create();
+#pragma warning restore CA5351
+ var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// MD5 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected static string SignMD5(byte[] data, SignOutputType? outputType = null)
+ {
+#pragma warning disable CA5351
+ using var encryptor = MD5.Create();
+#pragma warning restore CA5351
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// MD5 sign the data and return the hash
+ ///
+ /// Data to sign
+ ///
+ protected static byte[] SignMD5Bytes(string data)
+ {
+#pragma warning disable CA5351
+ using var encryptor = MD5.Create();
+#pragma warning restore CA5351
+ return encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
+ }
+
+ ///
+ /// HMACSHA256 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA256(string data, SignOutputType? outputType = null)
+ => SignHMACSHA256(Encoding.UTF8.GetBytes(data), outputType);
+
+ ///
+ /// HMACSHA256 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA256(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = new HMACSHA256(_sBytes);
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// HMACSHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA384(string data, SignOutputType? outputType = null)
+ => SignHMACSHA384(Encoding.UTF8.GetBytes(data), outputType);
+
+ ///
+ /// HMACSHA384 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA384(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = new HMACSHA384(_sBytes);
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// HMACSHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
+ => SignHMACSHA512(Encoding.UTF8.GetBytes(data), outputType);
+
+ ///
+ /// HMACSHA512 sign the data and return the hash
+ ///
+ /// Data to sign
+ /// String type
+ ///
+ protected string SignHMACSHA512(byte[] data, SignOutputType? outputType = null)
+ {
+ using var encryptor = new HMACSHA512(_sBytes);
+ var resultBytes = encryptor.ComputeHash(data);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA256 sign the data
+ ///
+ ///
+ ///
+ ///
+ protected string SignRSASHA256(byte[] data, SignOutputType? outputType = null)
+ {
+ using var rsa = CreateRSA();
+ using var sha256 = SHA256.Create();
+ var hash = sha256.ComputeHash(data);
+ var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ return outputType == SignOutputType.Base64? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA384 sign the data
+ ///
+ ///
+ ///
+ ///
+ protected string SignRSASHA384(byte[] data, SignOutputType? outputType = null)
+ {
+ using var rsa = CreateRSA();
+ using var sha384 = SHA384.Create();
+ var hash = sha384.ComputeHash(data);
+ var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ ///
+ /// SHA512 sign the data
+ ///
+ ///
+ ///
+ ///
+ protected string SignRSASHA512(byte[] data, SignOutputType? outputType = null)
+ {
+ using var rsa = CreateRSA();
+ using var sha512 = SHA512.Create();
+ var hash = sha512.ComputeHash(data);
+ var resultBytes = rsa.SignHash(hash, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
+ return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
+ }
+
+ private RSA CreateRSA()
+ {
+ var rsa = RSA.Create();
+ if (_credentials.CredentialType == ApiCredentialsType.RsaPem)
{
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
+ // Read from pem private key
+ var key = _credentials.Secret!
+ .Replace("\n", "")
+ .Replace("-----BEGIN PRIVATE KEY-----", "")
+ .Replace("-----END PRIVATE KEY-----", "")
+ .Trim();
+ rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(
+ key)
+ , out _);
+#else
+ throw new Exception("Pem format not supported when running from .NetStandard2.0. Convert the private key to xml format.");
+#endif
}
+ else if (_credentials.CredentialType == ApiCredentialsType.RsaXml)
+ {
+ // Read from xml private key format
+ rsa.FromXmlString(_credentials.Secret!);
+ }
+ else
+ {
+ throw new Exception("Invalid credentials type");
+ }
+
+ return rsa;
+ }
+
+ ///
+ /// Convert byte array to hex string
+ ///
+ ///
+ ///
+ protected static string BytesToHexString(byte[] buff)
+ {
+#if NET9_0_OR_GREATER
+ return Convert.ToHexString(buff);
+#else
+ var result = string.Empty;
+ foreach (var t in buff)
+ result += t.ToString("X2");
+ return result;
+#endif
+ }
+
+ ///
+ /// Convert byte array to base64 string
+ ///
+ ///
+ ///
+ protected static string BytesToBase64String(byte[] buff)
+ {
+ return Convert.ToBase64String(buff);
+ }
+
+ ///
+ /// Get current timestamp including the time sync offset from the api client
+ ///
+ ///
+ ///
+ protected DateTime GetTimestamp(RestApiClient apiClient)
+ {
+ return TimeProvider.GetTime().Add(apiClient.GetTimeOffset() ?? TimeSpan.Zero)!;
+ }
+
+ ///
+ /// Get millisecond timestamp as a string including the time sync offset from the api client
+ ///
+ ///
+ ///
+ protected string GetMillisecondTimestamp(RestApiClient apiClient)
+ {
+ return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Get millisecond timestamp as a long including the time sync offset from the api client
+ ///
+ ///
+ ///
+ protected long GetMillisecondTimestampLong(RestApiClient apiClient)
+ {
+ return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
+ }
+
+ ///
+ /// Return the serialized request body
+ ///
+ ///
+ ///
+ ///
+ protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary parameters)
+ {
+ if (serializer is not IStringMessageSerializer stringSerializer)
+ throw new InvalidOperationException("Non-string message serializer can't get serialized request body");
+
+ if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
+ return stringSerializer.Serialize(value);
+ else
+ return stringSerializer.Serialize(parameters);
+ }
+}
+
+///
+public abstract class AuthenticationProvider : AuthenticationProvider where TApiCredentials : ApiCredentials
+{
+ ///
+ protected new TApiCredentials _credentials => (TApiCredentials)base._credentials;
+
+ ///
+ /// ctor
+ ///
+ ///
+ protected AuthenticationProvider(TApiCredentials credentials) : base(credentials)
+ {
}
}
diff --git a/CryptoExchange.Net/Authentication/SignOutputType.cs b/CryptoExchange.Net/Authentication/SignOutputType.cs
index 2c8ae5a..8dd46f4 100644
--- a/CryptoExchange.Net/Authentication/SignOutputType.cs
+++ b/CryptoExchange.Net/Authentication/SignOutputType.cs
@@ -1,17 +1,16 @@
-namespace CryptoExchange.Net.Authentication
+namespace CryptoExchange.Net.Authentication;
+
+///
+/// Output string type
+///
+public enum SignOutputType
{
///
- /// Output string type
+ /// Hex string
///
- public enum SignOutputType
- {
- ///
- /// Hex string
- ///
- Hex,
- ///
- /// Base64 string
- ///
- Base64
- }
+ Hex,
+ ///
+ /// Base64 string
+ ///
+ Base64
}
diff --git a/CryptoExchange.Net/Caching/MemoryCache.cs b/CryptoExchange.Net/Caching/MemoryCache.cs
index ca2c3c4..6cd642e 100644
--- a/CryptoExchange.Net/Caching/MemoryCache.cs
+++ b/CryptoExchange.Net/Caching/MemoryCache.cs
@@ -1,53 +1,52 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Linq;
-namespace CryptoExchange.Net.Caching
+namespace CryptoExchange.Net.Caching;
+
+internal class MemoryCache
{
- internal class MemoryCache
+ private readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
+ private readonly object _lock = new object();
+
+ ///
+ /// Add a new cache entry. Will override an existing entry if it already exists
+ ///
+ /// The key identifier
+ /// Cache value
+ public void Add(string key, object value)
{
- private readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
- private readonly object _lock = new object();
+ var cacheItem = new CacheItem(DateTime.UtcNow, value);
+ _cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
+ }
- ///
- /// Add a new cache entry. Will override an existing entry if it already exists
- ///
- /// The key identifier
- /// Cache value
- public void Add(string key, object value)
+ ///
+ /// Get a cached value
+ ///
+ /// The key identifier
+ /// The max age of the cached entry
+ /// Cached value if it was in cache
+ public object? Get(string key, TimeSpan maxAge)
+ {
+ foreach (var item in _cache.Where(x => DateTime.UtcNow - x.Value.CacheTime > maxAge).ToList())
+ _cache.TryRemove(item.Key, out _);
+
+ _cache.TryGetValue(key, out CacheItem? value);
+ if (value == null)
+ return null;
+
+ return value.Value;
+ }
+
+ private class CacheItem
+ {
+ public DateTime CacheTime { get; }
+ public object Value { get; }
+
+ public CacheItem(DateTime cacheTime, object value)
{
- var cacheItem = new CacheItem(DateTime.UtcNow, value);
- _cache.AddOrUpdate(key, cacheItem, (key, val1) => cacheItem);
- }
-
- ///
- /// Get a cached value
- ///
- /// The key identifier
- /// The max age of the cached entry
- /// Cached value if it was in cache
- public object? Get(string key, TimeSpan maxAge)
- {
- foreach (var item in _cache.Where(x => DateTime.UtcNow - x.Value.CacheTime > maxAge).ToList())
- _cache.TryRemove(item.Key, out _);
-
- _cache.TryGetValue(key, out CacheItem? value);
- if (value == null)
- return null;
-
- return value.Value;
- }
-
- private class CacheItem
- {
- public DateTime CacheTime { get; }
- public object Value { get; }
-
- public CacheItem(DateTime cacheTime, object value)
- {
- CacheTime = cacheTime;
- Value = value;
- }
+ CacheTime = cacheTime;
+ Value = value;
}
}
}
diff --git a/CryptoExchange.Net/Clients/BaseApiClient.cs b/CryptoExchange.Net/Clients/BaseApiClient.cs
index 7806e1f..4c5739a 100644
--- a/CryptoExchange.Net/Clients/BaseApiClient.cs
+++ b/CryptoExchange.Net/Clients/BaseApiClient.cs
@@ -1,134 +1,131 @@
using System;
-using System.Collections.Generic;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
-using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using Microsoft.Extensions.Logging;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+/// Base API for all API clients
+///
+public abstract class BaseApiClient : IDisposable, IBaseApiClient
{
///
- /// Base API for all API clients
+ /// Logger
///
- public abstract class BaseApiClient : IDisposable, IBaseApiClient
+ protected ILogger _logger;
+
+ ///
+ /// If we are disposing
+ ///
+ protected bool _disposing;
+
+ ///
+ /// The authentication provider for this API client. (null if no credentials are set)
+ ///
+ public AuthenticationProvider? AuthenticationProvider { get; private set; }
+
+ ///
+ /// The environment this client communicates to
+ ///
+ public string BaseAddress { get; }
+
+ ///
+ /// Output the original string data along with the deserialized object
+ ///
+ public bool OutputOriginalData { get; }
+
+ ///
+ public bool Authenticated => ApiCredentials != null;
+
+ ///
+ public ApiCredentials? ApiCredentials { get; set; }
+
+ ///
+ /// Api options
+ ///
+ public ApiOptions ApiOptions { get; }
+
+ ///
+ /// Client Options
+ ///
+ public ExchangeOptions ClientOptions { get; }
+
+ ///
+ /// Mapping of a response code to known error types
+ ///
+ protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
+
+ ///
+ /// ctor
+ ///
+ /// Logger
+ /// Should data from this client include the original data in the call result
+ /// Base address for this API client
+ /// Api credentials
+ /// Client options
+ /// Api options
+ protected BaseApiClient(ILogger logger, bool outputOriginalData, ApiCredentials? apiCredentials, string baseAddress, ExchangeOptions clientOptions, ApiOptions apiOptions)
{
- ///
- /// Logger
- ///
- protected ILogger _logger;
+ _logger = logger;
- ///
- /// If we are disposing
- ///
- protected bool _disposing;
+ ClientOptions = clientOptions;
+ ApiOptions = apiOptions;
+ OutputOriginalData = outputOriginalData;
+ BaseAddress = baseAddress;
+ ApiCredentials = apiCredentials?.Copy();
- ///
- /// The authentication provider for this API client. (null if no credentials are set)
- ///
- public AuthenticationProvider? AuthenticationProvider { get; private set; }
+ if (ApiCredentials != null)
+ AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
+ }
- ///
- /// The environment this client communicates to
- ///
- public string BaseAddress { get; }
+ ///
+ /// Create an AuthenticationProvider implementation instance based on the provided credentials
+ ///
+ ///
+ ///
+ protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
- ///
- /// Output the original string data along with the deserialized object
- ///
- public bool OutputOriginalData { get; }
+ ///
+ public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
- ///
- public bool Authenticated => ApiCredentials != null;
+ ///
+ /// Get error info for a response code
+ ///
+ public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
- ///
- public ApiCredentials? ApiCredentials { get; set; }
+ ///
+ /// Get error info for a response code
+ ///
+ public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
- ///
- /// Api options
- ///
- public ApiOptions ApiOptions { get; }
+ ///
+ public void SetApiCredentials(T credentials) where T : ApiCredentials
+ {
+ ApiCredentials = credentials?.Copy();
+ if (ApiCredentials != null)
+ AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
+ }
- ///
- /// Client Options
- ///
- public ExchangeOptions ClientOptions { get; }
+ ///
+ public virtual void SetOptions(UpdateOptions options) where T : ApiCredentials
+ {
+ ClientOptions.Proxy = options.Proxy;
+ ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
- ///
- /// Mapping of a response code to known error types
- ///
- protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
+ ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
+ if (ApiCredentials != null)
+ AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
+ }
- ///
- /// ctor
- ///
- /// Logger
- /// Should data from this client include the original data in the call result
- /// Base address for this API client
- /// Api credentials
- /// Client options
- /// Api options
- protected BaseApiClient(ILogger logger, bool outputOriginalData, ApiCredentials? apiCredentials, string baseAddress, ExchangeOptions clientOptions, ApiOptions apiOptions)
- {
- _logger = logger;
-
- ClientOptions = clientOptions;
- ApiOptions = apiOptions;
- OutputOriginalData = outputOriginalData;
- BaseAddress = baseAddress;
- ApiCredentials = apiCredentials?.Copy();
-
- if (ApiCredentials != null)
- AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
- }
-
- ///
- /// Create an AuthenticationProvider implementation instance based on the provided credentials
- ///
- ///
- ///
- protected abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
-
- ///
- public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
-
- ///
- /// Get error info for a response code
- ///
- public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
-
- ///
- /// Get error info for a response code
- ///
- public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
-
- ///
- public void SetApiCredentials(T credentials) where T : ApiCredentials
- {
- ApiCredentials = credentials?.Copy();
- if (ApiCredentials != null)
- AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
- }
-
- ///
- public virtual void SetOptions(UpdateOptions options) where T : ApiCredentials
- {
- ClientOptions.Proxy = options.Proxy;
- ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
-
- ApiCredentials = options.ApiCredentials?.Copy() ?? ApiCredentials;
- if (ApiCredentials != null)
- AuthenticationProvider = CreateAuthenticationProvider(ApiCredentials);
- }
-
- ///
- /// Dispose
- ///
- public virtual void Dispose()
- {
- _disposing = true;
- }
+ ///
+ /// Dispose
+ ///
+ public virtual void Dispose()
+ {
+ _disposing = true;
}
}
diff --git a/CryptoExchange.Net/Clients/BaseClient.cs b/CryptoExchange.Net/Clients/BaseClient.cs
index 7523423..6a148c9 100644
--- a/CryptoExchange.Net/Clients/BaseClient.cs
+++ b/CryptoExchange.Net/Clients/BaseClient.cs
@@ -1,130 +1,128 @@
-using CryptoExchange.Net.Authentication;
+using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects.Options;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+/// The base for all clients, websocket client and rest client
+///
+public abstract class BaseClient : IDisposable
{
///
- /// The base for all clients, websocket client and rest client
+ /// Version of the CryptoExchange.Net base library
///
- public abstract class BaseClient : IDisposable
- {
- ///
- /// Version of the CryptoExchange.Net base library
- ///
- public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
+ public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
- ///
- /// Version of the client implementation
- ///
- public Version ExchangeLibVersion
- {
- get
+ ///
+ /// Version of the client implementation
+ ///
+ public Version ExchangeLibVersion
+ {
+ get
+ {
+ lock(_versionLock)
{
- lock(_versionLock)
- {
- if (_exchangeVersion == null)
- _exchangeVersion = GetType().Assembly.GetName().Version!;
+ if (_exchangeVersion == null)
+ _exchangeVersion = GetType().Assembly.GetName().Version!;
- return _exchangeVersion;
- }
+ return _exchangeVersion;
}
}
+ }
- ///
- /// The name of the API the client is for
- ///
- public string Exchange { get; }
+ ///
+ /// The name of the API the client is for
+ ///
+ public string Exchange { get; }
- ///
- /// Api clients in this client
- ///
- internal List ApiClients { get; } = new List();
+ ///
+ /// Api clients in this client
+ ///
+ internal List ApiClients { get; } = new List();
- ///
- /// The log object
- ///
- protected internal ILogger _logger;
+ ///
+ /// The log object
+ ///
+ protected internal ILogger _logger;
- private readonly object _versionLock = new object();
- private Version _exchangeVersion;
+ private readonly object _versionLock = new object();
+ private Version _exchangeVersion;
- ///
- /// Provided client options
- ///
- public ExchangeOptions ClientOptions { get; private set; }
+ ///
+ /// Provided client options
+ ///
+ public ExchangeOptions ClientOptions { get; private set; }
- ///
- /// ctor
- ///
- /// Logger
- /// The name of the exchange this client is for
+ ///
+ /// ctor
+ ///
+ /// Logger
+ /// The name of the exchange this client is for
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- protected BaseClient(ILoggerFactory? logger, string exchange)
+ protected BaseClient(ILoggerFactory? logger, string exchange)
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- {
- Exchange = exchange;
- }
+ {
+ Exchange = exchange;
+ }
- ///
- /// Initialize the client with the specified options
- ///
- ///
- ///
- protected virtual void Initialize(ExchangeOptions options)
- {
- if (options == null)
- throw new ArgumentNullException(nameof(options));
+ ///
+ /// Initialize the client with the specified options
+ ///
+ ///
+ ///
+ protected virtual void Initialize(ExchangeOptions options)
+ {
+ if (options == null)
+ throw new ArgumentNullException(nameof(options));
- ClientOptions = options;
- _logger.Log(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{CryptoExchangeLibVersion}, {Exchange}.Net: v{ExchangeLibVersion}");
- }
+ ClientOptions = options;
+ _logger.Log(LogLevel.Trace, "Client configuration: {Options}, CryptoExchange.Net: v{CryptoExchangeVersion}, {Exchange}.Net: v{ExchangeVersion}", options, CryptoExchangeLibVersion, Exchange, ExchangeLibVersion);
+ }
- ///
- /// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
- ///
- /// The credentials to set
- protected virtual void SetApiCredentials(T credentials) where T : ApiCredentials
- {
- foreach (var apiClient in ApiClients)
- apiClient.SetApiCredentials(credentials);
- }
+ ///
+ /// Set the API credentials for this client. All Api clients in this client will use the new credentials, regardless of earlier set options.
+ ///
+ /// The credentials to set
+ protected virtual void SetApiCredentials(T credentials) where T : ApiCredentials
+ {
+ foreach (var apiClient in ApiClients)
+ apiClient.SetApiCredentials(credentials);
+ }
- ///
- /// Register an API client
- ///
- /// The client
- protected T AddApiClient(T apiClient) where T : BaseApiClient
- {
- if (ClientOptions == null)
- throw new InvalidOperationException("Client should have called Initialize before adding API clients");
+ ///
+ /// Register an API client
+ ///
+ /// The client
+ protected T AddApiClient(T apiClient) where T : BaseApiClient
+ {
+ if (ClientOptions == null)
+ throw new InvalidOperationException("Client should have called Initialize before adding API clients");
- _logger.Log(LogLevel.Trace, $" {apiClient.GetType().Name}, base address: {apiClient.BaseAddress}");
- ApiClients.Add(apiClient);
- return apiClient;
- }
+ _logger.Log(LogLevel.Trace, " {ApiClient}, base address: {BaseAddress}", apiClient.GetType().Name, apiClient.BaseAddress);
+ ApiClients.Add(apiClient);
+ return apiClient;
+ }
- ///
- /// Apply the options delegate to a new options instance
- ///
- protected static T ApplyOptionsDelegate(Action? del) where T: new()
- {
- var opts = new T();
- del?.Invoke(opts);
- return opts;
- }
+ ///
+ /// Apply the options delegate to a new options instance
+ ///
+ protected static T ApplyOptionsDelegate(Action? del) where T: new()
+ {
+ var opts = new T();
+ del?.Invoke(opts);
+ return opts;
+ }
- ///
- /// Dispose
- ///
- public virtual void Dispose()
- {
- _logger.Log(LogLevel.Debug, "Disposing client");
- foreach (var client in ApiClients)
- client.Dispose();
- }
+ ///
+ /// Dispose
+ ///
+ public virtual void Dispose()
+ {
+ _logger.Log(LogLevel.Debug, "Disposing client");
+ foreach (var client in ApiClients)
+ client.Dispose();
}
}
diff --git a/CryptoExchange.Net/Clients/BaseRestClient.cs b/CryptoExchange.Net/Clients/BaseRestClient.cs
index bc5464b..fba0261 100644
--- a/CryptoExchange.Net/Clients/BaseRestClient.cs
+++ b/CryptoExchange.Net/Clients/BaseRestClient.cs
@@ -3,24 +3,23 @@ using CryptoExchange.Net.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
-namespace CryptoExchange.Net.Clients
-{
- ///
- /// Base rest client
- ///
- public abstract class BaseRestClient : BaseClient, IRestClient
- {
- ///
- public int TotalRequestsMade => ApiClients.OfType().Sum(s => s.TotalRequestsMade);
+namespace CryptoExchange.Net.Clients;
- ///
- /// ctor
- ///
- /// Logger factory
- /// The name of the API this client is for
- protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
- {
- _logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
- }
+///
+/// Base rest client
+///
+public abstract class BaseRestClient : BaseClient, IRestClient
+{
+ ///
+ public int TotalRequestsMade => ApiClients.OfType().Sum(s => s.TotalRequestsMade);
+
+ ///
+ /// ctor
+ ///
+ /// Logger factory
+ /// The name of the API this client is for
+ protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
+ {
+ _logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
}
}
diff --git a/CryptoExchange.Net/Clients/BaseSocketClient.cs b/CryptoExchange.Net/Clients/BaseSocketClient.cs
index 38f34e7..ef80ed0 100644
--- a/CryptoExchange.Net/Clients/BaseSocketClient.cs
+++ b/CryptoExchange.Net/Clients/BaseSocketClient.cs
@@ -1,132 +1,130 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using System.Xml.Linq;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+/// Base for socket client implementations
+///
+public abstract class BaseSocketClient : BaseClient, ISocketClient
{
+ #region fields
+
///
- /// Base for socket client implementations
+ /// If client is disposing
///
- public abstract class BaseSocketClient : BaseClient, ISocketClient
+ protected bool _disposing;
+
+ ///
+ public int CurrentConnections => ApiClients.OfType().Sum(c => c.CurrentConnections);
+ ///
+ public int CurrentSubscriptions => ApiClients.OfType().Sum(s => s.CurrentSubscriptions);
+ ///
+ public double IncomingKbps => ApiClients.OfType().Sum(s => s.IncomingKbps);
+ #endregion
+
+ ///
+ /// ctor
+ ///
+ /// Logger factory
+ /// The name of the exchange this client is for
+ protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
{
- #region fields
+ _logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
+ }
- ///
- /// If client is disposing
- ///
- protected bool _disposing;
-
- ///
- public int CurrentConnections => ApiClients.OfType().Sum(c => c.CurrentConnections);
- ///
- public int CurrentSubscriptions => ApiClients.OfType().Sum(s => s.CurrentSubscriptions);
- ///
- public double IncomingKbps => ApiClients.OfType().Sum(s => s.IncomingKbps);
- #endregion
-
- ///
- /// ctor
- ///
- /// Logger factory
- /// The name of the exchange this client is for
- protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name)
+ ///
+ /// Unsubscribe an update subscription
+ ///
+ /// The id of the subscription to unsubscribe
+ ///
+ public virtual async Task UnsubscribeAsync(int subscriptionId)
+ {
+ foreach (var socket in ApiClients.OfType())
{
- _logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name);
- }
-
- ///
- /// Unsubscribe an update subscription
- ///
- /// The id of the subscription to unsubscribe
- ///
- public virtual async Task UnsubscribeAsync(int subscriptionId)
- {
- foreach (var socket in ApiClients.OfType())
- {
- var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
- if (result)
- break;
- }
- }
-
- ///
- /// Unsubscribe an update subscription
- ///
- /// The subscription to unsubscribe
- ///
- public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
- {
- if (subscription == null)
- throw new ArgumentNullException(nameof(subscription));
-
- _logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
- await subscription.CloseAsync().ConfigureAwait(false);
- }
-
- ///
- /// Unsubscribe all subscriptions
- ///
- ///
- public virtual async Task UnsubscribeAllAsync()
- {
- var tasks = new List();
- foreach (var client in ApiClients.OfType())
- tasks.Add(client.UnsubscribeAllAsync());
-
- await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
- }
-
- ///
- /// Reconnect all connections
- ///
- ///
- public virtual async Task ReconnectAsync()
- {
- _logger.ReconnectingAllConnections(CurrentConnections);
- var tasks = new List();
- foreach (var client in ApiClients.OfType())
- {
- tasks.Add(client.ReconnectAsync());
- }
-
- await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
- }
-
- ///
- /// Log the current state of connections and subscriptions
- ///
- public string GetSubscriptionsState()
- {
- var result = new StringBuilder();
- foreach (var client in ApiClients.OfType().Where(c => c.CurrentSubscriptions > 0))
- {
- result.AppendLine(client.GetSubscriptionsState());
- }
-
- return result.ToString();
- }
-
- ///
- /// Returns the state of all socket api clients
- ///
- ///
- public List GetSocketApiClientStates()
- {
- var result = new List();
- foreach (var client in ApiClients.OfType())
- {
- result.Add(client.GetState());
- }
-
- return result;
+ var result = await socket.UnsubscribeAsync(subscriptionId).ConfigureAwait(false);
+ if (result)
+ break;
}
}
+
+ ///
+ /// Unsubscribe an update subscription
+ ///
+ /// The subscription to unsubscribe
+ ///
+ public virtual async Task UnsubscribeAsync(UpdateSubscription subscription)
+ {
+ if (subscription == null)
+ throw new ArgumentNullException(nameof(subscription));
+
+ _logger.UnsubscribingSubscription(subscription.SocketId, subscription.Id);
+ await subscription.CloseAsync().ConfigureAwait(false);
+ }
+
+ ///
+ /// Unsubscribe all subscriptions
+ ///
+ ///
+ public virtual async Task UnsubscribeAllAsync()
+ {
+ var tasks = new List();
+ foreach (var client in ApiClients.OfType())
+ tasks.Add(client.UnsubscribeAllAsync());
+
+ await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
+ }
+
+ ///
+ /// Reconnect all connections
+ ///
+ ///
+ public virtual async Task ReconnectAsync()
+ {
+ _logger.ReconnectingAllConnections(CurrentConnections);
+ var tasks = new List();
+ foreach (var client in ApiClients.OfType())
+ {
+ tasks.Add(client.ReconnectAsync());
+ }
+
+ await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
+ }
+
+ ///
+ /// Log the current state of connections and subscriptions
+ ///
+ public string GetSubscriptionsState()
+ {
+ var result = new StringBuilder();
+ foreach (var client in ApiClients.OfType().Where(c => c.CurrentSubscriptions > 0))
+ {
+ result.AppendLine(client.GetSubscriptionsState());
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Returns the state of all socket api clients
+ ///
+ ///
+ public List GetSocketApiClientStates()
+ {
+ var result = new List();
+ foreach (var client in ApiClients.OfType())
+ {
+ result.Add(client.GetState());
+ }
+
+ return result;
+ }
}
diff --git a/CryptoExchange.Net/Clients/CryptoBaseClient.cs b/CryptoExchange.Net/Clients/CryptoBaseClient.cs
index c781a66..4252dcc 100644
--- a/CryptoExchange.Net/Clients/CryptoBaseClient.cs
+++ b/CryptoExchange.Net/Clients/CryptoBaseClient.cs
@@ -1,67 +1,66 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+/// Base crypto client
+///
+public class CryptoBaseClient : IDisposable
{
+ private readonly Dictionary _serviceCache = new Dictionary();
+
///
- /// Base crypto client
+ /// Service provider
///
- public class CryptoBaseClient : IDisposable
+ protected readonly IServiceProvider? _serviceProvider;
+
+ ///
+ /// ctor
+ ///
+ public CryptoBaseClient() { }
+
+ ///
+ /// ctor
+ ///
+ ///
+ public CryptoBaseClient(IServiceProvider serviceProvider)
{
- private readonly Dictionary _serviceCache = new Dictionary();
+ _serviceProvider = serviceProvider;
+ _serviceCache = new Dictionary();
+ }
- ///
- /// Service provider
- ///
- protected readonly IServiceProvider? _serviceProvider;
+ ///
+ /// Try get a client by type for the service collection
+ ///
+ ///
+ ///
+ public T TryGet(Func createFunc)
+ {
+ var type = typeof(T);
+ if (_serviceCache.TryGetValue(type, out var value))
+ return (T)value;
- ///
- /// ctor
- ///
- public CryptoBaseClient() { }
-
- ///
- /// ctor
- ///
- ///
- public CryptoBaseClient(IServiceProvider serviceProvider)
+ if (_serviceProvider == null)
{
- _serviceProvider = serviceProvider;
- _serviceCache = new Dictionary();
+ // Create with default options
+ var createResult = createFunc();
+ _serviceCache.Add(typeof(T), createResult!);
+ return createResult;
}
- ///
- /// Try get a client by type for the service collection
- ///
- ///
- ///
- public T TryGet(Func createFunc)
- {
- var type = typeof(T);
- if (_serviceCache.TryGetValue(type, out var value))
- return (T)value;
+ var result = _serviceProvider.GetService()
+ ?? throw new InvalidOperationException($"No service was found for {typeof(T).Name}, make sure the exchange is registered in dependency injection with the `services.Add[Exchange]()` method");
+ _serviceCache.Add(type, result!);
+ return result;
+ }
- if (_serviceProvider == null)
- {
- // Create with default options
- var createResult = createFunc();
- _serviceCache.Add(typeof(T), createResult!);
- return createResult;
- }
-
- var result = _serviceProvider.GetService()
- ?? throw new InvalidOperationException($"No service was found for {typeof(T).Name}, make sure the exchange is registered in dependency injection with the `services.Add[Exchange]()` method");
- _serviceCache.Add(type, result!);
- return result;
- }
-
- ///
- /// Dispose
- ///
- public void Dispose()
- {
- _serviceCache.Clear();
- }
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ _serviceCache.Clear();
}
}
diff --git a/CryptoExchange.Net/Clients/CryptoRestClient.cs b/CryptoExchange.Net/Clients/CryptoRestClient.cs
index d4ee0bb..c582bc6 100644
--- a/CryptoExchange.Net/Clients/CryptoRestClient.cs
+++ b/CryptoExchange.Net/Clients/CryptoRestClient.cs
@@ -1,27 +1,23 @@
-using CryptoExchange.Net.Interfaces;
-using Microsoft.Extensions.DependencyInjection;
+using CryptoExchange.Net.Interfaces;
using System;
-using System.Collections.Generic;
-using System.Linq;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
{
- ///
- public class CryptoRestClient : CryptoBaseClient, ICryptoRestClient
+ ///
+ /// ctor
+ ///
+ public CryptoRestClient()
{
- ///
- /// ctor
- ///
- public CryptoRestClient()
- {
- }
+ }
- ///
- /// ctor
- ///
- ///
- public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
- {
- }
+ ///
+ /// ctor
+ ///
+ ///
+ public CryptoRestClient(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
}
}
\ No newline at end of file
diff --git a/CryptoExchange.Net/Clients/CryptoSocketClient.cs b/CryptoExchange.Net/Clients/CryptoSocketClient.cs
index 5c33ef9..8ed16b7 100644
--- a/CryptoExchange.Net/Clients/CryptoSocketClient.cs
+++ b/CryptoExchange.Net/Clients/CryptoSocketClient.cs
@@ -1,24 +1,23 @@
-using CryptoExchange.Net.Interfaces;
+using CryptoExchange.Net.Interfaces;
using System;
-namespace CryptoExchange.Net.Clients
-{
- ///
- public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
- {
- ///
- /// ctor
- ///
- public CryptoSocketClient()
- {
- }
+namespace CryptoExchange.Net.Clients;
- ///
- /// ctor
- ///
- ///
- public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
- {
- }
+///
+public class CryptoSocketClient : CryptoBaseClient, ICryptoSocketClient
+{
+ ///
+ /// ctor
+ ///
+ public CryptoSocketClient()
+ {
+ }
+
+ ///
+ /// ctor
+ ///
+ ///
+ public CryptoSocketClient(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
}
}
diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs
index 4f85a04..88f74e9 100644
--- a/CryptoExchange.Net/Clients/RestApiClient.cs
+++ b/CryptoExchange.Net/Clients/RestApiClient.cs
@@ -18,708 +18,707 @@ using CryptoExchange.Net.RateLimiting.Interfaces;
using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
-namespace CryptoExchange.Net.Clients
+namespace CryptoExchange.Net.Clients;
+
+///
+/// Base rest API client for interacting with a REST API
+///
+public abstract class RestApiClient : BaseApiClient, IRestApiClient
{
+ ///
+ public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
+
+ ///
+ public abstract TimeSyncInfo? GetTimeSyncInfo();
+
+ ///
+ public abstract TimeSpan? GetTimeOffset();
+
+ ///
+ public int TotalRequestsMade { get; set; }
+
///
- /// Base rest API client for interacting with a REST API
+ /// Request body content type
///
- public abstract class RestApiClient : BaseApiClient, IRestApiClient
+ protected internal RequestBodyFormat RequestBodyFormat = RequestBodyFormat.Json;
+
+ ///
+ /// How to serialize array parameters when making requests
+ ///
+ protected internal ArrayParametersSerialization ArraySerialization = ArrayParametersSerialization.Array;
+
+ ///
+ /// What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
+ ///
+ protected internal string RequestBodyEmptyContent = "{}";
+
+ ///
+ /// Request headers to be sent with each request
+ ///
+ protected Dictionary StandardRequestHeaders { get; set; } = [];
+
+ ///
+ /// Whether parameters need to be ordered
+ ///
+ protected internal bool OrderParameters { get; set; } = true;
+
+ ///
+ /// Parameter order comparer
+ ///
+ protected IComparer ParameterOrderComparer { get; } = new OrderedStringComparer();
+
+ ///
+ /// Where to put the parameters for requests with different Http methods
+ ///
+ public Dictionary ParameterPositions { get; set; } = new Dictionary
{
- ///
- public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
+ { HttpMethod.Get, HttpMethodParameterPosition.InUri },
+ { HttpMethod.Post, HttpMethodParameterPosition.InBody },
+ { HttpMethod.Delete, HttpMethodParameterPosition.InBody },
+ { HttpMethod.Put, HttpMethodParameterPosition.InBody },
+ { new HttpMethod("Patch"), HttpMethodParameterPosition.InBody },
+ };
- ///
- public abstract TimeSyncInfo? GetTimeSyncInfo();
+ ///
+ public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions;
- ///
- public abstract TimeSpan? GetTimeOffset();
+ ///
+ public new RestApiOptions ApiOptions => (RestApiOptions)base.ApiOptions;
- ///
- public int TotalRequestsMade { get; set; }
+ ///
+ /// Memory cache
+ ///
+ private readonly static MemoryCache _cache = new MemoryCache();
- ///
- /// Request body content type
- ///
- protected internal RequestBodyFormat RequestBodyFormat = RequestBodyFormat.Json;
+ ///
+ /// ctor
+ ///
+ /// Logger
+ /// HttpClient to use
+ /// Base address for this API client
+ /// The base client options
+ /// The Api client options
+ public RestApiClient(ILogger logger, HttpClient? httpClient, string baseAddress, RestExchangeOptions options, RestApiOptions apiOptions)
+ : base(logger,
+ apiOptions.OutputOriginalData ?? options.OutputOriginalData,
+ apiOptions.ApiCredentials ?? options.ApiCredentials,
+ baseAddress,
+ options,
+ apiOptions)
+ {
+ RequestFactory.Configure(options.Proxy, options.RequestTimeout, httpClient);
+ }
- ///
- /// How to serialize array parameters when making requests
- ///
- protected internal ArrayParametersSerialization ArraySerialization = ArrayParametersSerialization.Array;
+ ///
+ /// Create a message accessor instance
+ ///
+ ///
+ protected abstract IStreamMessageAccessor CreateAccessor();
- ///
- /// What request body should be set when no data is send (only used in combination with postParametersPosition.InBody)
- ///
- protected internal string RequestBodyEmptyContent = "{}";
+ ///
+ /// Create a serializer instance
+ ///
+ ///
+ protected abstract IMessageSerializer CreateSerializer();
- ///
- /// Request headers to be sent with each request
- ///
- protected Dictionary StandardRequestHeaders { get; set; } = [];
+ ///
+ /// Send a request to the base address based on the request definition
+ ///
+ /// Host and schema
+ /// Request definition
+ /// Request parameters
+ /// Cancellation token
+ /// Additional headers for this request
+ /// Override the request weight for this request definition, for example when the weight depends on the parameters
+ ///
+ protected virtual async Task SendAsync(
+ string baseAddress,
+ RequestDefinition definition,
+ ParameterCollection? parameters,
+ CancellationToken cancellationToken,
+ Dictionary? additionalHeaders = null,
+ int? weight = null)
+ {
+ var result = await SendAsync