Thursday, February 2, 2017

Disposing HttpRequestMessage

I've been working with HttpClient alot lately (oh, how I wish they created an interface for it....) and have noticed quite a few things come up.

Today I wanted to figure out if I actually need to worry about disposing an HttpRequestMessage which I create and use with HttpClient. Because I'm creating the instance directly and I know it's not a derived version (for example, passed into a method as an argument), I can confidently interrogate the circumstances under which it should be disposed.

If I were accepting it as an argument and was expected to control the rest of the objects lifetime, then I would say you *should* to dispose the HttpRequestMessage because you are expected to.

If you create one and use it, specifically with System.Net.Http.HttpClient, you do not actually need to dispose the request object assuming that you successfully call SendAsync and here's why.

I was looking into the corefx/HttpClient repo and in this case, there is a method that is called which disposes the content after reading the response (with a nice comment).


private void HandleFinishSendAsyncCleanup(HttpRequestMessage request, CancellationTokenSource cts, bool disposeCts)
{
    try
    {
        // When a request completes, dispose the request content so the user doesn't have to. This also
        // helps ensure that a HttpContent object is only sent once using HttpClient (similar to HttpRequestMessages
        // that can also be sent only once).
        request.Content?.Dispose();
    }
    finally
    {
        if (disposeCts)
        {
     cts.Dispose();
        }
    }
}


This method is called in a finally block as part of sending the request so it should always get called even if there is an error. I'm not sure there's really anything else to dispose aside from the content so its probably good enough, but I couldn't find any information or source code to persuade me one way or another.

The helper methods for Post, Get, Delete, and Put do not wrap the newly created HttpRequest created in a using statement or make any additional attempt to dispose.

public Task GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken)
{
    return SendAsync(new HttpRequestMessage(HttpMethod.Get, requestUri), completionOption, cancellationToken);
}

public Task PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri);
    request.Content = content;
    return SendAsync(request, cancellationToken);
}

public Task PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken)
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, requestUri);
    request.Content = content;
    return SendAsync(request, cancellationToken);
}

public Task DeleteAsync(Uri requestUri, CancellationToken cancellationToken)
{
    return SendAsync(new HttpRequestMessage(HttpMethod.Delete, requestUri), cancellationToken);
}


I was also looking into the mono version, which does not dispose the request message or its content for you.

So, if you are creating an HttpRequestMessage and passing that message to the SendAsync method of a System.Net.Http.HttpClient, you can rest assured that the request has been disposed for you. If you have reason to believe that an exception may be thrown between creating the message, and calling SendAsync, a using statement would ensure that it is disposed. HOWEVER, even in this case, the only thing that actually gets disposed is the Content property and of the standard content types, only StreamContent actually needs to be disposed. If you are using, say, StringContent, it does not need to be disposed anyway and therefore the message does not need to be disposed.

Again, if we were considering the case of accepting method parameters which we did not create, you can not make these assumptions, but for code which looks like this, you should be pretty safe!

/// <summary> Gets the single, shared HttpClient instance </summary>
protected HttpClient Client {get;}

public async Task<Order> GetOrder(string orderId)
{
    // this message will be dispose when SendAsync is awaited
    HttpRequestMessage getOrderMessage = new HttpRequestMessage(HttpMethod.Get, $"api/orders?orderId={orderId}");

    // there is not really a chance for an error here...
    using(HttpRepsonseMessage response = await this.Client.SendAsync(getOrderMessage))
    {
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsAsync<Order>();    
    }
}