4

I'm making a library for other developers to use and it's for communicating with a 3rd party web service. I read the issue that come up around HttpClient and I would like to handle this properly, so that the library doesn't become a burden.

I thought I would make a base ServiceClient that would have 3 ways to construct it.

The first would instantiate the HttpClient itself via using a static Lazy<HttpClient> to keep a single instance in the lib. If this constructor is never used, the inner object never gets instantiated.

The second constructor would accept an HttpMessageHandler and instantiate the HttpClient with it every time. This would be sort of a unit test-friendly mocking constructor that wouldn't get much use otherwise.

The third constructor would accept an HttpClient instance, making the developer responsible for the lifetime of the object if that is their wish.

My initial thought was that a developer might be already using an HttpClient in their code and they could pass that same instance to the lib and prevent having more than one instance. But then I thought the client and the lib would be communicating with totally different endpoints and the lib's configuration (base url, headers, etc) would conflict with the calling code, which is not useful for anyone.

So now I'm leaning towards dropping the 3rd constructor, because I'm not sure if it brings anything to the table.

Does anyone have experience with this?

1

3 Answers 3

1

HttpClient isn't really designed to be replaced with an alternative (it doesn't have a series of abstract interfaces which are generic).

So I doubt there are any API-compliant HttpClient replacements. That makes it a quite poor candidate for the kind of abstraction you are talking about.

The reasons which MIGHT make sense in your application is because there maybe some upstream requirements for communication which require either parameters to be passed in or otherwise specified. For example, you might need to specify a username/password, and client side cert, or set other parameters. It is for THIS reason, primarily, that allowing an optional CTOR argument of type HttpClient probably makes sense.

If you elect to provide an optional HttpClient parameter to the constructor, I would NOT define it so that the caller controls its lifetime. There would be no easy way to coordinate the assumptions about lifetime. Instead, consider it an ADOPTION, where your library ADOPTS ownership of the HttpClient passed in.

7
  • I don't understand what you mean by alternative to HttpClient. I'm not making an alternative. I'm making a library that you instantiate, call GetFooBar() to retrieve data from the external service. The question is about sticking to an internal HttpClient, or allow one to be injected.
    – romeozor
    Jul 31, 2018 at 19:50
  • adding extra headers for routing perhaps
    – Ewan
    Jul 31, 2018 at 20:45
  • @romeozor Among the reasons one MIGHT have wanted to allow specification of the HttpClient as an argument, was to select a different body of code to handle the http interaction (like libcurl for example). I was just saying that HttpClient isn't written in a way to make this practical. Which is why I then focused on a few more common reasons people want to do the sort of injection you were talking about. As Ewan said, to specify additional special headers, or client side certs, or auth headers, TLS version rules, etc. Jul 31, 2018 at 21:02
  • I disagree that the library should take ownership of the client. If you create it, you clean it up. (But maybe that’s just my C++ experience talking.)
    – RubberDuck
    Aug 2, 2018 at 23:15
  • @RubberDuck FOR C++ Use uniqueptr. But for either language I can explain better. If you want shared ownership for client that adds worlds of useless complexity and bugs. Can both owners use it at same time from different threads? How about even single threaded connect to different urls? Can one owner delete and other continue using? For shared ownership to work you need clear policies on how both can use at same time and manage lifetime. Sometimes warranted. Not here Aug 3, 2018 at 3:52
1

In case you don't need the full interface of HttpClient, I'd go another route: I'd create an interface for HttpClient and let the user inject an implementation.

You can define an interface that only requires the methods you actually require from HttpClient, let's say something like this:

public interface IHttpClient {
    public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
}

and then offer (and use) a wrapper:

public class HttpClientWrapper: IHttpClient {
    internal HttpClientWrapper(HttpClient client) {
        _client = client ?? new HttpClient();
    }

    public static FromHttpClient(HttpClient client) {
        return new HttpClientWrapper(client);
    }

    public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content) 
    {
        return await _client.PostAsync(requestUri, content);
    }

}

This means that you can, in an explicit way, define what parts of an HttpClient you actually depend on; and, more importantly, if anyone wants to make some automated tests that use your library, the actual http requests can be mocked with a custom implementation.

0

In general HttpMessageHandler is designed for this kind of injection. It allows the instantiating code to add their own changes to the way the http messages are sent. For example by adding an auth header.

I have written some api clients where a HttpClient instance is passed into the constructor. This was too allow for an odd case with a web api (A) which called another web api (B). I wanted to recreate the api client object per request (to A) in order to pass in request specific data using construction parameters (on the B client) while avoiding the problem of creating an HttpClient object per request (to A).

But really if I am honest it probably wasn't the best solution. I wanted to avoid adding an extra parameter to all the methods, changing the interface and this was the way that caught my eye.

I think in another similar situation I simply passed in a IGetPerRequestData object which had access to the request context, So it would totally have been possible to do the job with a custom HttpMessageHandler.

Side note: you really shouldn't use a static to store the http handler, what if you have two instances pointing at different endpoints? simply store it as as private readonly field

public class MyApiClient
{
    private readonly HttpClient client;
    public MyApiClient(HttpMessageHandler handler = null)
    {
        if(handler != null) 
        { 
            this.client = new HttpClient(handler); 
        }
        else
        {
            client = new HttpClient();
        }
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.