Why Is Httpclient Baseaddress Not Working

Why is HttpClient BaseAddress not working?

It turns out that, out of the four possible permutations of including or excluding trailing or leading forward slashes on the BaseAddress and the relative URI passed to the GetAsync method -- or whichever other method of HttpClient -- only one permutation works. You must place a slash at the end of the BaseAddress, and you must not place a slash at the beginning of your relative URI, as in the following example.

using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("http://something.com/api/");
var response = await client.GetAsync("resource/7");
}

Even though I answered my own question, I figured I'd contribute the solution here since, again, this unfriendly behavior is undocumented. My colleague and I spent most of the day trying to fix a problem that was ultimately caused by this oddity of HttpClient.

HttpClient with BaseAddress not working (production + integration test)?

Maybe HttpClient created from WebApplicationFactory does not have the behavior I assumed it has. Turns out the problem was in the Controller class which had the [Route("[controller]")] attribute, and the Ping method inside it had [Route(ApiRoutes.V1.Health.PingGet)].

In IIS ping was available at "/api/v1/ping" (not sure why), but when starting from VisualStudio swagger showed that the actual endpoint was "/Health/api/v1/ping" (which is wrong).

Removing the [Route("[controller]")] attribute fixed the problem and now both tests pass.

Why HttpClient does not hold the base address even when it`s set in Startup

Okay so I will answer my post because with the suggested TYPED way of doing it was causing problems with the values not being set inside the httpClient, E.G BaseAddress was always null.

In the startup I was trying to go with typed httpclient e.g

services.AddHttpClient<IReService, ReService>(c => ...

But instead of doing that, I choose the to go with the Named client. Which means that in the startup we need to register the httpclient like this

services.AddHttpClient("recipeService", c => {
....

And then in the service itself I used HttpClientFactory like below.

 private readonly IHttpClientFactory _httpClientFactory;

public RecipeService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

public async Task<List<Core.Dtos.Recipes>> GetRecipeBasedOnIngredientsAsync(string recipeEndpoint)
{
var client = _httpClientFactory.CreateClient("recipeService");

using (var response = await client.GetAsync(client.BaseAddress + recipeEndpoint))
{
response.EnsureSuccessStatusCode();

var responseRecipies = await response.Content.ReadAsStringAsync();
var recipeObj = ConvertResponseToObjectList<Core.Dtos.Recipes>(responseRecipies);

return recipeObj ?? null;
}
}

What is base address in httpclient.baseaddress?

httpclient.baseaddress is used as the starting point to send your http requests.

Example

If you have to send many requests starting with the same address

https://stackoverflow.com/hello/moreinfo1/1
https://stackoverflow.com/hello/moreinfo2/2

So you have to set

clt.baseaddress = New URI("https://stackoverflow.com/hello/")
Dim response1 As HttpResponseMessage = Await clt.GetAsync("moreinfo1/1")
Dim response2 As HttpResponseMessage = Await clt.GetAsync("moreinfo2/2")

Else (if not set baseaddress property) you have to write the full URI every time you are sending a request

Dim response1 As HttpResponseMessage = Await clt.GetAsync("https://stackoverflow.com/hello/moreinfo1/1")
Dim response2 As HttpResponseMessage = Await clt.GetAsync("https://stackoverflow.com/hello/moreinfo2/2")

Useful Links

  • Why is HttpClient BaseAddress not working?
  • HttpClient with BaseAddress
  • https://www.dotnetperls.com/httpclient-vbnet

HttpClient with BaseAddress

In the BaseAddress, just include the final slash: https://localhost:44302/AndonService.svc/. If you don't, the final part of the path is discarded, because it's not considered to be a "directory".

This sample code illustrates the difference:

// No final slash
var baseUri = new Uri("https://localhost:44302/AndonService.svc");
var uri = new Uri(baseUri, "Layouts/1100-00277");
Console.WriteLine(uri);
// Prints "https://localhost:44302/Layouts/1100-00277"


// With final slash
var baseUri = new Uri("https://localhost:44302/AndonService.svc/");
var uri = new Uri(baseUri, "Layouts/1100-00277");
Console.WriteLine(uri);
// Prints "https://localhost:44302/AndonService.svc/Layouts/1100-00277"

What is the purpose of HttpClient.BaseAddress and why can't I change it after the first request

For (1), a common use case would be a client which interacts with exactly one server. Maybe it's the back-end API that this client was built to use. The exact details will be stored in a config file that the client reads during startup.

We could litter our code with direct accesses to the config, or inject the string read from config into every place that needs to construct a full URL. Or we could just configure the BaseAddress of the HttpClient we're putting into our Dependency Injection container and just let the consuming locations have that object injected. That for me is a somewhat expected use case.

For (2), I don't think there's a technical limitation. I think this is more there to save people from themselves. Since setting a BaseAddress and causing an actual request to go out via e.g. GetAsync are separate actions, it would be unsafe for two separate pieces of code to be doing such a thing at the same time - you could easily get races. So it's easier to reason about multi-threaded programs that may be sharing a single instance of HttpClient if such races aren't allowed in the first place.

IHttpClientFactory and changing the base HttpClient.BaseAddress

there are times, during runtime that the BaseAddress needs to change. During the run I am not able to change the BaseAddress after it has been injected.

BaseAddress can be changed up until the first request is sent. At that point, it is locked down and cannot be changed again. The HttpClient factory pattern assumes that each injected client has only one BaseAddress (which may be unset).

Now I could ignore BaseAddress altogether and just send the entire address in the API call, however, I do not know if this is the correct way of doing it.

Your options are:

  1. Define multiple clients, one for each BaseAddress. This is the normal approach if you have a few well-known hosts.
  2. Define a single client and do not use BaseAddress, passing the entire url in each call. This is perfectly permissible.
  3. Define your own factory type that uses IHttpClientFactory to pass out HttpClient instances where each instance can specify its own BaseAddress. I would only use this more complex approach if you had some code that required a BaseAddress (e.g., Refit) but needed to use it with a dynamic host.

Changing the Base Address of a HttpClient

As it stands you can't change the base address.

How do we correctly proceed from here?

Do not set the base address and just use the full addresses for the requests.

That way the same client can be used for all requests other wise you would need to create a new client for each base address which will also defeat the advantages of having the single client.

The client factory in asp.net core 2+ has since fixed the problems associated with having multiple clients.

Disposing of the client is not mandatory, but doing so will cancel any ongoing requests and ensure the given instance of HttpClient cannot be used after Dispose is called. The factory takes care of tracking and disposing of the important resources that instances of HttpClient use, which means that HttpClient instances can be generally be treated as .NET objects that don’t require disposing.

One effect of this is that some common patterns that people use today to handle HttpClient instances, such as keeping a single HttpClient instance alive for a long time, are no longer required. Documentation about what exactly the factory does and what patterns it resolves will be available, but hasn’t been completed yet.

Completed documentation Use HttpClientFactory to implement resilient HTTP requests



Related Topics



Leave a reply



Submit