Отменить HttpRequestMessage - Исключение

Я хочу отправить один и тот же запрос более одного раза, например:

HttpClient client = new HttpClient();
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, "http://example.com");

await client.SendAsync(req, HttpCompletionOption.ResponseContentRead);
await client.SendAsync(req, HttpCompletionOption.ResponseContentRead);

Отправка запроса во второй раз вызовет исключение с сообщением:

Сообщение запроса уже отправлено. Не удается отправить тот же запрос сообщение несколько раз.

Является ли их способ "клонировать" запрос, чтобы я мог отправить его снова?

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

Ответ 1

Я написал следующий метод расширения для клонирования запроса.

public static HttpRequestMessage Clone(this HttpRequestMessage req)
{
    HttpRequestMessage clone = new HttpRequestMessage(req.Method, req.RequestUri);

    clone.Content = req.Content;
    clone.Version = req.Version;

    foreach (KeyValuePair<string, object> prop in req.Properties)
    {
        clone.Properties.Add(prop);
    }

    foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers)
    {
        clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
    }

    return clone;
}

Ответ 2

Я передаю экземпляр Func<HttpRequestMessage> вместо экземпляра HttpRequestMessage. Функция func указывает на метод factory, поэтому я получаю совершенно новое сообщение каждый раз, когда он вызывается вместо повторного использования.

Ответ 3

Вот усовершенствование метода расширения, предложенного @drahcir. Улучшение заключается в обеспечении клонирования содержимого запроса, а также самого запроса:

public static HttpRequestMessage Clone(this HttpRequestMessage request)
{
    var clone = new HttpRequestMessage(request.Method, request.RequestUri)
    {
        Content = request.Content.Clone(),
        Version = request.Version
    };
    foreach (KeyValuePair<string, object> prop in request.Properties)
    {
        clone.Properties.Add(prop);
    }
    foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
    {
        clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
    }

    return clone;
}

public static HttpContent Clone(this HttpContent content)
{
    if (content == null) return null;

    var ms = new MemoryStream();
    content.CopyToAsync(ms).Wait();
    ms.Position = 0;

    var clone = new StreamContent(ms);
    foreach (KeyValuePair<string, IEnumerable<string>> header in content.Headers)
    {
        clone.Headers.Add(header.Key, header.Value);
    }
    return clone;
}

Ответ 4

AFAIK, HttpClient - это всего лишь обертка вокруг "HttpWebRequest", которая использует потоки для отправки/получения данных, что делает невозможным повторное использование запроса, хотя это должно быть довольно просто, чтобы просто клонировать его/сделать это в цикле.

Ответ 5

У меня есть аналогичная проблема и разрешила это с помощью взлома, отражения.

Спасибо за открытый исходный код! Читая исходный код, в нем есть частное поле _sendStatus в HttpRequestMessage, то, что я сделал, это reset it до 0, прежде чем повторно использовать сообщение запроса. Он работает в .NET Core, и я хочу, чтобы Microsoft не переименовывала и не удаляла его навсегда.: Р

// using System.Reflection;
// using System.Net.Http;
// private const string SEND_STATUS_FIELD_NAME = "_sendStatus";
private void ResetSendStatus(HttpRequestMessage request)
{
    TypeInfo requestType = request.GetType().GetTypeInfo();
    FieldInfo sendStatusField = requestType.GetField(SEND_STATUS_FIELD_NAME, BindingFlags.Instance | BindingFlags.NonPublic);
    if (sendStatusField != null)
        sendStatusField.SetValue(request, 0);
    else
        throw new Exception($"Failed to hack HttpRequestMessage, {SEND_STATUS_FIELD_NAME} doesn't exist.");
}