StringContent 是否随 HttpResponseMessage 一起处理?

joe*_*joe 6 .net c# asp.net .net-core asp.net-core

StringContent/HttpContent是一次性的,HttpRequestMessage我想知道请求是否在处理StringContent时被处理HttpRequestMessage,或者我是否需要两个单独的using,或者是否有更好的方法来处理这些?例如

var content = new StringContent("test");

 using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
 {

        requestMessage.Content = content;

        var response = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)

这是否需要:

using(var content = new StringContent("test"))
{

     using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri))
     {
    
            requestMessage.Content = content;
    
            var response = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

Dai*_*Dai 9

(在撰写本文时,OP的问题对于他们是否指的是HttpRequestMessage或含糊不清HttpResponseMessage,所以我将描述两者)。

  • HttpRequestMessage.Dispose():
  • HttpResponseMessage.Dispose():
    • 仅处理其response.Content.
    • 它不会处置response.RequestMessage或HttpRequestMessage以任何其他方式。
      • 因此,它不会处置其中HttpRequestMessage.Content任何一个。
    • 请参阅此处的源代码。

在完全发送完您的请求之前,您HttpRequestMessage不应该丢弃它(我相信在某些(罕见)情况下,即使请求的内容尚未发送完毕,您也会得到回复 - 就像使用外来传输编码方案时一样)或多部分请求) - 但通常最好假设所有涉及的对象的嵌套生命周期,所以 _if是完美的(事实并非如此),那么你会这样做:HttpClientHttpResponseMessageSystem.Net.Http

  1. 确保请求/响应完全完成。
  2. 首先处理掉响应内容。
  3. 然后HttpResponseMessage.
  4. 然后是请求的内容。
  5. 然后HttpRequestMessage.
  6. 然后HttpClient.

...但是,当您处理父请求/响应消息时,内容的处理是隐式的(并且您可能也不应该调用HttpClient.Dispose),所以只需执行以下操作:

using (HttpClient httpClient = this.httpClientFactory.CreateHttpClient()) // Only dispose HttpClient instances created by IHttpClientFactory. *DO NOT DISPOSE* of other HttpClient instances unless you know what you're doing!
using (HttpContent reqContent = new StringContent("test")) // Disposing of this HttpContent is unnecessary (as it's disposed when `request` is disposed).
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri) { Content = reqContent })
using (HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false)) // Note there is no `using()` block for the HttpContent object, this is because we don't "own" the object, the HttpResponseMessage does.
{
    String responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
    return responseBody;
}
Run Code Online (Sandbox Code Playgroud)

尽管记住HttpRequestMessage和HttpResponseMessage对象是分开的,所以你可以处理你的HttpRequestMessage但仍然返回HttpResponseMessage(反之亦然!)(尽管如果你处理了你不能返回Stream来自- 所以如果你需要返回一个你应该子类并包装返回的值,但不处理它,而是将其处理在子类的方法中。ResponseMessage.Content.ReadAsStream()HttpResponseMessageStreamStreamstreamHttpResponseMessageStreamDispose

public async Task<Stream> GetStreamAsync()
{
    using( HttpClient httpClient = this.httpClientFactory.CreateHttpClient() )
    using( HttpContent reqContent = new StringContent("test") )
    using( HttpRequestMessage request = new HttpRequestMessage( HttpMethod.Post, requestUri) { Content = reqContent })
    {
        HttpResponseMessage response = await this.httpClient.SendAsync( request ).ConfigureAwait(false);
        try
        {
            Stream stream = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            return new StreamWithDependencies( stream, response ); // See `class StreamWithDependencies` below.
        }
        catch // <-- When returning an IDisposable you should only dispose of things in `catch`, not in a `finally`.
        {
            response.Dispose(); // <-- Doing this means `response` (and `response.Content` will always be disposed) *and* can optionally outlive the method's scope if it follows the happy-path.
            throw; // <-- Don't swallow any exceptions, re-throw them (don't use `throw ex;` as that resets the StackTrace, always use either `throw;` (without a name) or throw a new exception with the caught exception passed as the `Exception.InnerException`.)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

下面是一个看起来像这样的示例class Stream2:要点是它代理Stream,并且它的Dispose方法将处理您告诉它的其他对象。

using System.Collections.Immutable;
using System.IO;

class StreamWithDependencies : Stream
{
    private readonly Stream subject;
    private readonly ImmutableList<IDisposable> disposeWith;

    public StreamWithDependencies( Stream subject, params IDisposable[] disposeWith )
    {
        this.subject = subject ?? throw new ArgumentNullException(nameof(subject));
        this.disposeWith = ( disposeWith ?? Array.Empty<IDisposable>() ).ToImmutableList();
    }

    protected override void Dispose(Boolean disposing)
    {
        if( disposing )
        {
            this.subject.Dispose();
            foreach( IDisposable d in this.disposeWith )
            {
                d.Dispose();
            }
        }

        base.Dispose(disposing);
    }

    public override Int32 Read(...)  => this.subject.Read( ... ):
    pubiic override Task<Int32> ReadAsync( ... ) => this.Subject.ReadAsync( ... );
    // etc
    // Only implement and forward methods for reading, no need to implement methods for writing.
}
Run Code Online (Sandbox Code Playgroud)

  • @joe _Sort-of_,问题是您的代码(在该示例中)不处理“HttpRequestMessage”构造函数抛出的情况,在这种情况下,“Content”将不会被释放(因为它没有附加到任何请求,并且因为失败的 ctor 中的异常不会导致调用“Dispose”,所以它是全有或全无)。 (2认同)
  • “覆盖所有基础并安全处置其自身‘使用’所需的内容是吗?” - **对,就是这样**。 (2认同)
  • 尽管请记住,“HttpRequestMessage”和“HttpResponseMessage”对象是分开的,因此您可以处理“HttpRequestMessage”,但仍然返回“HttpResponseMessage”(反之亦然!)(尽管您_不能_从“ResponseMessage”返回“Stream” .Content.ReadAsStream()` 如果您确实处理了 `HttpResponseMessage` - 因此,如果您需要返回一个 `Stream`,您应该子类化 `Stream` 并包装返回的 `stream`,但不要处理 `HttpResponseMessage` 和而是在“Stream”子类的“Dispose”方法内部处理它。 (2认同)