Reputation: 79581
Consider the following code, where the BaseAddress
defines a partial URI path.
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");
}
I expect this to perform a GET
request to http://something.com/api/resource/7
. But it doesn't.
After some searching, I find this question and answer: HttpClient with BaseAddress. The suggestion is to place /
on the end of the BaseAddress
.
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");
}
It still doesn't work. Here's the documentation: HttpClient.BaseAddress What's going on here?
Upvotes: 582
Views: 232763
Reputation: 79581
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");
}
Upvotes: 1422
Reputation: 12757
Reference Resolution is described by RFC 3986 Uniform Resource Identifier (URI): Generic Syntax. And that is exactly how it supposed to work. To preserve base URI path you need to add slash at the end of the base URI and remove slash at the beginning of relative URI.
If base URI contains non-empty path, merge procedure discards its last part (after last /
). Relevant section:
5.2.3. Merge Paths
The pseudocode above refers to a "merge" routine for merging a relative-path reference with the path of the base URI. This is accomplished as follows:
If the base URI has a defined authority component and an empty path, then return a string consisting of "/" concatenated with the reference's path; otherwise
return a string consisting of the reference's path component appended to all but the last segment of the base URI's path (i.e., excluding any characters after the right-most "/" in the base URI path, or excluding the entire base URI path if it does not contain any "/" characters).
If relative URI starts with a slash, it is called an absolute-path relative URI. In this case the merge procedure ignores all base URI path. For more information check 5.2.2. Transform References section.
Upvotes: 155
Reputation: 18956
if you are using httpClient.SendAsync() there is no string overload for giving relative Uris like the overloads for Get or other verb-specific methods.
But you can create relative Uri with giving UriKind.Relative as second param
var httpRequestMessage = new HttpRequestMessage
{
Method = httpMethod,
RequestUri = new Uri(relativeRequestUri, UriKind.Relative),
Content = content
};
using var httpClient = HttpClientFactory.CreateClient("XClient");
var response = await httpClient.SendAsync(httpRequestMessage);
var responseText = await response.Content.ReadAsStringAsync();
Upvotes: 23
Reputation: 530
I also came upon this same issue with BaseAddress
. I decided to not use BaseAddress
at all and simplest solution would be a simple one-liner addition:
Uri GetUri(string path) => new Uri("http://something.com/api" + path);
Then your code would become:
Uri GetUri(string path) => new Uri("http://something.com/api" + path);
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
// Remove BaseAddress completely
// client.BaseAddress = new Uri("http://something.com/api");
var response = await client.GetAsync(GetUri("/resource/7"));
}
I have not investigated pros and cons of using BaseAddress
over this, but for me this works flawlessly. Hope this helps somebody.
Upvotes: 6
Reputation: 27
Ran into a issue with the HTTPClient, even with the suggestions still could not get it to authenticate. Turns out I needed a trailing '/' in my relative path.
i.e.
var result = await _client.GetStringAsync(_awxUrl + "api/v2/inventories/?name=" + inventoryName);
var result = await _client.PostAsJsonAsync(_awxUrl + "api/v2/job_templates/" + templateId+"/launch/" , new {
inventory = inventoryId
});
Upvotes: 1