1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2026-04-07 10:11:10 +00:00

Added ForcePathEndWithSlash setting to RequestDefinition, added encoding parameter to SetContent on REST Request, added RequestBodyContentEncoding and OmitContentTypeHeaderWithoutContent config to RestApiClient

This commit is contained in:
Jkorf 2026-03-06 09:25:04 +01:00
parent 33c0fb26a7
commit eaf092a334
7 changed files with 42 additions and 20 deletions

View File

@ -56,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
var request = new Mock<IRequest>(); var request = new Mock<IRequest>();
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com"));
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object)); request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object));
request.Setup(c => c.SetContent(It.IsAny<string>(), It.IsAny<string>())).Callback(new Action<string, string>((content, type) => { request.Setup(r => r.Content).Returns(content); })); request.Setup(c => c.SetContent(It.IsAny<string>(), It.IsAny<Encoding>(), It.IsAny<string>())).Callback(new Action<string, Encoding, string>((content, encoding, type) => { request.Setup(r => r.Content).Returns(content); }));
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new string[] { val })); request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new string[] { val }));
request.Setup(c => c.GetHeaders()).Returns(() => headers); request.Setup(c => c.GetHeaders()).Returns(() => headers);

View File

@ -77,6 +77,16 @@ namespace CryptoExchange.Net.Clients
{ new HttpMethod("Patch"), HttpMethodParameterPosition.InBody }, { new HttpMethod("Patch"), HttpMethodParameterPosition.InBody },
}; };
/// <summary>
/// Encoding/charset for the ContentType header
/// </summary>
protected Encoding? RequestBodyContentEncoding { get; set; } = Encoding.UTF8;
/// <summary>
/// Whether to omit the ContentType header if there is no content
/// </summary>
protected bool OmitContentTypeHeaderWithoutContent { get; set; } = false;
/// <inheritdoc /> /// <inheritdoc />
public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions; public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions;
@ -374,7 +384,11 @@ namespace CryptoExchange.Net.Clients
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?")) if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?"))
queryString = $"?{queryString}"; queryString = $"?{queryString}";
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString); var path = baseAddress.AppendPath(definition.Path);
if (definition.ForcePathEndWithSlash == true && !path.EndsWith("/"))
path += "/";
var uri = new Uri(path + queryString);
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId); var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
request.Accept = MessageHandler.AcceptHeader; request.Accept = MessageHandler.AcceptHeader;
@ -398,14 +412,14 @@ namespace CryptoExchange.Net.Clients
var bodyContent = requestConfiguration.GetBodyContent(); var bodyContent = requestConfiguration.GetBodyContent();
if (bodyContent != null) if (bodyContent != null)
{ {
request.SetContent(bodyContent, contentType); request.SetContent(bodyContent, RequestBodyContentEncoding, contentType);
} }
else else
{ {
if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0) if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0)
WriteParamBody(request, requestConfiguration.BodyParameters, contentType); WriteParamBody(request, requestConfiguration.BodyParameters, contentType);
else else if (OmitContentTypeHeaderWithoutContent != true)
request.SetContent(RequestBodyEmptyContent, contentType); request.SetContent(RequestBodyEmptyContent, RequestBodyContentEncoding, contentType);
} }
} }
@ -646,13 +660,13 @@ namespace CryptoExchange.Net.Clients
stringData = stringSerializer.Serialize(value); stringData = stringSerializer.Serialize(value);
else else
stringData = stringSerializer.Serialize(parameters); stringData = stringSerializer.Serialize(parameters);
request.SetContent(stringData, contentType); request.SetContent(stringData, RequestBodyContentEncoding, contentType);
} }
else if (contentType == Constants.FormContentHeader) else if (contentType == Constants.FormContentHeader)
{ {
// Write the parameters as form data in the body // Write the parameters as form data in the body
var stringData = parameters.ToFormData(); var stringData = parameters.ToFormData();
request.SetContent(stringData, contentType); request.SetContent(stringData, RequestBodyContentEncoding, contentType);
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -43,9 +44,7 @@ namespace CryptoExchange.Net.Interfaces
/// <summary> /// <summary>
/// Set string content /// Set string content
/// </summary> /// </summary>
/// <param name="data"></param> void SetContent(string data, Encoding? encoding, string contentType);
/// <param name="contentType"></param>
void SetContent(string data, string contentType);
/// <summary> /// <summary>
/// Add a header to the request /// Add a header to the request

View File

@ -72,6 +72,11 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public int? ConnectionId { get; set; } public int? ConnectionId { get; set; }
/// <summary>
/// Whether the endpoint path should always include the trailing `/`
/// </summary>
public bool? ForcePathEndWithSlash { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>

View File

@ -47,6 +47,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="arraySerialization">Array serialization type</param> /// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param> /// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param> /// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <param name="forcePathEndWithSlash">Force trailing `/`</param>
/// <returns></returns> /// <returns></returns>
public RequestDefinition GetOrCreate( public RequestDefinition GetOrCreate(
HttpMethod method, HttpMethod method,
@ -59,8 +60,9 @@ namespace CryptoExchange.Net.Objects
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null, bool? preventCaching = null,
bool? tryParseOnNonSuccess = null) bool? tryParseOnNonSuccess = null,
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess); bool? forcePathEndWithSlash = null)
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess, forcePathEndWithSlash);
/// <summary> /// <summary>
/// Get a definition if it is already in the cache or create a new definition and add it to the cache /// Get a definition if it is already in the cache or create a new definition and add it to the cache
@ -77,6 +79,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="arraySerialization">Array serialization type</param> /// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param> /// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param> /// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <param name="forcePathEndWithSlash">Force trailing `/`</param>
/// <returns></returns> /// <returns></returns>
public RequestDefinition GetOrCreate( public RequestDefinition GetOrCreate(
string identifier, string identifier,
@ -90,7 +93,8 @@ namespace CryptoExchange.Net.Objects
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null, bool? preventCaching = null,
bool? tryParseOnNonSuccess = null) bool? tryParseOnNonSuccess = null,
bool? forcePathEndWithSlash = null)
{ {
if (!_definitions.TryGetValue(identifier, out var def)) if (!_definitions.TryGetValue(identifier, out var def))
@ -105,7 +109,8 @@ namespace CryptoExchange.Net.Objects
RequestBodyFormat = requestBodyFormat, RequestBodyFormat = requestBodyFormat,
ParameterPosition = parameterPosition, ParameterPosition = parameterPosition,
PreventCaching = preventCaching ?? false, PreventCaching = preventCaching ?? false,
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false TryParseOnNonSuccess = tryParseOnNonSuccess ?? false,
ForcePathEndWithSlash = forcePathEndWithSlash ?? false
}; };
_definitions.TryAdd(identifier, def); _definitions.TryAdd(identifier, def);
} }

View File

@ -19,9 +19,6 @@ namespace CryptoExchange.Net.Requests
/// <summary> /// <summary>
/// Create request object for web request /// Create request object for web request
/// </summary> /// </summary>
/// <param name="request"></param>
/// <param name="client"></param>
/// <param name="requestId"></param>
public Request(HttpRequestMessage request, HttpClient client, int requestId) public Request(HttpRequestMessage request, HttpClient client, int requestId)
{ {
_httpClient = client; _httpClient = client;
@ -55,10 +52,12 @@ namespace CryptoExchange.Net.Requests
public int RequestId { get; } public int RequestId { get; }
/// <inheritdoc /> /// <inheritdoc />
public void SetContent(string data, string contentType) public void SetContent(string data, Encoding? encoding, string contentType)
{ {
Content = data; Content = data;
_request.Content = new StringContent(data, Encoding.UTF8, contentType); _request.Content = new StringContent(data, encoding ?? Encoding.UTF8, contentType);
if (encoding == null)
_request.Content.Headers.ContentType!.CharSet = null;
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -46,7 +46,7 @@ namespace CryptoExchange.Net.Testing.Implementations
Content = Encoding.UTF8.GetString(data); Content = Encoding.UTF8.GetString(data);
} }
public void SetContent(string data, string contentType) public void SetContent(string data, Encoding? encoding, string contentType)
{ {
Content = data; Content = data;
} }