32

I'm trying to clone a request using the method outlined in this answer: https://stackoverflow.com/a/18014515/406322

However, I get an ObjectDisposedException, if the original request has content.

How can you reliably clone a HttpRequestMessage?

2
  • I don't know if you can. If you open a stream and use that as the content for instance, the stream can only be used once. Where is the original being created? Commented Jul 30, 2014 at 18:27
  • My original request is a PUT with an authentication header and some parameters as the content. I need to do retries (but change the header for each retry), so what are my options? Commented Jul 30, 2014 at 18:40

3 Answers 3

51

This should do the trick:

 public static async Task<HttpRequestMessage> CloneHttpRequestMessageAsync(HttpRequestMessage req) { HttpRequestMessage clone = new HttpRequestMessage(req.Method, req.RequestUri); // Copy the request's content (via a MemoryStream) into the cloned object var ms = new MemoryStream(); if (req.Content != null) { await req.Content.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; clone.Content = new StreamContent(ms); // Copy the content headers foreach (var h in req.Content.Headers) clone.Content.Headers.Add(h.Key, h.Value); } clone.Version = req.Version; foreach (KeyValuePair<string, object?> option in req.Options) clone.Options.Set(new HttpRequestOptionsKey<object?>(option.Key), option.Value); foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers) clone.Headers.TryAddWithoutValidation(header.Key, header.Value); return clone; } 
Sign up to request clarification or add additional context in comments.

6 Comments

I just used this snippet and so far it works fine. @Prahbu: could your mark it as right answer?
as of .net 5 req.Content.Headers is never null, so there's no point in checking for null-ness. I just run the loop without the condition. Also req.Properties is obsolete and req.Options should be used instead.
so in .NET 5, it should change to something like foreach (KeyValuePair<string, object?> option in req.Options) clone.Options.Set(new HttpRequestOptionsKey<object?>(option.Key), option.Value);
why not just clone.Content = req.Content?
Because Content as well can be separately Disposed, example is StringContent
|
8

If you call LoadIntoBufferAsync on the content, you can guarantee that the content is buffered inside the HttpContent object. The only problem remaining is that reading the stream does not reset the position, so you need to ReadAsStreamAsync and set the stream Position = 0.

My example is very similar to the one Carlos showed...

 private async Task<HttpResponseMessage> CloneResponseAsync(HttpResponseMessage response) { var newResponse = new HttpResponseMessage(response.StatusCode); var ms = new MemoryStream(); foreach (var v in response.Headers) newResponse.Headers.TryAddWithoutValidation(v.Key, v.Value); if (response.Content != null) { await response.Content.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; newResponse.Content = new StreamContent(ms); foreach (var v in response.Content.Headers) newResponse.Content.Headers.TryAddWithoutValidation(v.Key, v.Value); } return newResponse; } 

```

3 Comments

Link is now dead, perhaps you can add the relevant snippet to your answer?
The file was moved it seems and the LoadIntoBufferAsync was removed by a newer commit: github.com/tavis-software/Tavis.HttpCache/commit/…
One note: this answer regards HttpResponseMessage cloning. The question was about cloning the request, not the response. The approach is fairly similar.
4

Carlos's answer with some linq shortcuts:

public static async Task<HttpRequestMessage> Clone(this HttpRequestMessage httpRequestMessage) { HttpRequestMessage httpRequestMessageClone = new HttpRequestMessage(httpRequestMessage.Method, httpRequestMessage.RequestUri); if (httpRequestMessage.Content != null) { var ms = new MemoryStream(); await httpRequestMessage.Content.CopyToAsync(ms); ms.Position = 0; httpRequestMessageClone.Content = new StreamContent(ms); httpRequestMessage.Content.Headers?.ToList().ForEach(header => httpRequestMessageClone.Content.Headers.Add(header.Key, header.Value)); } httpRequestMessageClone.Version = httpRequestMessage.Version; httpRequestMessage.Properties.ToList().ForEach(props => httpRequestMessageClone.Properties.Add(props)); httpRequestMessage.Headers.ToList().ForEach(header => httpRequestMessageClone.Headers.TryAddWithoutValidation(header.Key, header.Value)); return httpRequestMessageClone; } 

UPDATED VERSION FOR .NET VERSION > .NET 5

public static async Task<HttpRequestMessage> Clone(this HttpRequestMessage request) { var clone = new HttpRequestMessage(request.Method, request.RequestUri) { Version = request.Version }; if (request.Content != null) { var ms = new MemoryStream(); await request.Content.CopyToAsync(ms); ms.Position = 0; clone.Content = new StreamContent(ms); request.Content.Headers.ToList().ForEach(header => clone.Content.Headers.TryAddWithoutValidation(header.Key, header.Value)); } request.Options.ForEach(option => clone.Options.TryAdd(option.Key, option.Value)); request.Headers .ForEach(header => clone.Headers.TryAddWithoutValidation(header.Key, header.Value)); return clone; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.