Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add named and typed clients section #32456

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

peter-csala
Copy link

@peter-csala peter-csala commented Apr 29, 2024

This PR is basically a follow up of my other PR against the dotnet/docs repository.

It extends the Make HTTP requests using IHttpClientFactory in ASP.NET Core page with named and typed client capability.


Internal previews

📄 File 🔗 Preview link
aspnetcore/fundamentals/http-requests.md Make HTTP requests using IHttpClientFactory in ASP.NET Core

@Rick-Anderson
Copy link
Contributor

@IEvangelist I'll do the edit review after you do the technical review.

@Rick-Anderson Rick-Anderson self-assigned this May 23, 2024
@peter-csala
Copy link
Author

@IEvangelist Hi David, can I ask you to please review my PR?

@IEvangelist
Copy link
Member

I had a look, seems ok - but let's please have @CarnaViire review this for technical accuracy instead.


Named clients and typed clients have their own strengths and weaknesses. There is a way to combine these two client types to get the best of both worlds.

The primary use case is the following: You want to register the exact same type client multiple times but with different settings (different base address, timeout, and credentials for example).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I believe this is the case where you should use just an ordinary Transient service instead of a typed client.

That service could either

  1. use IHttpClientFactory and incapsulate multiple named clients inside
service.UseNamedClient();
service.UseOtherClient();

/* Output:
Name=NamedClient, BaseAddress=https://some-uri/
Name=OtherNamedClient, BaseAddress=https://other-uri/
*/

class Service(IHttpClientFactory _httpClientFactory /*, ... */)
{
    private void UseHttpClient(string name) => Console.WriteLine($"Name={name}, BaseAddress={_httpClientFactory.CreateClient(name).BaseAddress}");
    public void UseNamedClient() => UseHttpClient("NamedClient");
    public void UseOtherClient() => UseHttpClient("OtherNamedClient");
}

-OR-

  1. Have an instance of HttpClient passed as a parameter to the method each time
foreach (var name in new[] {"NamedClient", "OtherNamedClient" })
{
    service.UseHttpClient(name, _httpClientFactory.CreateClient(name));
}

/* Output:
Name=NamedClient, BaseAddress=https://some-uri/
Name=OtherNamedClient, BaseAddress=https://other-uri/
*/

class Service(/* ... */)
{
    private void UseHttpClient(string name, HttpClient client) => Console.WriteLine($"Name={name}, BaseAddress={client.BaseAddress}");
}

-OR-

  1. Leverage Keyed Services DI Pattern (and it can even be non-transient in such case):
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddHttpClient("NamedClient", c => c.BaseAddress = new Uri("https://some-uri"));
services.AddHttpClient("OtherNamedClient", c => c.BaseAddress = new Uri("https://other-uri"));

services.AddKeyedSingleton<Service>(KeyedService.AnyKey); // instead of a typed client
services.AddSingleton<TwoNamesDependentService>();

var provider = services.BuildServiceProvider();

var dependentService = provider.GetRequiredService<TwoNamesDependentService>();
dependentService.UseServices();

/* Output:
Name=NamedClient, BaseAddress=https://some-uri/
Name=OtherNamedClient, BaseAddress=https://other-uri/
*/

class Service([ServiceKey] string _name, IHttpClientFactory _httpClientFactory)
{
    private HttpClient Client => _httpClientFactory.CreateClient(_name);
    public void UseHttpClient() => Console.WriteLine($"Name={_name}, BaseAddress={Client.BaseAddress}");
}

class TwoNamesDependentService([FromKeyedServices("NamedClient")] Service _service, [FromKeyedServices("OtherNamedClient")] Service _otherService)
{
    public void UseServices()
    {
        _service.UseHttpClient();
        _otherService.UseHttpClient();
    }
}

}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
var tolerantClient = _httpClientFactory.CreateClient("LatencyTolerant");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exact scenario should be handled not by multiple clients, but by resilience strategies applied via Polly

Copy link
Member

@CarnaViire CarnaViire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tagging me @IEvangelist!

While I appreciate your contribution @peter-csala I believe this is not a good guidance, see my comment #32456 (comment). If you strongly believe using multiple named clients should be highlighted in the docs, I'd propose to change the guidance to the one I provided in the comment.

I see that dotnet/docs#40359 was merged but I missed it -- @IEvangelist I think we would need to either revert it or substitute it with the guidance from #32456 (comment).

On a side note:
I also keep thinking that we need to make some effort to reduce duplications between the docs -- as HttpClientFactory belongs to BCL now, it would be better to have the conceptual docs there, and only leave ASP.NET Core related stuff here, and redirect to .NET docs otherwise. But this needs to be done carefully, not to disrupt the users flow. Let's discuss it later @IEvangelist

@peter-csala
Copy link
Author

Thank you @CarnaViire for the review. I agree that my made up example is not the best. I wanted to create a simple example to showcase the usage of named and typed clients because (for some unknown reason) they are not listed on any of the documentation pages. Neither here nor on the dotnet docs.

Do you have any idea what would be an ideal sample scenario for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants