If you pass a string to the Uri or Uri builder constructor which contains Html encoded spaces, both strip the %20 encoded spaces and replace it with actual space.
If you pass the following string: https://restcountries.eu/rest/v2/name/United%20States%20of%20America to the Uri or Uri builder object you will get: https://restcountries.eu/rest/v2/name/United States of America which is an invalid Uri.
/// <summary>
/// Creates a new <see cref="Uri"/> based on the result of adding a path to the original <paramref name="uri"/> and its query string if one exists.
/// </summary>
/// <param name="uri">The <see cref="Uri"/> to which path is being added.</param>
/// <param name="path">The path being added to the <see cref="Uri"/>.</param>
/// <returns>A <see cref="Uri"/>.</returns>
/// <exception cref="NullReferenceException">In the case the <paramref name="uri"/> is null</exception>
public static Uri Combine(this Uri uri, string path) {
if (uri == null) {
throw new ArgumentNullException(nameof(uri));
}
var uriBuilder = new UriBuilder(uri);
if (string.IsNullOrEmpty(path) || string.IsNullOrWhiteSpace(path)) {
return uriBuilder.Uri;
}
var queryString = uriBuilder.Query;
var basePath = uriBuilder.Uri.AbsoluteUri.Trim().TrimEnd('/');
if (!string.IsNullOrEmpty(queryString)) {
var queryStringIndex = basePath.IndexOf(queryString, StringComparison.Ordinal);
if (queryStringIndex != -1) {
basePath = basePath.Remove(queryStringIndex);
}
}
var result = $"{basePath}/{path.Trim().TrimStart('/').TrimEnd('/')}";
uriBuilder = new UriBuilder($"{result}{queryString}");
//this is where I notice the the removal of the spaces.
return uriBuilder.Uri;
}
Note that the "United States of America" (the name parameter passed to the extension method above) has been Url encoded and the spaces are replaced with %20 across the board. However, the resulting url on the last line bellow ends up with the un-encoded spaces.
//the base url in this case is "https://restcountries.eu/rest/v2/" and the name parameter is "United States of America"
var url = new Uri(this.config.BaseUrl);
url = url.Combine("name");
url = url.Combine(Uri.EscapeDataString(name));
When the url variable above is passed to an HttpClient.BaseUrl and the client attempts to send a get request to the endpoint I get an error. I can't tell if the issue is with the Uri object or the HttpClient object not using the "absolute uri". In Either cases it's broken.
Microsoft.NETCoreApp 3.10
Microsoft.Extensions.Configuration.Abstractions 3.1.7
Microsoft.Extensions.Http 3.17
Microsoft.Extensions.Logging.Abstractions 3.1.7
Visual Studio Professional 16.6.5
Windows 10 Pro, build 18362
.NET Core Version: 3.1.302
Please FIX!
Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.
@bleepzter I am not sure which part of the code above is expected to demonstrate a problem. Is the method Combine above code that you wrote?
Can you please create minimal repro?
I tried this code, which behaves as expected on both .NET Framework 4.7.2 and .NET Core 3.1:
c#
HttpClient c = new HttpClient();
Console.WriteLine(c.GetStringAsync(new Uri("https://restcountries.eu/rest/v2/name/United%20States%20of%20America")).Result);
Are you looking at the output of Uri.ToString()?
That one will convert %20 to spaces as it's meant to create a user-readable representation.
```c#
var uri = new Uri("http://foo/a%20b");
Console.WriteLine(uri.ToString());
Console.WriteLine(uri.AbsoluteUri);
```text
http://foo/a b
http://foo/a%20b
@bleepzter I am not sure which part of the code above is expected to demonstrate a problem. Is the method
Combineabove code that you wrote?Can you please create minimal repro?
I tried this code, which behaves as expected on both .NET Framework 4.7.2 and .NET Core 3.1:
HttpClient c = new HttpClient(); Console.WriteLine(c.GetStringAsync(new Uri("https://restcountries.eu/rest/v2/name/United%20States%20of%20America")).Result);
You can't use the new HttpClient() constructor. Use the IHttpClientFactory instead.
Sorry, I found the issue with the code. I appreciate the help!