With HttpClientFactory we can configure dependancy injection to create and manage the lifetime of HttpClients:
public class GitHubService : IGitHubService
{
public HttpClient Client { get; }
public GitHubService(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;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Then in Startup.cs we configure DI:
services.AddHttpClient<GitHubService>();
However, if the typed client has additional constructor arguments, how should these be provided? For example, if a repository name was to be passed in:
public class GitHubService : IGitHubService
{
public HttpClient Client { get; }
private readonly string _repositoryName;
public GitHubService(HttpClient client, string repositoryName)
{
_repositoryName = repositoryName;
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;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
$"/repos/aspnet/{_repositoryName}/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Perhaps this isn't a realistic example, but how would dependency injection be configured to provide the repository name?
An alternative if you don't want to use the named clients is to create a custom class for the extra parameters. Because the problem is that it does not know what to resolve for the type string, what you can do is to create an object with a string property that will contain the value that you want to pass, register it as a singleton and let the container resolve it.
Create a class that contains all the extra parameters. In your case "repositoryName"
public class RepositoryConfig
{
public string RepositoryName {get; set;}
}
Register the new class
services.AddSingleton(new RepositoryConfig { RepositoryName = "MyRepo"});
Then register the HttpClient
services.AddHttpClient<IGitHubService, GitHubService>();
Now your class will be instantiated correctly.
I managed to get this working by switching to named clients:
//To start with, create a named client:
services.AddHttpClient("GitHubClient", ctx => { ctx.BaseAddress = new Uri("https://api.github.com/"); });
//Then set up DI for the TypedClient
services.AddTransient<IGitHubService>(ctx =>
{
var clientFactory = ctx.GetRequiredService<IHttpClientFactory>();
var httpClient = clientFactory.CreateClient("GitHubClient");
return new GitHubService(httpClient, repositoryName);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With