I'm building a .NET Core class library wrapper for a REST API that, ideally, could be used in both console applications and ASP.NET Core web applications. So far, I've based development on supporting dependency injection for the latter by creating a typed client for each group of REST API methods, i.e. one client for each group of (Index, Create, Destroy, etc.). It may be important to note that in this case, each group has same base address, and in most cases, the same authorization header would be used.
The typed clients inherit from a base client that handles some of the configuration:
public abstract class BaseClient
{
protected readonly HttpClient _client;
public BaseClient(IConfigService config, HttpClient client)
{
_client = client;
_client.BaseAddress = new Uri(config.BaseAddress);
// More configuration
}
}
So I end up with something like this:
public class ClientA : BaseClient, IClientA
{
public ClientA(IConfigService config, HttpClient client) : base(config, client) { }
// Some methods
}
public class ClientB : BaseClient, IClientB
{
public ClientB(IConfigService config, HttpClient client) : base(config, client) { }
// More methods
}
And so on. With this I've written an IServiceCollection
extension that registers all of these services:
public static class WrapperServiceCollectionExtensions
{
public static IServiceCollection AddWrapper(this IServiceCollection services, string username, string key)
{
services.AddSingleton<IConfigService>(new ConfigService(username, key));
services.AddHttpClient<IClientA, ClientA>();
services.AddHttpClient<IClientB, ClientB>();
// More clients
return services;
}
}
As I understand it, using this DI pattern, a new HttpClient
is instantiated for each typed client, so I see no issue with a typed client being individually responsible for its own configuration. But let's say I want to create these clients in a console application. Finally, I get to my question: knowing that the clients all have the same base address and all will most likely be using the same credentials for authorization, what would be the recommended way of instantiating them in a console application? I'm inclined to create a new HttpClient
for each typed client:
class Program
{
private static HttpClient _clientA = new HttpClient();
private static HttpClient _clientB = new HttpClient();
static void Main(string[] args)
{
var config = new ConfigService("user", "key");
var a = new ClientA(config, _clientA);
var b = new ClientB(config, _clientB);
}
}
But is this really necessary if the configurations would be the same for both anyways? Should I only worry about one HttpClient
unless I'm dealing with multiple configurations? Should I even have multiple typed clients if the configuration would be the same for each? To me, it feels a little hacky to let each typed client successively overwrite a single HttpClient
's configuration in its constructor, but in terms of behavior, unless different credentials were used, I don't think it would matter. Seriously stuck here.
Best Answer
First of all this a good question. :)
Let me try to help you by comparing the two approaches that you have mentioned.
One typed client for each group of API
Pros
Cons
A single shared client for all groups
Pros
Cons
IOException
if you use wrong TLS versionSocketException
if you exhaust the connection pool (too frequent connection open requests)BaseAddress
of the client can causeInvalidOperationException
if there are one or more pending requests.Timeout
orMaxResponseContentBufferSize
CancelPendingRequests
method call can take a whileMy point is that each approach has its own strengths and trade-offs. It is up to you to decide which one to use based on the functional and non-functional requirements.