Typed client using HttpClient subclass

Viewed 2479

In my app I will have few TypedClient services. However, these classes will share a bunch of methods. My solution to this is to create CustomHttpClient:

public class CustomHttpClient:HttpClient
{
  //here shared methods
}

Then my typed client classes will use this derived class instead of standard HttpClient:

public class MyService : IMyService
{
  public SomeService(CustomHttpClient client, IConfiguration configuration){}
}

However, if i try to register this service in startup i get an error that there is no suitable constructor for 'MyService' :

services.AddHttpClient<IMyService, MyService>();

In the documentation I have found:

A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service

Does it mean, that it cannot accept a subclass of HttpClient? If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods ( i don't really like this solution). Is there maybe a workaround this, or extension methods are my only way out? I have tried registering also CustomHttpClient so DI container would find it but the error is still the same. What can you advise me?

3 Answers

Does it mean, that it cannot accept a subclass of HttpClient?

Yes.

If this is the case, then my only solution would be to implement shared methods as HttpClient extension methods

No. Per the docs typed clients encapsulate an HttpClient, rather than extending it. You configure the HttpClient in the constructor then add custom methods to the Typed Client that use the encapsulated HttpClient instance.

If you don't want to use the framework's pattern for HttpClient handling, you're free to create your own, but it's probably not worth the effort.

You can have typed clients that share a base class. eg

public class MyBaseTypedClient
{
    public HttpClient Client { get; }

    public MyBaseTypedClient(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    //other methods

}
public class MyTypedClient : MyBaseTypedClient
{
    public MyTypedClient(HttpClient client) : base(client) { }
}

If you need to add common methods only you can create an Interface with default implementation of those methods, then you would just need to inherit your IMyService with that interface.

You can also have a look at the below link which has some interesting workarounds.

https://github.com/dotnet/extensions/issues/1988

I think this is what you want to do:

(1) Create your base MyBaseTypedClient as follows:


public interface IMyBaseTypedClient
{ 
    //other methods
    //like FetchAsync(), PostAsync()
}

public class MyBaseTypedClient
{
    private HttpClient _client

    public MyBaseTypedClient(HttpClient client)
    {
        _client = client;
    }

    //other methods
    //like FetchAsync(), PostAsync()
}

(2) Then create your typed clients as follows:

public interface IServiceOne: IMyBaseTypedClient{ }

public class ServiceOne: MyBaseTypedClient, IServiceOne
{
    public ServiceOne(HttpClient httpClient) : base(httpClient)
    {
    }
}
public interface IServiceTwo: IMyBaseTypedClient{ }

public class ServiceTwo: MyBaseTypedClient, IServiceTwo
{
    public ServiceTwo(HttpClient httpClient) : base(httpClient)
    {
    }
}

(3) Then register as follows

services
    .AddHttpClient<IServiceOne, ServiceOne>(c => c.BaseAddress = new Uri("https://serviceone.com"));
    .AddHttpClient<IServiceTwo, ServiceTwo>(c => c.BaseAddress = new Uri("https://servicetwo.com");

(4) Then inject as follows

public void SomeMethodThatNeedsOne(IServiceOne serviceOne)
{
    //etc
}

public void SomeMethodThatNeedsTwo(IServiceTwo serviceTwo)
{
    //etc
}
Related