`AddHttpClient<MyService>()` fails within Modules.
See original GitHub issuenew version of #6677 as it was asked that a new issue be created.
Typed calls to AddHttpClient
are failing within a module in .Net 5.0
and .Net 6.0
. (I tried rolling back to .Net 5.0 in case OC 1.1 was incompatibile with 6.0).
// MainProject/Startup.cs
class MainStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<MyService>(); // this is cool
}
}
// Module/Startup.cs
class ModuleStartup : OrchardCore.Modules.StartupBase
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<MyService>(); // NullReferenceException
}
}
The issue is as follows. In AspNetCore, AddHttpClient
registers a singleton of HttpClientMappingRegistry
.
When initializing a typed HTTP Client, HttpClientBuilderExtensions.AddTypedClient
calls HttpClientBuilderExtensions.ReserveClient
, which iterates through the Builder’s Services and attempts to find an instantiated instance of HttpClientMappingRegistry.
Orchard Core’s FeatureAwareServiceCollection doesn’t automatically instantiate Singletons, for the reason that @marlon-tucker linked in the initial issue – it doesn’t implement IDisposable
, so it isn’t instantiated until ServiceProvider.GetService<HttpClientMappingRegistry>()
is called:
As you can see in the ReserveClient
method above, however, GetService is never called on the HttpClientMappingRegistry
– it is instead selected through LINQ, meaning it is never instantiated. Single()
returns a registered service, but Single().ImplementedInstance
is null.
I’ll try and provide a repro when I have the chance.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:39 (33 by maintainers)
We have a main TenantStartup.cs that we run before doing any other modules and were able to work around this problem by adding the following code earlier in that
In the meantime here some thoughts based on what I saw.
First I think that connections sharing is done at a lower IO level.
What I saw is that anyway a new
HttpClient
is always created and that handlers are resolved or activated, for a typed client an object factory is cached but not sure it takes a lot of memory.Yes, this is the Handlers Pipeline that is cached when built by the
DefaultHttpClientFactory
, but here too not sure that it takes a lot of memory, this is not the activated handler instances that are cached.Note: Tenants are isolated “Sites” with their Filters/Handlers, Pipeline, APIs and so on, so make sense that they also have their own http client Handlers Pipeline, but I understand the memory concern.
One annoying thing is that the client factory creates a scope that will be used to activate handlers, which is not a
ShellScope
which e.g. prevents a shell to be disposed while a scope is still active.Anyway the scope is created from the service provider resolved by the factory, so what happens if a
DelegatingHandler
resolve services that may depend on the enabled features of a given tenant.This is the case of our
Twitter
handler that injects tenant specific services including a security service, more over this handler is only registered by the tenants having theTwitter
feature enabled.Note: Here we may need a custom service provider as we use for the
RequestServices
that we made aware of the currentShellScope
so that it returns theShellScope.Services
if a current one exists.So, in case there is not so much memory consumption by duplicating some caches, maybe a good compromise would be to support the point 2. from my previous list.
This to allow http client configurations at both host and tenant levels but without sharing them, and let it working as before at the tenant level (no sharing across tenants).