3

I need to make a series of HTTP calls to obtain database credentials from a 3rd party vault, given that I need to run this code in Program.cs or, at the very latest, Startup.cs before adding the DBContext, I need to be able to make these calls without using IHttpClientFactory, as that requires Dependency Injection to already have been initialized.

The following code works fine when called during runtime, but doesn't work during the ConfigureAppConfiguration step.

HttpClient client = _clientFactory.CreateClient();

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{_configuration["CredentialsVault:vaultUrl"]}Auth/SignAppIn");
request.Headers.Add("Authorization", $"PS-Auth key={_apiKey}; runas={_runAsUser};");

var response = await client.SendAsync(request);

Is there a way I can either make an HTTP call without having to rely on Dependency Injection, or delay AddDbContext until after Dependency Injection has been set up?

I have tried creating an instance of HttpClient like this:

HttpClient client = new HttpClient();

However this did not seem to work, and according to this question it should not be instantiated like that.

0

2 Answers 2

5

You don't need to inject a typed HttpClient, or use IHttpClientFactory. It is recommended to solve certain historical issues, but it's not mandatory.

If you really need to resolve dependencies and configuration to call remote resources (during or before configuration construction), or you have a typed client that needs to be instantiated, you could just spin up a new ConfigurationBuilder (if needed) and ServiceProvider.

var preConfiguration = new ConfigurationBuilder()
    //... add your sources
    .Build();

var collection = new ServiceCollection()
     .AddHttpClient<...>(...)
     ...

var provider = collection.BuildServiceProvider();

var client = provider.GetService<SomeClient>();

client.YourCall();

//... normal configuration here

The above approach also gives you the ability and flexibility to insert Handlers to the request chain if needed.

However, this is likely not your problem. From your question all you need to do is make the same call with a standard HttpClient and call it in the same way you already call it. Once again, you can spinup a ConfigurationBuilder if you need to resolve configuration.

var preConfiguration = new ConfigurationBuilder()
    //... add your sources
    .Build();

HttpClient client = new HttpClient();

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{preConfiguration["CredentialsVault:vaultUrl"]}Auth/SignAppIn");
request.Headers.Add("Authorization", $"PS-Auth key={_apiKey}; runas={_runAsUser};");

var response = await client.SendAsync(request);

//... normal configuration here

Fun Fact

.net 6 is adding a ConfigurationManager to address some of these configuration problems, as such it implements both IConfigurationBuilder and IConfigurationRoot and gives you the ability to partially build sources and providers, to resolve and in turn add to the builder again.

This solves the use-case of needing to resolve and build configuration in a consistent way. For instance, you could use the Manager to resolve a configuration, spin up a Service Provider and resolve a dependency to call a remote resource, and then add more configuration to the IConfigurationRoot.

Sign up to request clarification or add additional context in comments.

2 Comments

Do I need to manually add appsettings.json as a source when doing _configuration = new ConfigurationBuilder().Build();? When doing _configuration["CredentialsVault:apiKey"] it's returning null, I was able to get it to work by passing a configuration instance from Program.cs.
Yes you would need to add any json configration as sources to the new peeconfiguraiton to use it
3

I believe we finally have clear guidelines on how to use HttpClient without DI and IHttpClientFactory in .NET core and .NET 5+ posted on Microsoft Docs.

In .NET Core and .NET 5+: Use a static or singleton HttpClient instance with PooledConnectionLifetime set to the desired interval, such as 2 minutes, depending on expected DNS changes. This solves both the port exhaustion and DNS changes problems without adding the overhead of IHttpClientFactory.

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(15) // Recreate every 15 minutes
};
var sharedClient = new HttpClient(handler);

The connection pool for an HttpClient is linked to the underlying SocketsHttpHandler. When the HttpClient instance is disposed, it disposes all existing connections inside the pool.

In this article, I also find useful the example with resilience policies and static clients, since I have been using Polly and Microsoft.Extensions.Http.Polly nuget packages in combination with IHttpClientFactory in my projects.

It's possible to configure a static or singleton client to use any number of resilience policies using the following pattern:

using System;
using System.Net.Http;
using Microsoft.Extensions.Http;
using Polly;
using Polly.Extensions.Http;

var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

var socketHandler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(15) };
var pollyHandler = new PolicyHttpMessageHandler(retryPolicy)
{
    InnerHandler = socketHandler,
};

var httpClient = new HttpClient(pollyHandler);

1 Comment

Unfortunately this answer is all over SO but it relies on an old nuget :(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.