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

ASP.NET Best Practices? #117

Open
elibroftw opened this issue Feb 6, 2023 · 5 comments
Open

ASP.NET Best Practices? #117

elibroftw opened this issue Feb 6, 2023 · 5 comments

Comments

@elibroftw
Copy link

elibroftw commented Feb 6, 2023

Is it better to create a new postmark client for each controller, or should we be using a Singleton or something like that and create only one client?

For reference, Microsoft tutorials for MongoDB creates a service for a collection and uses that service/singleton to insert/get.

@elibroftw
Copy link
Author

// Program.cs
builder.Services.AddSingleton(new PostmarkClient(builder.Configuration["PostmarkServerToken"]));
// MyController.cs
    public MyController(PostmarkClient postmarkClient) {
        // Initialize Postmark client
        _postmarkClient = postmarkClient;
}

@MariuszTrybus
Copy link
Contributor

Postmark-dotnet internally uses a static instance of HttpClient that is shared by all postmark clients - you can use either approach.

@rjgotten
Copy link

rjgotten commented Jun 3, 2024

Postmark-dotnet internally uses a static instance of HttpClient that is shared by all postmark clients

This is an anti-pattern.
For ASP.NET Core applications HttpClient is meant to be used from the service collection with AddHttpClient which registers a transient factory dependency that will use the framework implementation of IHttpClientFactory to create clients.

The clients are a thin layer on top of a message handler pool that handles the actual HTTP connections.
When a new HttpClient is created, the pool is checked for available compatible handlers that haven't expired yet. If any are available they are reconnected and reused. When an HttpClient is disposed (at the end of a service scope's lifetime) its handler is returned to the pool where it remains until it becomes stale.

The Http clients themselves are basically just thin wrappers to freely create/destroy with transient scope. All the heavy lifting is done in the message handlers whose lifetime is managed by the pool internal to the IHttpClientFactory implementation type.

If instead you use a static HttpClient then the handler is never allowed to return to the pool for as long as the application is running. This also means the handler, once stale, can never close and be refreshed. Among other things this means e.g. underlying DNS changes never propagate. It can lead to incredibly hard to diagnose 'random' network failures of a non-transient nature - that are instantly resolved by killing and restarting an application only to return at another 'random' future moment.

If this library is using an internal static field holding an HttpClient, then it is 'doing it wrong,' irrefutably.

@elibroftw
Copy link
Author

Thank you @rjgotten . As someone who is fairly newish at C# and .net, I only knew how to create http clients for asp.net but could never give such a fabulous answer on why it's done so which is why I asked my question to begin with.

@rjgotten
Copy link

rjgotten commented Jun 3, 2024

@elibroftw
Don't mention it. I see this going wrong a lot - mainly because it used to be the other way around.
For .NET Framework, the advice was to create HttpClient instances once, and reuse them as much as possible. Because they were top-heavy back then; didn't have the separation with all the heavy stuff living in pooled message handlers under the hood, but were each self-contained, handling the whole thing front-to-back.

That said, I just had a slightly deeper look and... yikes.
The way the SimpleHttpClient wrapper is implemented is using a finalizer rather than following the basic Dispose pattern. In .NET Core 5 and up finalizers are not guaranteed to run upon process termination. Which has some potentially nasty repercussions for network resources not being freed; network connections not being gracefully closed; etc.

This type of thing really needs to be Disposed correctly and should be using a System.AppDomain.ProcessExit handler to call the Dispose on the HttpClient to ensure that that happens before the process actually fully exits.

Normally you wouldn't have to worry about that because IServiceProvider calls Dispose on anything that is IDisposable when it itself is disposed. And it is disposed as part of the orderly shutdown. But if you start involving HttpClient instances that live on static fields ...

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

No branches or pull requests

3 participants