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:
- Define multiple clients, one for each
BaseAddress
. This is the normal approach if you have a few well-known hosts. - Define a single client and do not use
BaseAddress
, passing the entire url in each call. This is perfectly permissible. - Define your own factory type that uses
IHttpClientFactory
to pass outHttpClient
instances where each instance can specify its ownBaseAddress
. I would only use this more complex approach if you had some code that required aBaseAddress
(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
Detect If Running as Administrator with or Without Elevated Privileges
How to Know If a Process Is Running
Inner Join of Datatables in C#
Free File Locked by New Bitmap(Filepath)
Difference Between Console.Read() and Console.Readline()
How to Write a Backslash (\) in a String
Cross-Thread Operation Not Valid
How to Clean SQLdependency from SQL Server Memory
Deserializing JSON into an Object
What Does a Lock Statement Do Under the Hood
The Type Is Defined in an Assembly That Is Not Referenced, How to Find the Cause
Unauthorizedaccessexception Cannot Resolve Directory.Getfiles Failure
Execute Task in Background in Wpf Application
What Is the Maximum Size That an Array Can Hold