I have a method that take Stream parameter and pass it to server
public async Task<string> Execute(Stream archive)
{
archive.Seek(0, SeekOrigin.Begin);
using var content = new MultipartFormDataContent();
content.Add(new StreamContent(archive), "file1", "file1");
var result = "";
using (var response = await _client.PostAsync(_uri, content))
{
if (response.IsSuccessStatusCode)
{
var stringResult = await response.Content.ReadAsStringAsync();
result = stringResult;
}
}
// here archive is already disposed
return result;
}
Now I implement the retry policy of this method. If outside code calling this method gets "" as result, then it tries to call this method againg. But the archive is disposed to that moment. I see that archive stream is disposed immediately after disposing of response. Why? What should I do if I need stream parameter outside after this method?
CodePudding user response:
It's the StreamContent
that will dispose the Stream
, as states in it's source. And that will be disposed by the MultipartContent
. And that will be disposed in PostAsync... all though the chain.
A solution is to subclass the Stream and remove the dispose method, like proposed here. but you'll have to make sure the original stream gets disposed yourself!.
Edit: update. Stream
is an abstract class, so it would be easier if you know the specific stream type, e.g.
public sealed class NoDisposeMemoryStream : MemoryStream
{
protected override void Dispose(bool disposing) { }
}
Else you will need to write your own complete Stream
wrapper, see bottom.
Another solution is to implement the retry mechanism in the innermost using block, resetting the archive
seek origin every fail. That's likely safer.
public sealed class NoDisposeStream : Stream
{
private Stream _original;
public NoDisposeStream(Stream original) => _original = original
?? throw new ArgumentNullException(nameof(original));
public override bool CanRead => _original.CanRead;
public override bool CanSeek => _original.CanSeek;
public override bool CanWrite => _original.CanWrite;
public override long Length => _original.Length;
public override long Position { get => _original.Position; set { _original.Position = value; } }
public override void Flush() => _original.Flush();
public override int Read(byte[] buffer, int offset, int count) => _original.Read(buffer, offset, count);
public override long Seek(long offset, SeekOrigin origin) => _original.Seek(offset, origin);
public override void SetLength(long value) => _original.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _original.Write(buffer, offset, count);
protected override void Dispose(bool disposing) { }
}
CodePudding user response:
This happens because HttpClient PostAsync Disposes of Content that you pass. https://github.com/microsoft/referencesource/blob/master/System/net/System/Net/Http/HttpClient.cs
The relevant code:
public Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content,
CancellationToken cancellationToken)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Content = content;
return SendAsync(request, cancellationToken);
}
And then SendAsync calls DisposeRequestContent which is implemented like:
private void DisposeRequestContent(HttpRequestMessage request)
{
Contract.Requires(request != null);
// When a request completes, HttpClient disposes the request content so the user doesn't have to. This also
// ensures that a HttpContent object is only sent once using HttpClient (similar to HttpRequestMessages
// that can also be sent only once).
HttpContent content = request.Content;
if (content != null)
{
content.Dispose();
}
}
The comments say why