まったく同じリクエストを複数回送信したいのですが、例えば:
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);
もう一度リクエストを送信すると、次のメッセージで例外がスローされます。
リクエストメッセージは既に送信されています。同じリクエストメッセージを複数回送信することはできません。
それらを「clone」にして、再度送信できるようにする方法はありますか?
実際のコードでは、上記の例よりも多くの変数がHttpRequestMessage
に設定されています。これは、ヘッダーやリクエストメソッドなどの変数です。
リクエストのクローンを作成するために、次の拡張メソッドを作成しました。
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;
}
@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;
}
編集05/02/18:これは非同期バージョンです
public static Task<HttpRequestMessage> CloneAsync(this HttpRequestMessage request)
{
var clone = new HttpRequestMessage(request.Method, request.RequestUri)
{
Content = await request.Content.CloneAsync().ConfigureAwait(false),
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 Task<HttpContent> CloneAsync(this HttpContent content)
{
if (content == null) return null;
var ms = new MemoryStream();
await content.CopyToAsync(ms).ConfigureAwait(false);
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;
}
HttpRequestMessage
のインスタンスの代わりにFunc<HttpRequestMessage>
のインスタンスを渡しています。 funcはファクトリメソッドを指しているので、再利用する代わりに、呼び出されるたびに新しいメッセージが表示されます。
私は同様の問題を抱えており、ハッキングの方法、反射でそれを解決しました。
オープンソースをありがとう!ソースコードを読むと、HttpRequestMessage
クラスにプライベートフィールド_sendStatus
があることがわかります。私が行ったのは、リクエストメッセージを再利用する前に0
にリセットすることです。 .NET Coreで動作し、Microsoftが名前を変更したり、永久に削除したりしないことを願っています。 :p
// 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.");
}
申し訳ありませんが、HttpClientは 'HttpWebRequest'の単なるラッパーであり、データを送受信するためにストリームを使用するため、要求を再利用することは不可能ですが、それを複製してループで作成するのは非常に簡単です。