Aspnetcore: [Blazor] HttpClient is registered incorrectly

Created on 9 May 2020  路  14Comments  路  Source: dotnet/aspnetcore

The default registration in a Blazor app for HttpClient is

builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

According to Microsoft docs, HttpClient should be registered as a Singleton.

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors. Below is an example using HttpClient correctly.
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=netcore-3.1

Not only can this exhaust the number of available sockets (most likely in a Blazor Server app) but HttpClient also implements IDisposable, which means the dependency container will hold on to every HttpClient instance created until the user closes their browser window. See #21652

Done Servicing-consider area-blazor blazor-wasm

Most helpful comment

Yea this seems really wrong almost patch worthy for 3.1.

All 14 comments

@mrpmorris thanks for contacting us.

That only applies to the server. In the wasm implementation its backed by the wasm handler, so it is not an issue.

@javiercn every time you have an HttpClient injected into a component you'll get a new instance, but because it implements IDisposable the container will hold a reference to it in until the container is disposed, which isn't until the user goes away.

This means you'll keep creating new instances that aren't garbage collected,and you'll have a memory leak.

If you aren't convinced then look at the repo in #21652 and/or read the explanation here https://blazor-university.com/dependency-injection/dependency-lifetimes-and-scopes/transient-dependencies/#avoiding-memory-leaks

PS: I think Blazor should probably register HttpClient as Scoped.

Yea this seems really wrong almost patch worthy for 3.1.

DryIoc won't even allow you to register disposable transients: https://bitbucket.org/dadhi/dryioc/wiki/ReuseAndScopes.md#markdown-header-disposable-transient

Other containers as well, or do not pass validation, so you cannot validate the container. This is an issue.

My proposition would be as follows:

  • Resolve Transients from a newly introduced "Component IServiceScope" that is disposed with the component. I can imagine that making IComponent disposable (public API change) or adding this to ComponentBase is unwanted (does not support your own IComponent implementations), so my proposition would be to hide and attach this scope to the ComponentState that is already there in the renderer and available when a component is created and when a component is disposed.
  • Ditch OwnedComponentBase. It violates "Composition over inheritance" and was a bad idea anyway as it only supports a single owned service.
  • Either introduce a new "OwnedInject" attribute or adapt "InjectAttribute" with a parameter so that for services registered with scoped lifetime can either be resolved from the Component scope or the parent scope (root for WASM, request/circuit for Server Side Blazor (depending on prerender). This functionality replaces OwnedComponent and makes it possible to tie multiple services to the component scope or parent scope.
  • Adapt the inject directive so that it supports `@inject [owned] , for razor syntax to support the same functionality as the attribute.
  • Change ComponentFactory to use the different scopes based on the attribute / attribute parameter and the ServiceLifeTime (Transient or not). Now there is an additional issue, because IServiceProvider after container building does not allow introspection of the ServiceLifeTime anymore. So you'd need to get the ServiceDescriptors from IServiceCollection just before container building to get a list of all transient registrations and make that available to the ComponentFactory. Every compatible IoC container should at least support IServiceCollection so this is the safest bet to get the ServiceLifeTime for each service.

Willing to code this solution and make a PR. But is this the right direction for the solution?

I went for a new component ScopedComponent with [InjectOwned]

https://mrpmorris.blogspot.com/2020/04/blazor-scoping-services-to-component.html

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

Euh. msftbot? Does it really work that way? Open this please.

@javiercn this is an issue that has a lot of attention on the Gitter channel and we're confused why it's been marked by the bot as closed. Can you please re-open?

@simonziegler reopened.

Thanks @javiercn

+1
This has been an issue for me because I'm using JWT authentication and the authentication token is set on the httpclient header (httpClient.DefaultRequestHeaders.Authorization) after a successful authentication. Now when I inject the httpClient in a component to call a remote service requiring authentication, I get a new instance with a null authorization header! This is very inconvenient and it was working fine in 3.1 by the way (maybe it was injected as singleton in this version...).

Will be available in 3.2.1

Cool. That'll be 拢1, please :)

Was this page helpful?
0 / 5 - 0 ratings