С# HttpClient Существующее соединение было принудительно закрыто удаленным хостом

Я работаю над интеграцией с Альтернативными платежами, используя их размещенную на странице интеграцию страниц. В их С# SDK в настоящее время нет этой интеграции, но, как вы можете видеть, это довольно просто, и я создал небольшой класс для отправки запроса на публикацию и получения ответа JSON.

Я протестировал объект json, который отправляю на PostMan и cURL, и оба работают, а также заголовок аутентификации, поэтому я думаю, что они не являются проблемой. Вот конструктор моего класса:

public AlternativePaymentsCli(string apiSecretKey)
{
    this._apiSecretKey = apiSecretKey;

    _httpClient = new HttpClient();
    _httpClient.DefaultRequestHeaders.Accept
        .Add(new MediaTypeWithQualityHeaderValue("application/json"));

    var authInfo = _apiSecretKey;
    authInfo = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:", _apiSecretKey)));

    // The two line below because I saw in an answer on stackoverflow.
    _httpClient.DefaultRequestHeaders.Add("Connection", "Keep-Alive"); 
    _httpClient.DefaultRequestHeaders.Add("Keep-Alive", "3600");

    _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Anything.com custom client v1.0");
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);

}

И метод, которым я публикую данные:

public string CreateHostedPageTransaction(HostedPageRequest req) 
{
    var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };

    // I send this same json content on PostMan and it works. The json is not the problem
    var content = new StringContent(JsonConvert.SerializeObject(req, settings), Encoding.UTF8, "application/json");
    var response = _httpClient.PostAsync(this._baseUrl + "/transactions/hosted", content).Result;
    var responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();

    if (response.IsSuccessStatusCode)
        return responseText;

    return "";
}

Затем я получаю эту ошибку: An existing connection was forcibly closed by the remote host в строке PostAsync. Это детали ошибки:

[SocketException (0x2746): An existing connection was forcibly closed by the remote host]
   System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult) +8192811
   System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult) +47

[IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.]
   System.Net.TlsStream.EndWrite(IAsyncResult asyncResult) +294
   System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar) +149

[WebException: The underlying connection was closed: An unexpected error occurred on a send.]
   System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) +324
   System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) +137

[HttpRequestException: An error occurred while sending the request.]

Я использую С# 4.5, Asp.Net MVC. Я читал ответы на ту же ошибку, и ни один из них до сих пор не решил мою проблему. Чего мне не хватает в этом коде?

Спасибо за любую помощь

Ответ 1

Я не вижу в вашем примере кода, где вы устанавливаете значение _baseUrl, но я предполагаю, что это делается где-то. Я также предполагаю, что, поскольку это связано с платежами, URL-адрес - HTTPS. Если удаленный хост отключил TLS 1.0 и ваше соединение входит в TLS 1.0, это может привести к такому поведению. Я знаю, что С# 4.6 имеет поддержку TLS 1.0/1.1/1.2 по умолчанию, но я думаю, что С# 4.6 по-прежнему по умолчанию использует только SSL3/TLS 1.0, хотя поддерживаются TLS 1.1 и 1.2. Если это является причиной проблемы, вы можете вручную добавить TLS 1.1 и 1.2 к включенным значениям, используя следующий код.

System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

Ответ 2

Если вы используете .Net 4.0, то SecurityProtocolType.Tls11 и SecurityProtocolType.Tls2 не определены, поэтому вместо жесткого кодированного значения вы можете использовать ниже.

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

Ответ 3

Можно решить проблему без каких-либо изменений в коде, как описано в этом превосходном ответе на аналогичный вопрос:

Переориентируйте веб-проект на .Net 4. 6+, затем обновите web.config следующим образом:

<system.web>
  <compilation targetFramework="4.6" /> 
  <httpRuntime targetFramework="4.6" /> 
</system.web>