From d1a2856a0875c33cf7ae5f8e59553ae2ca22da63 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 15 Sep 2021 14:48:19 +0200 Subject: [PATCH] Added option to send additional headers for each request or for individual requests, added HttpMethod parameter position configuration to rest client --- .../RestClientTests.cs | 60 +++++- .../TestImplementations/TestBaseClient.cs | 4 +- .../TestImplementations/TestRestClient.cs | 27 ++- .../Authentication/AuthenticationProvider.cs | 12 +- CryptoExchange.Net/CryptoExchange.Net.xml | 191 +++--------------- CryptoExchange.Net/Interfaces/IRequest.cs | 8 + CryptoExchange.Net/Objects/Enums.cs | 8 +- CryptoExchange.Net/Requests/Request.cs | 8 + CryptoExchange.Net/RestClient.cs | 81 ++++++-- 9 files changed, 203 insertions(+), 196 deletions(-) diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index 286db2b..9a569ca 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -10,6 +10,8 @@ using System.Linq; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.RateLimiter; using Microsoft.Extensions.Logging; +using System.Net.Http; +using System.Threading.Tasks; namespace CryptoExchange.Net.UnitTests { @@ -22,7 +24,7 @@ namespace CryptoExchange.Net.UnitTests // arrange var client = new TestRestClient(); var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" }; - client.SetResponse(JsonConvert.SerializeObject(expected)); + client.SetResponse(JsonConvert.SerializeObject(expected), out _); // act var result = client.Request().Result; @@ -37,7 +39,7 @@ namespace CryptoExchange.Net.UnitTests { // arrange var client = new TestRestClient(); - client.SetResponse("{\"property\": 123"); + client.SetResponse("{\"property\": 123", out _); // act var result = client.Request().Result; @@ -119,6 +121,46 @@ namespace CryptoExchange.Net.UnitTests Assert.IsTrue(client.RequestTimeout == TimeSpan.FromMinutes(1)); } + [TestCase("GET", HttpMethodParameterPosition.InUri)] // No need to test InBody for GET since thats not valid + [TestCase("POST", HttpMethodParameterPosition.InBody)] + [TestCase("POST", HttpMethodParameterPosition.InUri)] + [TestCase("DELETE", HttpMethodParameterPosition.InBody)] + [TestCase("DELETE", HttpMethodParameterPosition.InUri)] + [TestCase("PUT", HttpMethodParameterPosition.InUri)] + [TestCase("PUT", HttpMethodParameterPosition.InBody)] + public async Task Setting_Should_ResultInOptionsSet(string method, HttpMethodParameterPosition pos) + { + // arrange + // act + var client = new TestRestClient(new RestClientOptions("") + { + BaseAddress = "http://test.address.com", + }); + + client.SetParameterPosition(new HttpMethod(method), pos); + + client.SetResponse("{}", out var request); + + await client.RequestWithParams(new HttpMethod(method), new Dictionary + { + { "TestParam1", "Value1" }, + { "TestParam2", 2 }, + }, + new Dictionary + { + { "TestHeader", "123" } + }); + + // assert + Assert.AreEqual(request.Method, new HttpMethod(method)); + Assert.AreEqual(request.Content?.Contains("TestParam1") == true, pos == HttpMethodParameterPosition.InBody); + Assert.AreEqual(request.Uri.ToString().Contains("TestParam1"), pos == HttpMethodParameterPosition.InUri); + Assert.AreEqual(request.Content?.Contains("TestParam2") == true, pos == HttpMethodParameterPosition.InBody); + Assert.AreEqual(request.Uri.ToString().Contains("TestParam2"), pos == HttpMethodParameterPosition.InUri); + Assert.AreEqual(request.GetHeaders().First().Key, "TestHeader"); + Assert.IsTrue(request.GetHeaders().First().Value.Contains("123")); + } + [TestCase] public void SettingRateLimitingBehaviourToFail_Should_FailLimitedRequests() { @@ -128,12 +170,12 @@ namespace CryptoExchange.Net.UnitTests RateLimiters = new List { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, RateLimitingBehaviour = RateLimitingBehaviour.Fail }); - client.SetResponse("{\"property\": 123}"); + client.SetResponse("{\"property\": 123}", out _); // act var result1 = client.Request().Result; - client.SetResponse("{\"property\": 123}"); + client.SetResponse("{\"property\": 123}", out _); var result2 = client.Request().Result; @@ -151,13 +193,13 @@ namespace CryptoExchange.Net.UnitTests RateLimiters = new List { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, RateLimitingBehaviour = RateLimitingBehaviour.Wait }); - client.SetResponse("{\"property\": 123}"); + client.SetResponse("{\"property\": 123}", out _); // act var sw = Stopwatch.StartNew(); var result1 = client.Request().Result; - client.SetResponse("{\"property\": 123}"); // reset response stream + client.SetResponse("{\"property\": 123}", out _); // reset response stream var result2 = client.Request().Result; sw.Stop(); @@ -178,17 +220,17 @@ namespace CryptoExchange.Net.UnitTests LogLevel = LogLevel.Debug, ApiCredentials = new ApiCredentials("TestKey", "TestSecret") }); - client.SetResponse("{\"property\": 123}"); + client.SetResponse("{\"property\": 123}", out _); // act var sw = Stopwatch.StartNew(); var result1 = client.Request().Result; client.SetKey("TestKey2", "TestSecret2"); // set to different key - client.SetResponse("{\"property\": 123}"); // reset response stream + client.SetResponse("{\"property\": 123}", out _); // reset response stream var result2 = client.Request().Result; client.SetKey("TestKey", "TestSecret"); // set back to original key, should delay - client.SetResponse("{\"property\": 123}"); // reset response stream + client.SetResponse("{\"property\": 123}", out _); // reset response stream var result3 = client.Request().Result; sw.Stop(); diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs index 93fec79..cac8ca6 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestBaseClient.cs @@ -38,12 +38,12 @@ namespace CryptoExchange.Net.UnitTests { } - public override Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization) + public override Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization) { return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization); } - public override Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization) + public override Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization) { return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization); } diff --git a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs index 075f291..3d70993 100644 --- a/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs +++ b/CryptoExchange.Net.UnitTests/TestImplementations/TestRestClient.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using CryptoExchange.Net.Authentication; +using System.Collections.Generic; namespace CryptoExchange.Net.UnitTests.TestImplementations { @@ -26,12 +27,17 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations RequestFactory = new Mock().Object; } + public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position) + { + ParameterPositions[method] = position; + } + public void SetKey(string key, string secret) { SetAuthenticationProvider(new UnitTests.TestAuthProvider(new ApiCredentials(key, secret))); } - public void SetResponse(string responseData, Stream requestStream = null) + public void SetResponse(string responseData, out IRequest requestObj) { var expectedBytes = Encoding.UTF8.GetBytes(responseData); var responseStream = new MemoryStream(); @@ -42,13 +48,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations response.Setup(c => c.IsSuccessStatusCode).Returns(true); response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream)); + var headers = new Dictionary>(); var request = new Mock(); request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); request.Setup(c => c.GetResponseAsync(It.IsAny())).Returns(Task.FromResult(response.Object)); + request.Setup(c => c.SetContent(It.IsAny(), It.IsAny())).Callback(new Action((content, type) => { request.Setup(r => r.Content).Returns(content); })); + request.Setup(c => c.AddHeader(It.IsAny(), It.IsAny())).Callback((key, val) => headers.Add(key, new List { val })); + request.Setup(c => c.GetHeaders()).Returns(() => headers); var factory = Mock.Get(RequestFactory); factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((method, uri, id) => + { + request.Setup(a => a.Uri).Returns(new Uri(uri)); + request.Setup(a => a.Method).Returns(method); + }) .Returns(request.Object); + requestObj = request.Object; } public void SetErrorWithoutResponse(HttpStatusCode code, string message) @@ -75,12 +91,16 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations response.Setup(c => c.IsSuccessStatusCode).Returns(false); response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream)); + var headers = new Dictionary>(); var request = new Mock(); request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); request.Setup(c => c.GetResponseAsync(It.IsAny())).Returns(Task.FromResult(response.Object)); + request.Setup(c => c.AddHeader(It.IsAny(), It.IsAny())).Callback((key, val) => headers.Add(key, new List { val })); + request.Setup(c => c.GetHeaders()).Returns(headers); var factory = Mock.Get(RequestFactory); factory.Setup(c => c.Create(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((method, uri, id) => request.Setup(a => a.Uri).Returns(new Uri(uri))) .Returns(request.Object); } @@ -88,6 +108,11 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations { return await SendRequestAsync(new Uri("http://www.test.com"), HttpMethod.Get, ct); } + + public async Task> RequestWithParams(HttpMethod method, Dictionary parameters, Dictionary headers) where T : class + { + return await SendRequestAsync(new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers); + } } public class TestAuthProvider : AuthenticationProvider diff --git a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs index c3524a9..ed266c6 100644 --- a/CryptoExchange.Net/Authentication/AuthenticationProvider.cs +++ b/CryptoExchange.Net/Authentication/AuthenticationProvider.cs @@ -30,11 +30,11 @@ namespace CryptoExchange.Net.Authentication /// The HTTP method of the request /// The provided parameters for the request /// Wether or not the request needs to be signed. If not typically the parameters list can just be returned - /// Where post parameters are placed, in the URI or in the request body + /// Where parameters are placed, in the URI or in the request body /// How array parameters are serialized /// Should return the original parameter list including any authentication parameters needed - public virtual Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, - PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization) + public virtual Dictionary AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary parameters, bool signed, + HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization) { return parameters; } @@ -46,11 +46,11 @@ namespace CryptoExchange.Net.Authentication /// The HTTP method of the request /// The provided parameters for the request /// Wether or not the request needs to be signed. If not typically the parameters list can just be returned - /// Where post parameters are placed, in the URI or in the request body + /// Where post parameters are placed, in the URI or in the request body /// How array parameters are serialized /// Should return a dictionary containing any header key/value pairs needed for authenticating the request - public virtual Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, - PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization) + public virtual Dictionary AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary parameters, bool signed, + HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization) { return new Dictionary(); } diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index 9b2d7ad..b9c58c4 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -97,7 +97,7 @@ - + Add authentication to the parameter list based on the provided credentials @@ -105,11 +105,11 @@ The HTTP method of the request The provided parameters for the request Wether or not the request needs to be signed. If not typically the parameters list can just be returned - Where post parameters are placed, in the URI or in the request body + Where parameters are placed, in the URI or in the request body How array parameters are serialized Should return the original parameter list including any authentication parameters needed - + Add authentication to the header dictionary based on the provided credentials @@ -117,7 +117,7 @@ The HTTP method of the request The provided parameters for the request Wether or not the request needs to be signed. If not typically the parameters list can just be returned - Where post parameters are placed, in the URI or in the request body + Where post parameters are placed, in the URI or in the request body How array parameters are serialized Should return a dictionary containing any header key/value pairs needed for authenticating the request @@ -1163,6 +1163,12 @@ + + + Get all headers + + + Get the response @@ -1934,19 +1940,19 @@ Wait till the request can be send - + - Where the parameters for a post request should be added + Where the parameters for a HttpMethod should be added in a request - + - Post parameters in body + Parameters in body - + - Post parameters in url + Parameters in url @@ -2802,6 +2808,9 @@ + + + @@ -2855,9 +2864,9 @@ The factory for creating requests. Used for unit testing - + - Where to place post parameters by default + Where to put the parameters for requests with different Http methods @@ -2900,6 +2909,11 @@ Total requests made by this client + + + Request headers to be sent with each request + + ctor @@ -2931,7 +2945,7 @@ The roundtrip time of the ping request - + Execute a request to the uri and deserialize the response into the provided type parameter @@ -2942,10 +2956,11 @@ The parameters of the request Whether or not the request should be authenticated Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) - Where the post parameters should be placed, overwrites the value set in the client + Where the parameters should be placed, overwrites the value set in the client How array parameters should be serialized, overwrites the value set in the client Credits used for the request The JsonSerializer to use for deserialization + Additional headers to send with the request @@ -2966,7 +2981,7 @@ Received data Null if not an error, Error otherwise - + Creates a request object @@ -2974,9 +2989,10 @@ The method of the request The parameters of the request Whether or not the request should be authenticated - Where the post parameters should be placed + Where the parameters should be placed How array parameters should be serialized Unique id of a request + Additional headers to send with the request @@ -3924,148 +3940,5 @@ - - - Specifies that is allowed as an input even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that is disallowed as an input even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that a method that will never return under any circumstance. - - - - - Initializes a new instance of the class. - - - - - Specifies that the method will not return if the associated - parameter is passed the specified value. - - - - - Gets the condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Initializes a new instance of the - class with the specified parameter value. - - - The condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Specifies that an output may be even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that when a method returns , - the parameter may be even if the corresponding type disallows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter may be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter may be . - - - - - Specifies that an output is not even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that the output will be non- if the - named parameter is non-. - - - - - Gets the associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Initializes the attribute with the associated parameter name. - - - The associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Specifies that when a method returns , - the parameter will not be even if the corresponding type allows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter will not be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter will not be . - - diff --git a/CryptoExchange.Net/Interfaces/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 1645d1e..ad6c591 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -48,6 +49,13 @@ namespace CryptoExchange.Net.Interfaces /// /// void AddHeader(string key, string value); + + /// + /// Get all headers + /// + /// + Dictionary> GetHeaders(); + /// /// Get the response /// diff --git a/CryptoExchange.Net/Objects/Enums.cs b/CryptoExchange.Net/Objects/Enums.cs index d31b48d..0543015 100644 --- a/CryptoExchange.Net/Objects/Enums.cs +++ b/CryptoExchange.Net/Objects/Enums.cs @@ -16,16 +16,16 @@ } /// - /// Where the parameters for a post request should be added + /// Where the parameters for a HttpMethod should be added in a request /// - public enum PostParameters + public enum HttpMethodParameterPosition { /// - /// Post parameters in body + /// Parameters in body /// InBody, /// - /// Post parameters in url + /// Parameters in url /// InUri } diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 9cb7073..58c2d44 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -63,6 +65,12 @@ namespace CryptoExchange.Net.Requests request.Headers.Add(key, value); } + /// + public Dictionary> GetHeaders() + { + return request.Headers.ToDictionary(h => h.Key, h => h.Value); + } + /// public void SetContent(byte[] data) { diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 599a7a7..c7797d1 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -32,9 +32,15 @@ namespace CryptoExchange.Net public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); /// - /// Where to place post parameters by default + /// Where to put the parameters for requests with different Http methods /// - protected PostParameters postParametersPosition = PostParameters.InBody; + protected Dictionary ParameterPositions { get; set; } = new Dictionary + { + { HttpMethod.Get, HttpMethodParameterPosition.InUri }, + { HttpMethod.Post, HttpMethodParameterPosition.InBody }, + { HttpMethod.Delete, HttpMethodParameterPosition.InBody }, + { HttpMethod.Put, HttpMethodParameterPosition.InBody } + }; /// /// Request body content type @@ -73,6 +79,11 @@ namespace CryptoExchange.Net /// public int TotalRequestsMade { get; private set; } + /// + /// Request headers to be sent with each request + /// + protected Dictionary? StandardRequestHeaders { get; set; } + /// /// ctor /// @@ -167,16 +178,25 @@ namespace CryptoExchange.Net /// The parameters of the request /// Whether or not the request should be authenticated /// Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) - /// Where the post parameters should be placed, overwrites the value set in the client + /// Where the parameters should be placed, overwrites the value set in the client /// How array parameters should be serialized, overwrites the value set in the client /// Credits used for the request /// The JsonSerializer to use for deserialization + /// Additional headers to send with the request /// [return: NotNull] - protected virtual async Task> SendRequestAsync(Uri uri, HttpMethod method, CancellationToken cancellationToken, - Dictionary? parameters = null, bool signed = false, bool checkResult = true, - PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null, int credits = 1, - JsonSerializer? deserializer = null) where T : class + protected virtual async Task> SendRequestAsync( + Uri uri, + HttpMethod method, + CancellationToken cancellationToken, + Dictionary? parameters = null, + bool signed = false, + bool checkResult = true, + HttpMethodParameterPosition? parameterPosition = null, + ArrayParametersSerialization? arraySerialization = null, + int credits = 1, + JsonSerializer? deserializer = null, + Dictionary? additionalHeaders = null) where T : class { var requestId = NextId(); log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri); @@ -186,7 +206,8 @@ namespace CryptoExchange.Net return new WebCallResult(null, null, null, new NoApiCredentialsError()); } - var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization, requestId); + var paramsPosition = parameterPosition ?? ParameterPositions[method]; + var request = ConstructRequest(uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders); foreach (var limiter in RateLimiters) { var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits); @@ -201,9 +222,16 @@ namespace CryptoExchange.Net } string? paramString = null; - if (method == HttpMethod.Post) + if (parameterPosition == HttpMethodParameterPosition.InBody) paramString = " with request body " + request.Content; + if (log.Level == LogLevel.Trace) + { + var headers = request.GetHeaders(); + if (headers.Any()) + paramString = " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")); + } + log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")}"); return await GetResponseAsync(request, deserializer, cancellationToken).ConfigureAwait(false); } @@ -320,20 +348,29 @@ namespace CryptoExchange.Net /// The method of the request /// The parameters of the request /// Whether or not the request should be authenticated - /// Where the post parameters should be placed + /// Where the parameters should be placed /// How array parameters should be serialized /// Unique id of a request + /// Additional headers to send with the request /// - protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization, int requestId) + protected virtual IRequest ConstructRequest( + Uri uri, + HttpMethod method, + Dictionary? parameters, + bool signed, + HttpMethodParameterPosition parameterPosition, + ArrayParametersSerialization arraySerialization, + int requestId, + Dictionary? additionalHeaders) { if (parameters == null) parameters = new Dictionary(); var uriString = uri.ToString(); if (authProvider != null) - parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, postPosition, arraySerialization); + parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization); - if ((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true) + if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true) uriString += "?" + parameters.CreateParamString(true, arraySerialization); var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; @@ -342,12 +379,26 @@ namespace CryptoExchange.Net var headers = new Dictionary(); if (authProvider != null) - headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, postPosition, arraySerialization); + headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization); foreach (var header in headers) request.AddHeader(header.Key, header.Value); - if ((method == HttpMethod.Post || method == HttpMethod.Put) && postPosition != PostParameters.InUri) + if (additionalHeaders != null) + { + foreach (var header in additionalHeaders) + request.AddHeader(header.Key, header.Value); + } + + if(StandardRequestHeaders != null) + { + foreach (var header in StandardRequestHeaders) + // Only add it if it isn't overwritten + if(additionalHeaders?.ContainsKey(header.Key) != true) + request.AddHeader(header.Key, header.Value); + } + + if (parameterPosition == HttpMethodParameterPosition.InBody) { if (parameters?.Any() == true) WriteParamBody(request, parameters, contentType);