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

HttpClientFactory doc improvements #44730

Open
22 tasks
CarnaViire opened this issue Feb 10, 2025 · 0 comments
Open
22 tasks

HttpClientFactory doc improvements #44730

CarnaViire opened this issue Feb 10, 2025 · 0 comments

Comments

@CarnaViire
Copy link
Member

Describe the issue or suggestion

Follow-ups from #44533:

  • 1. Simplify three main steps example description

    HttpClientFactory Keyed DI docs #44533 (comment)

    This code snippet illustrates how the registration `(1)`, obtaining the configured `HttpClient` instance `(2)`, and using the obtained client instance as needed `(3)` can look when using the _Keyed DI approach_.

    This sentence feels hard to read and interpret. Would it make sense to rewrite the beginning of the paragraph as following?

    Consider the IHttpClientFactory-related code from the Basic Usage example. The code snippet illustrates the three main steps using the Keyed DI approach:

    1. Registration
    2. Obtaining the configured HttpClient instance
    3. Using the obtained client instance
    services.AddHttpClient("github", /* ... */).AddAsKeyed();                // (1)
    app.MapGet("/", ([FromKeyedServices("github")] HttpClient httpClient) => // (2)
        //httpClient.Get....                                                 // (3)
  • 2. Link captive dependency term

    HttpClientFactory Keyed DI docs #44533 (comment)

    Additionally, the Scoped lifetime of the clients can help catch cases of captive dependencies:

    I see that the term captive dependency is defined later in the doc. Would it make sense to link that paragraph?

  • 3. Use emojis in titles

    HttpClientFactory Keyed DI docs #44533 (comment)

    ### Avoid captive dependency

    Not sure if it fits our guidelines but for me it would read better if we used emojis in the titles to raise attention instead of the nested [!IMPORTANT] sections.

    ### ❌ Avoid captive dependency
    
  • 4. Separate code blocks for better parsing

    HttpClientFactory Keyed DI docs #44533 (comment)

    For me the diff feels harder to parse than if we had two separate code blocks (preferably with detailed explanations in also comments rather then just in the text below).

  • 5. Reduce overuse of highlighting

    HttpClientFactory Keyed DI docs #44533 (comment)

    I understand that HttpClientFactory is full of traps so all these sections are important, but it feels like highlighting is heavily overused in this paragraph, so it somewhat loses its purpose.

  • 6. Present examples of traps

    HttpClientFactory Keyed DI docs #44533 (comment)

    > `KeyedService.AnyKey` registrations define a mapping from _any_ key value to some service instance. However, as a result, the Container validation doesn't apply, and an _erroneous_ key value _silently_ leads to a _wrong instance_ being injected.

    Would it make sense to present examples of the traps?

  • 7. Avoid feeling words

    HttpClientFactory Keyed DI docs #44533 (comment)

    `IHttpClientFactory` and Named `HttpClient` instances, unsurprisingly, align well with the Keyed Services idea. Historically, among other things, `IHttpClientFactory` was a way to overcome this long-missing DI feature. But plain Named clients require you to obtain, store, and query the `IHttpClientFactory` instance—instead of injecting a configured `HttpClient`—which might be inconvenient. While Typed clients attempt to simplify that part, it comes with a catch: Typed clients are easy to [misconfigure](httpclient-factory-troubleshooting.md#typed-client-has-the-wrong-httpclient-injected) and [misuse](httpclient-factory.md#avoid-typed-clients-in-singleton-services), and the supporting infrastructure can also be a tangible overhead in certain scenarios (for example, on mobile platforms).

    • I'd avoid feeling words like "unsurprisingly" here, and I saw "nasty" somewhere later in the doc.

    • What does this has to do with Keyed DI:

    and the supporting infrastructure can also be a tangible overhead in certain scenarios (for example, on mobile platforms).

    ???

  • 8. Clarify "request handler"

    HttpClientFactory Keyed DI docs #44533 (comment)

    In the example, the configured `HttpClient` is injected into the request handler through the standard Keyed DI infrastructure, which is integrated into ASP.NET Core parameter binding. For more information on Keyed Services in ASP.NET Core, see [Dependency injection in ASP.NET Core](/aspnet/core/fundamentals/dependency-injection#keyed-services).

    What does "request handler" mean here? It's confusing because usually the handlers are put inside the client, not the other way around. So either I'm missing something or it's used here to mean something else, which I'm also missing.

  • 9. Explicitly use "scoped"

    HttpClientFactory Keyed DI docs #44533 (comment)

    services.AddHttpClient("scoped").AddAsKeyed();

    I know scoped is default, but I'd use it here explicitly or used a different name. Some people might get confused and think that the string "scoped" has any meaning appart from just being the name.

  • 10. Add small example

    HttpClientFactory Keyed DI docs #44533 (comment)

    If you call `AddAsKeyed()` within a Typed client registration, only the underlying Named client is registered as Keyed. The Typed client itself continues to be registered as a plain Transient service.

    This is not related to the previous example, is it? It took me a while to understand what it's saying, so maybe another small example wouldn't hurt.

  • 11. Define "reasonable" and link to API docs

    HttpClientFactory Keyed DI docs #44533 (comment)

    In cases when client's longevity can't be avoided—or if it's consciously desired, for example, for a Keyed Singleton—it's advised to [leverage `SocketsHttpHandler`](httpclient-factory.md#using-ihttpclientfactory-together-with-socketshttphandler) by setting `PooledConnectionLifetime` to a reasonable value.

    What does "reasonable" mean? "Small enough value to observe and react to DNS changes regularly"...
    Also, I'd add a link to PooledConnectionLifetime API ref docs.

  • 12. Link Singleton and Transient pitfalls

    HttpClientFactory Keyed DI docs #44533 (comment)

    While Scoped lifetime is much less problematic for the Named `HttpClient`s (compared to Singleton and Transient pitfalls), it has its own catch.

    Singleton and Transient pitfalls

    Link?

  • 13. Remove "as expected"

    HttpClientFactory Keyed DI docs #44533 (comment)

    > Keyed Scoped lifetime of a specific `HttpClient` instance is bound—as expected—to the "ordinary" application scope (for example, incoming request scope) where it was resolved from. However, it does NOT apply to the underlying message handler chain, which is still managed by the `IHttpClientFactory`, in the same way it is for the Named clients created directly from factory. `HttpClient`s with the _same_ name, but resolved (within a `HandlerLifetime` timeframe) in two different scopes (for example, two concurrent requests to the same endpoint), can reuse the _same_ `HttpMessageHandler` instance. That instance, in turn, has its own separate scope, as illustrated in the [Message handler scopes](httpclient-factory.md#message-handler-scopes-in-ihttpclientfactory).

    "as expected" is unnecessary here.

  • 14. Remove "nasty", "unfortunately"

    HttpClientFactory Keyed DI docs #44533 (comment)

    > The [Scope Mismatch](httpclient-factory-troubleshooting.md#httpclient-doesnt-respect-scoped-lifetime) problem is nasty and long-existing one, and as of .NET 9 still remains [unsolved](https://github.com/dotnet/runtime/issues/47091). From a service injected through the regular DI infra, you would expect all the dependencies to be satisfied from the same scope—but for the Keyed Scoped `HttpClient` instances, that's unfortunately not the case.

    Remove "nasty", "unfortunately".

  • 15. Simplify "hidden" Named clients sentence

    The name doesn't have to be `nameof(Service)`, but the example aimed to minimize the behavioral changes. Internally, typed clients use Named clients, and by default, such "hidden" Named clients go by the linked Typed client's type name. In this case, the "hidden" name was `nameof(Service)`, so the example preserved it.

    such "hidden" Named clients go by the linked Typed client's type name

    -->

    such hidden Named clients go by the Typed client's type name

    • The addition of "linked" confused me and I thought it has some special meaning. I don't think you need any adjective here to express that it's talking about the typed client from the beginning of the sentence.

    • Also, I see "Named client" and "Typed client" capitalized, but also not capitalized: "typed clients". Is there a reason for it?

  • 16. Reduce quotes usage

    Technically, the example "unwraps" the Typed client, so that the previously "hidden" Named client becomes "exposed," and the dependency is satisfied via the Keyed DI infra instead of the Typed client infra.

    Too much quotes and I don't think they are used here correctly. If you want to emphasize unwraps, hidden, exposed, make them bold.

  • 17. Clarify "AnyKey" and conjunction usage

    > `KeyedService.AnyKey` registrations define a mapping from _any_ key value to some service instance. However, as a result, the Container validation doesn't apply, and an _erroneous_ key value _silently_ leads to a _wrong instance_ being injected.

    • Is "AnyKey" some constant? Does it have API docs that could be linked? It just appears here out of nowhere.
    • However, as a result, - 2 conjunctions with commas, pick one and stick with it.
  • 18. Avoid overusing —

    > For Keyed `HttpClient`s, a mistake in the client name can result in erroneously injecting an "unknown" client—meaning, a client whose name was never registered.

    I feel like you started overusing — instead of splitting into 2 sentences, or just using comma.

  • 19. Call out differences in similar examples

    provider.GetRequiredKeyedService<HttpClient>("unknown"); // OK (unconfigured instance)

    This is very similar to the example from the beginning of the document and I'd explicitly call out the difference and why it doesn't throw here.

  • 20. Remove unnecessary quotes and unfortunate

    Even though the "global" opt-in is a one-liner, it's unfortunate that the feature still requires it, instead of just working "out of the box." For full context and reasoning on that decision, see [dotnet/runtime#89755](https://github.com/dotnet/runtime/issues/89755) and [dotnet/runtime#104943](https://github.com/dotnet/runtime/pull/104943). In short, the main blocker for "on by default" is the `ServiceLifetime` "controversy": for the current (`9.0.0`) state of the DI and `IHttpClientFactory` implementations, there's no single `ServiceLifetime` that would be reasonably safe for all `HttpClient`s in all possible situations. There's an intention, however, to address the caveats in the upcoming releases, and switch the strategy from "opt-in" to "opt-out".

    • Quotes for global are unnecessary. It's not so-called global, it is global.
    • Remove unfortunate.
  • 21. Describe scenarios for opting out

    ## How to: Opt out from keyed registration

    You just said that it's opt-in, so opting out might not make sense here.
    I'd describe scenarios when one would want to use that.

  • 22. Fix typo "happen"

    3. If both `ConfigureHttpClientDefaults` and specific client name were used, all defaults are considered to "happen" before all per-name settings. Thus, defaults can be disregarded, and the last of the per-name settings wins.

    "happen" --> happen

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

No branches or pull requests

1 participant