mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-10 01:16:24 +00:00
Refactor clients/options
This commit is contained in:
parent
49de7e89cc
commit
3c3b5639f5
@ -11,27 +11,12 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
[TestFixture()]
|
[TestFixture()]
|
||||||
public class BaseClientTests
|
public class BaseClientTests
|
||||||
{
|
{
|
||||||
[TestCase(null, null)]
|
|
||||||
[TestCase("", "")]
|
|
||||||
[TestCase("test", null)]
|
|
||||||
[TestCase("test", "")]
|
|
||||||
[TestCase(null, "test")]
|
|
||||||
[TestCase("", "test")]
|
|
||||||
public void SettingEmptyValuesForAPICredentials_Should_ThrowException(string key, string secret)
|
|
||||||
{
|
|
||||||
// arrange
|
|
||||||
// act
|
|
||||||
// assert
|
|
||||||
Assert.Throws(typeof(ArgumentException),
|
|
||||||
() => new RestSubClientOptions() { ApiCredentials = new ApiCredentials(key, secret) });
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase]
|
[TestCase]
|
||||||
public void SettingLogOutput_Should_RedirectLogOutput()
|
public void SettingLogOutput_Should_RedirectLogOutput()
|
||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var logger = new TestStringLogger();
|
var logger = new TestStringLogger();
|
||||||
var client = new TestBaseClient(new RestClientOptions()
|
var client = new TestBaseClient(new BaseRestClientOptions()
|
||||||
{
|
{
|
||||||
LogWriters = new List<ILogger> { logger }
|
LogWriters = new List<ILogger> { logger }
|
||||||
});
|
});
|
||||||
@ -71,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
var logger = new TestStringLogger();
|
var logger = new TestStringLogger();
|
||||||
var options = new RestClientOptions()
|
var options = new BaseRestClientOptions()
|
||||||
{
|
{
|
||||||
LogWriters = new List<ILogger> { logger }
|
LogWriters = new List<ILogger> { logger }
|
||||||
};
|
};
|
||||||
|
300
CryptoExchange.Net.UnitTests/OptionsTests.cs
Normal file
300
CryptoExchange.Net.UnitTests/OptionsTests.cs
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
using CryptoExchange.Net.Authentication;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
|
using CryptoExchange.Net.UnitTests.TestImplementations;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.UnitTests
|
||||||
|
{
|
||||||
|
[TestFixture()]
|
||||||
|
public class OptionsTests
|
||||||
|
{
|
||||||
|
[TestCase(null, null)]
|
||||||
|
[TestCase("", "")]
|
||||||
|
[TestCase("test", null)]
|
||||||
|
[TestCase("test", "")]
|
||||||
|
[TestCase(null, "test")]
|
||||||
|
[TestCase("", "test")]
|
||||||
|
public void SettingEmptyValuesForAPICredentials_Should_ThrowException(string key, string secret)
|
||||||
|
{
|
||||||
|
// arrange
|
||||||
|
// act
|
||||||
|
// assert
|
||||||
|
Assert.Throws(typeof(ArgumentException),
|
||||||
|
() => new RestApiClientOptions() { ApiCredentials = new ApiCredentials(key, secret) });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicOptionsAreSet()
|
||||||
|
{
|
||||||
|
// arrange, act
|
||||||
|
var options = new TestClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
ReceiveWindow = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.ReceiveWindow, TimeSpan.FromSeconds(10));
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Secret.GetString(), "456");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApiOptionsAreSet()
|
||||||
|
{
|
||||||
|
// arrange, act
|
||||||
|
var options = new TestClientOptions
|
||||||
|
{
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
BaseAddress = "http://test1.com"
|
||||||
|
},
|
||||||
|
Api2Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("789", "101"),
|
||||||
|
BaseAddress = "http://test2.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Secret.GetString(), "456");
|
||||||
|
Assert.AreEqual(options.Api1Options.BaseAddress, "http://test1.com");
|
||||||
|
Assert.AreEqual(options.Api2Options.ApiCredentials.Key.GetString(), "789");
|
||||||
|
Assert.AreEqual(options.Api2Options.ApiCredentials.Secret.GetString(), "101");
|
||||||
|
Assert.AreEqual(options.Api2Options.BaseAddress, "http://test2.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNotOverridenApiOptionsAreStillDefault()
|
||||||
|
{
|
||||||
|
// arrange, act
|
||||||
|
var options = new TestClientOptions
|
||||||
|
{
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.Api1Options.RateLimitingBehaviour, RateLimitingBehaviour.Wait);
|
||||||
|
Assert.AreEqual(options.Api1Options.BaseAddress, "https://api1.test.com/");
|
||||||
|
Assert.AreEqual(options.Api2Options.BaseAddress, "https://api2.test.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSettingDefaultBaseOptionsAreRespected()
|
||||||
|
{
|
||||||
|
// arrange
|
||||||
|
TestClientOptions.Default = new TestClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
LogLevel = LogLevel.Trace
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
var options = new TestClientOptions();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.LogLevel, LogLevel.Trace);
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Secret.GetString(), "456");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSettingDefaultApiOptionsAreRespected()
|
||||||
|
{
|
||||||
|
// arrange
|
||||||
|
TestClientOptions.Default = new TestClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
LogLevel = LogLevel.Trace,
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("456", "789")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
var options = new TestClientOptions();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Secret.GetString(), "456");
|
||||||
|
Assert.AreEqual(options.Api1Options.BaseAddress, "https://api1.test.com/");
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Key.GetString(), "456");
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Secret.GetString(), "789");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSettingDefaultApiOptionsWithSomeOverriddenAreRespected()
|
||||||
|
{
|
||||||
|
// arrange
|
||||||
|
TestClientOptions.Default = new TestClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
LogLevel = LogLevel.Trace,
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("456", "789")
|
||||||
|
},
|
||||||
|
Api2Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("111", "222")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
var options = new TestClientOptions
|
||||||
|
{
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("333", "444")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// assert
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(options.ApiCredentials.Secret.GetString(), "456");
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Key.GetString(), "333");
|
||||||
|
Assert.AreEqual(options.Api1Options.ApiCredentials.Secret.GetString(), "444");
|
||||||
|
Assert.AreEqual(options.Api2Options.ApiCredentials.Key.GetString(), "111");
|
||||||
|
Assert.AreEqual(options.Api2Options.ApiCredentials.Secret.GetString(), "222");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClientUsesCorrectOptions()
|
||||||
|
{
|
||||||
|
var client = new TestRestClient(new TestClientOptions()
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("111", "222")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Key.GetString(), "111");
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Secret.GetString(), "222");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Secret.GetString(), "456");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClientUsesCorrectOptionsWithDefault()
|
||||||
|
{
|
||||||
|
TestClientOptions.Default = new TestClientOptions()
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("111", "222")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = new TestRestClient();
|
||||||
|
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Key.GetString(), "111");
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Secret.GetString(), "222");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Secret.GetString(), "456");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClientUsesCorrectOptionsWithOverridingDefault()
|
||||||
|
{
|
||||||
|
TestClientOptions.Default = new TestClientOptions()
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("123", "456"),
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("111", "222")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = new TestRestClient(new TestClientOptions
|
||||||
|
{
|
||||||
|
Api1Options = new RestApiClientOptions
|
||||||
|
{
|
||||||
|
ApiCredentials = new ApiCredentials("333", "444")
|
||||||
|
},
|
||||||
|
Api2Options = new RestApiClientOptions()
|
||||||
|
{
|
||||||
|
BaseAddress = "http://test.com"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Key.GetString(), "333");
|
||||||
|
Assert.AreEqual(client.Api1.AuthenticationProvider.Credentials.Secret.GetString(), "444");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Key.GetString(), "123");
|
||||||
|
Assert.AreEqual(client.Api2.AuthenticationProvider.Credentials.Secret.GetString(), "456");
|
||||||
|
Assert.AreEqual(client.Api2.BaseAddress, "http://test.com");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestClientOptions: BaseRestClientOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default options for the futures client
|
||||||
|
/// </summary>
|
||||||
|
public static TestClientOptions Default { get; set; } = new TestClientOptions()
|
||||||
|
{
|
||||||
|
Api1Options = new RestApiClientOptions(),
|
||||||
|
Api2Options = new RestApiClientOptions()
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default receive window for requests
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan ReceiveWindow { get; set; } = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
|
private RestApiClientOptions _api1Options = new RestApiClientOptions("https://api1.test.com/");
|
||||||
|
public RestApiClientOptions Api1Options
|
||||||
|
{
|
||||||
|
get => _api1Options;
|
||||||
|
set => _api1Options.Copy(_api1Options, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestApiClientOptions _api2Options = new RestApiClientOptions("https://api2.test.com/");
|
||||||
|
public RestApiClientOptions Api2Options
|
||||||
|
{
|
||||||
|
get => _api2Options;
|
||||||
|
set => _api2Options.Copy(_api2Options, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ctor
|
||||||
|
/// </summary>
|
||||||
|
public TestClientOptions()
|
||||||
|
{
|
||||||
|
if (Default == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Copy(this, Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the values of the def to the input
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="def"></param>
|
||||||
|
public new void Copy<T>(T input, T def) where T : TestClientOptions
|
||||||
|
{
|
||||||
|
base.Copy(input, def);
|
||||||
|
|
||||||
|
input.ReceiveWindow = def.ReceiveWindow;
|
||||||
|
|
||||||
|
input.Api1Options = new RestApiClientOptions(def.Api1Options);
|
||||||
|
input.Api2Options = new RestApiClientOptions(def.Api2Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,9 +106,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
// act
|
// act
|
||||||
var client = new TestRestClient(new TestRestClientOptions()
|
var client = new TestRestClient(new TestClientOptions()
|
||||||
{
|
{
|
||||||
SubOptions = new RestSubClientOptions
|
Api1Options = new RestApiClientOptions
|
||||||
{
|
{
|
||||||
BaseAddress = "http://test.address.com",
|
BaseAddress = "http://test.address.com",
|
||||||
RateLimiters = new List<IRateLimiter> { new RateLimiter() },
|
RateLimiters = new List<IRateLimiter> { new RateLimiter() },
|
||||||
@ -119,9 +119,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.IsTrue(((TestRestClientOptions)client.ClientOptions).SubOptions.BaseAddress == "http://test.address.com");
|
Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.BaseAddress == "http://test.address.com");
|
||||||
Assert.IsTrue(((TestRestClientOptions)client.ClientOptions).SubOptions.RateLimiters.Count == 1);
|
Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimiters.Count == 1);
|
||||||
Assert.IsTrue(((TestRestClientOptions)client.ClientOptions).SubOptions.RateLimitingBehaviour == RateLimitingBehaviour.Fail);
|
Assert.IsTrue(((TestClientOptions)client.ClientOptions).Api1Options.RateLimitingBehaviour == RateLimitingBehaviour.Fail);
|
||||||
Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1));
|
Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,9 +136,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
// arrange
|
// arrange
|
||||||
// act
|
// act
|
||||||
var client = new TestRestClient(new TestRestClientOptions()
|
var client = new TestRestClient(new TestClientOptions()
|
||||||
{
|
{
|
||||||
SubOptions = new RestSubClientOptions
|
Api1Options = new RestApiClientOptions
|
||||||
{
|
{
|
||||||
BaseAddress = "http://test.address.com"
|
BaseAddress = "http://test.address.com"
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
//act
|
//act
|
||||||
var client = new TestSocketClient(new TestOptions()
|
var client = new TestSocketClient(new TestOptions()
|
||||||
{
|
{
|
||||||
SubOptions = new SubClientOptions
|
SubOptions = new RestApiClientOptions
|
||||||
{
|
{
|
||||||
BaseAddress = "http://test.address.com"
|
BaseAddress = "http://test.address.com"
|
||||||
},
|
},
|
||||||
|
@ -8,11 +8,11 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
public class TestBaseClient: BaseClient
|
public class TestBaseClient: BaseClient
|
||||||
{
|
{
|
||||||
public TestBaseClient(): base("Test", new RestClientOptions())
|
public TestBaseClient(): base("Test", new BaseClientOptions())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestBaseClient(RestClientOptions exchangeOptions) : base("Test", exchangeOptions)
|
public TestBaseClient(BaseRestClientOptions exchangeOptions) : base("Test", exchangeOptions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,17 +15,19 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||||
{
|
{
|
||||||
public class TestRestClient: RestClient
|
public class TestRestClient: BaseRestClient
|
||||||
{
|
{
|
||||||
public TestRestSubClient SubClient { get; }
|
public TestRestApi1Client Api1 { get; }
|
||||||
|
public TestRestApi2Client Api2 { get; }
|
||||||
|
|
||||||
public TestRestClient() : this(new TestRestClientOptions())
|
public TestRestClient() : this(new TestClientOptions())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestRestClient(TestRestClientOptions exchangeOptions) : base("Test", exchangeOptions)
|
public TestRestClient(TestClientOptions exchangeOptions) : base("Test", exchangeOptions)
|
||||||
{
|
{
|
||||||
SubClient = new TestRestSubClient(exchangeOptions);
|
Api1 = new TestRestApi1Client(exchangeOptions);
|
||||||
|
Api2 = new TestRestApi2Client(exchangeOptions);
|
||||||
RequestFactory = new Mock<IRequestFactory>().Object;
|
RequestFactory = new Mock<IRequestFactory>().Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,26 +105,35 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
|
|
||||||
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T:class
|
public async Task<CallResult<T>> Request<T>(CancellationToken ct = default) where T:class
|
||||||
{
|
{
|
||||||
return await SendRequestAsync<T>(SubClient, new Uri("http://www.test.com"), HttpMethod.Get, ct);
|
return await SendRequestAsync<T>(Api1, new Uri("http://www.test.com"), HttpMethod.Get, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
|
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
|
||||||
{
|
{
|
||||||
return await SendRequestAsync<T>(SubClient, new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers);
|
return await SendRequestAsync<T>(Api1, new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestRestSubClient: RestSubClient
|
public class TestRestApi1Client : RestApiClient
|
||||||
{
|
{
|
||||||
public TestRestSubClient(TestRestClientOptions options): base(options.SubOptions, null)
|
public TestRestApi1Client(TestClientOptions options): base(options, options.Api1Options)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
||||||
|
=> new TestAuthProvider(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestRestClientOptions: RestClientOptions
|
public class TestRestApi2Client : RestApiClient
|
||||||
{
|
{
|
||||||
public RestSubClientOptions SubOptions { get; set; } = new RestSubClientOptions();
|
public TestRestApi2Client(TestClientOptions options) : base(options, options.Api2Options)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
||||||
|
=> new TestAuthProvider(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestAuthProvider : AuthenticationProvider
|
public class TestAuthProvider : AuthenticationProvider
|
||||||
@ -135,7 +146,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
public class ParseErrorTestRestClient: TestRestClient
|
public class ParseErrorTestRestClient: TestRestClient
|
||||||
{
|
{
|
||||||
public ParseErrorTestRestClient() { }
|
public ParseErrorTestRestClient() { }
|
||||||
public ParseErrorTestRestClient(TestRestClientOptions exchangeOptions) : base(exchangeOptions) { }
|
public ParseErrorTestRestClient(TestClientOptions exchangeOptions) : base(exchangeOptions) { }
|
||||||
|
|
||||||
protected override Error ParseErrorResponse(JToken error)
|
protected override Error ParseErrorResponse(JToken error)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
@ -9,7 +10,7 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||||
{
|
{
|
||||||
public class TestSocketClient: SocketClient
|
public class TestSocketClient: BaseSocketClient
|
||||||
{
|
{
|
||||||
public TestSubSocketClient SubClient { get; }
|
public TestSubSocketClient SubClient { get; }
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
|
|
||||||
public TestSocketClient(TestOptions exchangeOptions) : base("test", exchangeOptions)
|
public TestSocketClient(TestOptions exchangeOptions) : base("test", exchangeOptions)
|
||||||
{
|
{
|
||||||
SubClient = new TestSubSocketClient(exchangeOptions.SubOptions);
|
SubClient = new TestSubSocketClient(exchangeOptions, exchangeOptions.SubOptions);
|
||||||
SocketFactory = new Mock<IWebsocketFactory>().Object;
|
SocketFactory = new Mock<IWebsocketFactory>().Object;
|
||||||
Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<string>())).Returns(new TestSocket());
|
Mock.Get(SocketFactory).Setup(f => f.CreateWebsocket(It.IsAny<Log>(), It.IsAny<string>())).Returns(new TestSocket());
|
||||||
}
|
}
|
||||||
@ -67,17 +68,20 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestOptions: SocketClientOptions
|
public class TestOptions: BaseSocketClientOptions
|
||||||
{
|
{
|
||||||
public SubClientOptions SubOptions { get; set; } = new SubClientOptions();
|
public ApiClientOptions SubOptions { get; set; } = new ApiClientOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestSubSocketClient : SocketSubClient
|
public class TestSubSocketClient : SocketApiClient
|
||||||
{
|
{
|
||||||
|
|
||||||
public TestSubSocketClient(SubClientOptions options): base(options, options.ApiCredentials == null ? null: new TestAuthProvider(options.ApiCredentials))
|
public TestSubSocketClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials)
|
||||||
|
=> new TestAuthProvider(credentials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
CryptoExchange.Net/Clients/BaseApiClient.cs
Normal file
57
CryptoExchange.Net/Clients/BaseApiClient.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using CryptoExchange.Net.Authentication;
|
||||||
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Objects;
|
||||||
|
using CryptoExchange.Net.Requests;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base rest client
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseApiClient: IDisposable
|
||||||
|
{
|
||||||
|
private ApiCredentials? _apiCredentials;
|
||||||
|
private AuthenticationProvider _authenticationProvider;
|
||||||
|
public AuthenticationProvider? AuthenticationProvider
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_authenticationProvider == null && _apiCredentials != null)
|
||||||
|
_authenticationProvider = CreateAuthenticationProvider(_apiCredentials);
|
||||||
|
|
||||||
|
return _authenticationProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal protected string BaseAddress { get; }
|
||||||
|
|
||||||
|
public BaseApiClient(BaseClientOptions options, ApiClientOptions apiOptions)
|
||||||
|
{
|
||||||
|
_apiCredentials = apiOptions.ApiCredentials ?? options.ApiCredentials;
|
||||||
|
BaseAddress = apiOptions.BaseAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
AuthenticationProvider?.Credentials?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,14 +46,14 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provided client options
|
/// Provided client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClientOptions ClientOptions { get; }
|
public BaseClientOptions ClientOptions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
||||||
/// <param name="options">The options for this client</param>
|
/// <param name="options">The options for this client</param>
|
||||||
protected BaseClient(string exchangeName, ClientOptions options)
|
protected BaseClient(string exchangeName, BaseClientOptions options)
|
||||||
{
|
{
|
||||||
log = new Log(exchangeName);
|
log = new Log(exchangeName);
|
||||||
log.UpdateWriters(options.LogWriters);
|
log.UpdateWriters(options.LogWriters);
|
@ -21,7 +21,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base rest client
|
/// Base rest client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RestClient : BaseClient, IRestClient
|
public abstract class BaseRestClient : BaseClient, IRestClient
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The factory for creating requests. Used for unit testing
|
/// The factory for creating requests. Used for unit testing
|
||||||
@ -70,14 +70,14 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client options
|
/// Client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new RestClientOptions ClientOptions { get; }
|
public new BaseRestClientOptions ClientOptions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
||||||
/// <param name="exchangeOptions">The options for this client</param>
|
/// <param name="exchangeOptions">The options for this client</param>
|
||||||
protected RestClient(string exchangeName, RestClientOptions exchangeOptions) : base(exchangeName, exchangeOptions)
|
protected BaseRestClient(string exchangeName, BaseRestClientOptions exchangeOptions) : base(exchangeName, exchangeOptions)
|
||||||
{
|
{
|
||||||
if (exchangeOptions == null)
|
if (exchangeOptions == null)
|
||||||
throw new ArgumentNullException(nameof(exchangeOptions));
|
throw new ArgumentNullException(nameof(exchangeOptions));
|
||||||
@ -104,7 +104,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[return: NotNull]
|
[return: NotNull]
|
||||||
protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(
|
protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(
|
||||||
RestSubClient subClient,
|
RestApiClient apiClient,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
@ -119,17 +119,17 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
var requestId = NextId();
|
var requestId = NextId();
|
||||||
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
||||||
if (signed && subClient.AuthenticationProvider == null)
|
if (signed && apiClient.AuthenticationProvider == null)
|
||||||
{
|
{
|
||||||
log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
|
log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
|
||||||
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError());
|
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError());
|
||||||
}
|
}
|
||||||
|
|
||||||
var paramsPosition = parameterPosition ?? ParameterPositions[method];
|
var paramsPosition = parameterPosition ?? ParameterPositions[method];
|
||||||
var request = ConstructRequest(subClient, uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
|
var request = ConstructRequest(apiClient, uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
|
||||||
foreach (var limiter in subClient.RateLimiters)
|
foreach (var limiter in apiClient.RateLimiters)
|
||||||
{
|
{
|
||||||
var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, subClient.Options.ApiCredentials?.Key, subClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
|
var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, apiClient.Options.ApiCredentials?.Key, apiClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
|
||||||
if (!limitResult.Success)
|
if (!limitResult.Success)
|
||||||
return new WebCallResult<T>(null, null, null, limitResult.Error);
|
return new WebCallResult<T>(null, null, null, limitResult.Error);
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="additionalHeaders">Additional headers to send with the request</param>
|
/// <param name="additionalHeaders">Additional headers to send with the request</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual IRequest ConstructRequest(
|
protected virtual IRequest ConstructRequest(
|
||||||
SubClient subClient,
|
RestApiClient apiClient,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
Dictionary<string, object>? parameters,
|
Dictionary<string, object>? parameters,
|
||||||
@ -280,8 +280,8 @@ namespace CryptoExchange.Net
|
|||||||
parameters ??= new Dictionary<string, object>();
|
parameters ??= new Dictionary<string, object>();
|
||||||
|
|
||||||
var uriString = uri.ToString();
|
var uriString = uri.ToString();
|
||||||
if (subClient.AuthenticationProvider != null)
|
if (apiClient.AuthenticationProvider != null)
|
||||||
parameters = subClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
|
parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
|
||||||
|
|
||||||
if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
|
if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
|
||||||
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
||||||
@ -291,8 +291,8 @@ namespace CryptoExchange.Net
|
|||||||
request.Accept = Constants.JsonContentHeader;
|
request.Accept = Constants.JsonContentHeader;
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
if (subClient.AuthenticationProvider != null)
|
if (apiClient.AuthenticationProvider != null)
|
||||||
headers = subClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
|
headers = apiClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
|
||||||
|
|
||||||
foreach (var header in headers)
|
foreach (var header in headers)
|
||||||
request.AddHeader(header.Key, header.Value);
|
request.AddHeader(header.Key, header.Value);
|
@ -19,7 +19,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base for socket client implementations
|
/// Base for socket client implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SocketClient: BaseClient, ISocketClient
|
public abstract class BaseSocketClient: BaseClient, ISocketClient
|
||||||
{
|
{
|
||||||
#region fields
|
#region fields
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -95,7 +95,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client options
|
/// Client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new SocketClientOptions ClientOptions { get; }
|
public new BaseSocketClientOptions ClientOptions { get; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
/// <param name="exchangeName">The name of the exchange this client is for</param>
|
||||||
/// <param name="exchangeOptions">The options for this client</param>
|
/// <param name="exchangeOptions">The options for this client</param>
|
||||||
protected SocketClient(string exchangeName, SocketClientOptions exchangeOptions): base(exchangeName, exchangeOptions)
|
protected BaseSocketClient(string exchangeName, BaseSocketClientOptions exchangeOptions): base(exchangeName, exchangeOptions)
|
||||||
{
|
{
|
||||||
if (exchangeOptions == null)
|
if (exchangeOptions == null)
|
||||||
throw new ArgumentNullException(nameof(exchangeOptions));
|
throw new ArgumentNullException(nameof(exchangeOptions));
|
||||||
@ -133,9 +133,9 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="dataHandler">The handler of update data</param>
|
/// <param name="dataHandler">The handler of update data</param>
|
||||||
/// <param name="ct">Cancellation token for closing this subscription</param>
|
/// <param name="ct">Cancellation token for closing this subscription</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual Task<CallResult<UpdateSubscription>> SubscribeAsync<T>(SocketSubClient subClient, object? request, string? identifier, bool authenticated, Action<DataEvent<T>> dataHandler, CancellationToken ct)
|
protected virtual Task<CallResult<UpdateSubscription>> SubscribeAsync<T>(SocketApiClient apiClient, object? request, string? identifier, bool authenticated, Action<DataEvent<T>> dataHandler, CancellationToken ct)
|
||||||
{
|
{
|
||||||
return SubscribeAsync(subClient, subClient.Options.BaseAddress, request, identifier, authenticated, dataHandler, ct);
|
return SubscribeAsync(apiClient, apiClient.Options.BaseAddress, request, identifier, authenticated, dataHandler, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -149,7 +149,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="dataHandler">The handler of update data</param>
|
/// <param name="dataHandler">The handler of update data</param>
|
||||||
/// <param name="ct">Cancellation token for closing this subscription</param>
|
/// <param name="ct">Cancellation token for closing this subscription</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<CallResult<UpdateSubscription>> SubscribeAsync<T>(SocketSubClient subClient, string url, object? request, string? identifier, bool authenticated, Action<DataEvent<T>> dataHandler, CancellationToken ct)
|
protected virtual async Task<CallResult<UpdateSubscription>> SubscribeAsync<T>(SocketApiClient apiClient, string url, object? request, string? identifier, bool authenticated, Action<DataEvent<T>> dataHandler, CancellationToken ct)
|
||||||
{
|
{
|
||||||
SocketConnection socketConnection;
|
SocketConnection socketConnection;
|
||||||
SocketSubscription subscription;
|
SocketSubscription subscription;
|
||||||
@ -168,7 +168,7 @@ namespace CryptoExchange.Net
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get a new or existing socket connection
|
// Get a new or existing socket connection
|
||||||
socketConnection = GetSocketConnection(subClient, url, authenticated);
|
socketConnection = GetSocketConnection(apiClient, url, authenticated);
|
||||||
|
|
||||||
// Add a subscription on the socket connection
|
// Add a subscription on the socket connection
|
||||||
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
|
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
|
||||||
@ -253,9 +253,9 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="request">The request to send, will be serialized to json</param>
|
/// <param name="request">The request to send, will be serialized to json</param>
|
||||||
/// <param name="authenticated">If the query is to an authenticated endpoint</param>
|
/// <param name="authenticated">If the query is to an authenticated endpoint</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual Task<CallResult<T>> QueryAsync<T>(SocketSubClient subClient, object request, bool authenticated)
|
protected virtual Task<CallResult<T>> QueryAsync<T>(SocketApiClient apiClient, object request, bool authenticated)
|
||||||
{
|
{
|
||||||
return QueryAsync<T>(subClient, subClient.Options.BaseAddress, request, authenticated);
|
return QueryAsync<T>(apiClient, apiClient.Options.BaseAddress, request, authenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -266,14 +266,14 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="request">The request to send</param>
|
/// <param name="request">The request to send</param>
|
||||||
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<CallResult<T>> QueryAsync<T>(SocketSubClient subClient, string url, object request, bool authenticated)
|
protected virtual async Task<CallResult<T>> QueryAsync<T>(SocketApiClient apiClient, string url, object request, bool authenticated)
|
||||||
{
|
{
|
||||||
SocketConnection socketConnection;
|
SocketConnection socketConnection;
|
||||||
var released = false;
|
var released = false;
|
||||||
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
socketConnection = GetSocketConnection(subClient, url, authenticated);
|
socketConnection = GetSocketConnection(apiClient, url, authenticated);
|
||||||
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
|
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
|
||||||
{
|
{
|
||||||
// Can release early when only a single sub per connection
|
// Can release early when only a single sub per connection
|
||||||
@ -480,10 +480,10 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="address">The address the socket is for</param>
|
/// <param name="address">The address the socket is for</param>
|
||||||
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual SocketConnection GetSocketConnection(SocketSubClient subClient, string address, bool authenticated)
|
protected virtual SocketConnection GetSocketConnection(SocketApiClient apiClient, string address, bool authenticated)
|
||||||
{
|
{
|
||||||
var socketResult = sockets.Where(s => s.Value.Socket.Url.TrimEnd('/') == address.TrimEnd('/')
|
var socketResult = sockets.Where(s => s.Value.Socket.Url.TrimEnd('/') == address.TrimEnd('/')
|
||||||
&& (s.Value.SubClient.GetType() == subClient.GetType())
|
&& (s.Value.ApiClient.GetType() == apiClient.GetType())
|
||||||
&& (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.SubscriptionCount).FirstOrDefault();
|
&& (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.SubscriptionCount).FirstOrDefault();
|
||||||
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
|
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
|
||||||
if (result != null)
|
if (result != null)
|
||||||
@ -497,7 +497,7 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
// Create new socket
|
// Create new socket
|
||||||
var socket = CreateSocket(address);
|
var socket = CreateSocket(address);
|
||||||
var socketConnection = new SocketConnection(this, subClient, socket);
|
var socketConnection = new SocketConnection(this, apiClient, socket);
|
||||||
socketConnection.UnhandledMessage += HandleUnhandledMessage;
|
socketConnection.UnhandledMessage += HandleUnhandledMessage;
|
||||||
foreach (var kvp in genericHandlers)
|
foreach (var kvp in genericHandlers)
|
||||||
{
|
{
|
@ -21,21 +21,21 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base rest client
|
/// Base rest client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RestSubClient: SubClient
|
public abstract class RestApiClient: BaseApiClient
|
||||||
{
|
{
|
||||||
internal RestSubClientOptions Options { get; }
|
internal RestApiClientOptions Options { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of rate limiters
|
/// List of rate limiters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
||||||
|
|
||||||
public RestSubClient(RestSubClientOptions options, AuthenticationProvider? authProvider): base(options,authProvider)
|
public RestApiClient(BaseRestClientOptions options, RestApiClientOptions apiOptions): base(options, apiOptions)
|
||||||
{
|
{
|
||||||
Options = options;
|
Options = apiOptions;
|
||||||
|
|
||||||
var rateLimiters = new List<IRateLimiter>();
|
var rateLimiters = new List<IRateLimiter>();
|
||||||
foreach (var rateLimiter in options.RateLimiters)
|
foreach (var rateLimiter in apiOptions.RateLimiters)
|
||||||
rateLimiters.Add(rateLimiter);
|
rateLimiters.Add(rateLimiter);
|
||||||
RateLimiters = rateLimiters;
|
RateLimiters = rateLimiters;
|
||||||
}
|
}
|
@ -21,13 +21,13 @@ namespace CryptoExchange.Net
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base rest client
|
/// Base rest client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SocketSubClient : SubClient
|
public abstract class SocketApiClient : BaseApiClient
|
||||||
{
|
{
|
||||||
internal SubClientOptions Options { get; }
|
internal ApiClientOptions Options { get; }
|
||||||
|
|
||||||
public SocketSubClient(SubClientOptions options, AuthenticationProvider? authProvider): base(options,authProvider)
|
public SocketApiClient(BaseClientOptions options, ApiClientOptions apiOptions): base(options, apiOptions)
|
||||||
{
|
{
|
||||||
Options = options;
|
Options = apiOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -21,6 +21,6 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The options provided for this client
|
/// The options provided for this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RestClientOptions ClientOptions { get; }
|
BaseRestClientOptions ClientOptions { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The options provided for this client
|
/// The options provided for this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SocketClientOptions ClientOptions { get; }
|
BaseSocketClientOptions ClientOptions { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Incoming kilobytes per second of data
|
/// Incoming kilobytes per second of data
|
||||||
|
@ -50,25 +50,17 @@ namespace CryptoExchange.Net.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base for order book options
|
/// Base client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OrderBookOptions : BaseOptions
|
public class BaseClientOptions : BaseOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
|
/// Proxy to use when connecting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ChecksumValidationEnabled { get; set; } = true;
|
public ApiProxy? Proxy { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class SubClientOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The base address of the sub client
|
|
||||||
/// </summary>
|
|
||||||
public string BaseAddress { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The api credentials used for signing requests
|
/// Api credentials to be used for all api clients unless overriden
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiCredentials? ApiCredentials { get; set; }
|
public ApiCredentials? ApiCredentials { get; set; }
|
||||||
|
|
||||||
@ -78,40 +70,12 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <param name="def"></param>
|
/// <param name="def"></param>
|
||||||
public new void Copy<T>(T input, T def) where T : SubClientOptions
|
public new void Copy<T>(T input, T def) where T : BaseClientOptions
|
||||||
{
|
|
||||||
input.BaseAddress = def.BaseAddress;
|
|
||||||
input.ApiCredentials = def.ApiCredentials?.Copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{base.ToString()}, Credentials: {(ApiCredentials == null ? "-" : "Set")}, BaseAddress: {BaseAddress}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base client options
|
|
||||||
/// </summary>
|
|
||||||
public class ClientOptions : BaseOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Proxy to use when connecting
|
|
||||||
/// </summary>
|
|
||||||
public ApiProxy? Proxy { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copy the values of the def to the input
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <param name="def"></param>
|
|
||||||
public new void Copy<T>(T input, T def) where T : ClientOptions
|
|
||||||
{
|
{
|
||||||
base.Copy(input, def);
|
base.Copy(input, def);
|
||||||
|
|
||||||
input.Proxy = def.Proxy;
|
input.Proxy = def.Proxy;
|
||||||
|
input.ApiCredentials = def.ApiCredentials?.Copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -121,43 +85,10 @@ namespace CryptoExchange.Net.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RestSubClientOptions: SubClientOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// List of rate limiters to use
|
|
||||||
/// </summary>
|
|
||||||
public List<IRateLimiter> RateLimiters { get; set; } = new List<IRateLimiter>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What to do when a call would exceed the rate limit
|
|
||||||
/// </summary>
|
|
||||||
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copy the values of the def to the input
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <param name="def"></param>
|
|
||||||
public new void Copy<T>(T input, T def) where T : RestSubClientOptions
|
|
||||||
{
|
|
||||||
base.Copy(input, def);
|
|
||||||
|
|
||||||
input.RateLimiters = def.RateLimiters.ToList();
|
|
||||||
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{base.ToString()}, RateLimiters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base for rest client options
|
/// Base for rest client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RestClientOptions : ClientOptions
|
public class BaseRestClientOptions : BaseClientOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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
|
||||||
@ -175,7 +106,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <param name="def"></param>
|
/// <param name="def"></param>
|
||||||
public new void Copy<T>(T input, T def) where T : RestClientOptions
|
public new void Copy<T>(T input, T def) where T : BaseRestClientOptions
|
||||||
{
|
{
|
||||||
base.Copy(input, def);
|
base.Copy(input, def);
|
||||||
|
|
||||||
@ -186,14 +117,14 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{base.ToString()}, RequestTimeout: {RequestTimeout:c}, HttpClient: {(HttpClient == null ? "-": "set")}";
|
return $"{base.ToString()}, RequestTimeout: {RequestTimeout:c}, HttpClient: {(HttpClient == null ? "-" : "set")}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base for socket client options
|
/// Base for socket client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SocketClientOptions : ClientOptions
|
public class BaseSocketClientOptions : BaseClientOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not the socket should automatically reconnect when losing connection
|
/// Whether or not the socket should automatically reconnect when losing connection
|
||||||
@ -244,7 +175,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
/// <param name="def"></param>
|
/// <param name="def"></param>
|
||||||
public new void Copy<T>(T input, T def) where T : SocketClientOptions
|
public new void Copy<T>(T input, T def) where T : BaseSocketClientOptions
|
||||||
{
|
{
|
||||||
base.Copy(input, def);
|
base.Copy(input, def);
|
||||||
|
|
||||||
@ -264,4 +195,110 @@ namespace CryptoExchange.Net.Objects
|
|||||||
return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, MaxReconnectTries: {MaxReconnectTries}, MaxResubscribeTries: {MaxResubscribeTries}, MaxConcurrentResubscriptionsPerSocket: {MaxConcurrentResubscriptionsPerSocket}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketNoDataTimeout: {SocketNoDataTimeout}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
|
return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, MaxReconnectTries: {MaxReconnectTries}, MaxResubscribeTries: {MaxResubscribeTries}, MaxConcurrentResubscriptionsPerSocket: {MaxConcurrentResubscriptionsPerSocket}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketNoDataTimeout: {SocketNoDataTimeout}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ApiClientOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the CallResult and DataEvent objects will also include the originally received json data in the OriginalData property
|
||||||
|
/// </summary>
|
||||||
|
public string BaseAddress { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The api credentials used for signing requests
|
||||||
|
/// </summary>
|
||||||
|
public ApiCredentials? ApiCredentials { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public ApiClientOptions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiClientOptions(string baseAddres)
|
||||||
|
{
|
||||||
|
BaseAddress = baseAddres;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiClientOptions(ApiClientOptions baseOn)
|
||||||
|
{
|
||||||
|
Copy(this, baseOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the values of the def to the input
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="def"></param>
|
||||||
|
public void Copy<T>(T input, T def) where T : ApiClientOptions
|
||||||
|
{
|
||||||
|
if (def.BaseAddress != null)
|
||||||
|
input.BaseAddress = def.BaseAddress;
|
||||||
|
input.ApiCredentials = def.ApiCredentials?.Copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{base.ToString()}, Credentials: {(ApiCredentials == null ? "-" : "Set")}, BaseAddress: {BaseAddress}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RestApiClientOptions: ApiClientOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of rate limiters to use
|
||||||
|
/// </summary>
|
||||||
|
public List<IRateLimiter> RateLimiters { get; set; } = new List<IRateLimiter>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What to do when a call would exceed the rate limit
|
||||||
|
/// </summary>
|
||||||
|
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
||||||
|
|
||||||
|
public RestApiClientOptions()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestApiClientOptions(string baseAddress): base(baseAddress)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestApiClientOptions(RestApiClientOptions baseOn)
|
||||||
|
{
|
||||||
|
Copy(this, baseOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the values of the def to the input
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="def"></param>
|
||||||
|
public new void Copy<T>(T input, T def) where T : RestApiClientOptions
|
||||||
|
{
|
||||||
|
base.Copy(input, def);
|
||||||
|
|
||||||
|
if(def.RateLimiters != null)
|
||||||
|
input.RateLimiters = def.RateLimiters.ToList();
|
||||||
|
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base for order book options
|
||||||
|
/// </summary>
|
||||||
|
public class OrderBookOptions : BaseOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not checksum validation is enabled. Default is true, disabling will ignore checksum messages.
|
||||||
|
/// </summary>
|
||||||
|
public bool ChecksumValidationEnabled { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,10 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IWebsocket Socket { get; set; }
|
public IWebsocket Socket { get; set; }
|
||||||
|
|
||||||
public SocketSubClient SubClient { get; set; }
|
/// <summary>
|
||||||
|
/// The API client the connection is for
|
||||||
|
/// </summary>
|
||||||
|
public SocketApiClient ApiClient { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the socket should be reconnected upon closing
|
/// If the socket should be reconnected upon closing
|
||||||
@ -128,7 +131,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
private bool lostTriggered;
|
private bool lostTriggered;
|
||||||
private readonly Log log;
|
private readonly Log log;
|
||||||
private readonly SocketClient socketClient;
|
private readonly BaseSocketClient socketClient;
|
||||||
|
|
||||||
private readonly List<PendingRequest> pendingRequests;
|
private readonly List<PendingRequest> pendingRequests;
|
||||||
|
|
||||||
@ -136,12 +139,13 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// New socket connection
|
/// New socket connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">The socket client</param>
|
/// <param name="client">The socket client</param>
|
||||||
|
/// <param name="apiClient">The api client</param>
|
||||||
/// <param name="socket">The socket</param>
|
/// <param name="socket">The socket</param>
|
||||||
public SocketConnection(SocketClient client, SocketSubClient subClient, IWebsocket socket)
|
public SocketConnection(BaseSocketClient client, SocketApiClient apiClient, IWebsocket socket)
|
||||||
{
|
{
|
||||||
log = client.log;
|
log = client.log;
|
||||||
socketClient = client;
|
socketClient = client;
|
||||||
SubClient = subClient;
|
ApiClient = apiClient;
|
||||||
|
|
||||||
pendingRequests = new List<PendingRequest>();
|
pendingRequests = new List<PendingRequest>();
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event when the connection is closed. This event happens when reconnecting/resubscribing has failed too often based on the <see cref="SocketClientOptions.MaxReconnectTries"/> and <see cref="SocketClientOptions.MaxResubscribeTries"/> options,
|
/// Event when the connection is closed. This event happens when reconnecting/resubscribing has failed too often based on the <see cref="BaseSocketClientOptions.MaxReconnectTries"/> and <see cref="BaseSocketClientOptions.MaxResubscribeTries"/> options,
|
||||||
/// or <see cref="SocketClientOptions.AutoReconnect"/> is false. The socket will not be reconnected
|
/// or <see cref="BaseSocketClientOptions.AutoReconnect"/> is false. The socket will not be reconnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ConnectionClosed
|
public event Action ConnectionClosed
|
||||||
{
|
{
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
using CryptoExchange.Net.Authentication;
|
|
||||||
using CryptoExchange.Net.Interfaces;
|
|
||||||
using CryptoExchange.Net.Objects;
|
|
||||||
using CryptoExchange.Net.Requests;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Base rest client
|
|
||||||
/// </summary>
|
|
||||||
public abstract class SubClient: IDisposable
|
|
||||||
{
|
|
||||||
public AuthenticationProvider? AuthenticationProvider { get; }
|
|
||||||
protected string BaseAddress { get; }
|
|
||||||
|
|
||||||
public SubClient(SubClientOptions options, AuthenticationProvider? authProvider)
|
|
||||||
{
|
|
||||||
AuthenticationProvider = authProvider;
|
|
||||||
BaseAddress = options.BaseAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
AuthenticationProvider?.Credentials?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user