15

I am having issues which seem to be related calling a specific API asynchronously using HttpClient - the strange thing is that it doesn't happen all the time and can be solved by refreshing the page (sometimes once, sometimes multiple times).

I thought this could be a local issue but hosting on Azure produces the same results.

Raw exception details:

System.Net.Sockets.SocketException (11001): No such host is known at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)

I have checked:

  • There are no limits imposed on the API
  • Passing the request url in a browser returns the expected JSON result
  • Refreshing the page sometimes resolves the issue

The start of the error: Error top

The rest: Error bottom

This is the method that seems to be causing the issue:

public async Task<List<MoonPhase.Phasedata>> GetPhaseDataAsync(double lat, double lng, int year)
{
    string requestUrl = "https://api.usno.navy.mil/moon/phase?year=" + year + "&coords=" + locationService.FormatCoordinates(lat, lng) + "&tz=0";

    using (var client = new HttpClient())
    {
        var content = await client.GetStringAsync(requestUrl);
        var moonPhaseObject = JsonConvert.DeserializeObject<MoonPhase.RootObject>(content, new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore
        });

        return moonPhaseObject.PhaseData;
    }
}
Ciarán Bruen
  • 5,221
  • 13
  • 59
  • 69
Beau
  • 427
  • 1
  • 6
  • 21
  • First of all, it is a bad practice to treat HttpClient as an IDisposable. – Timothy Jannace Jun 27 '19 at 14:58
  • 1
    @TimothyJannace Why is that? `HttpClient` implements `IDisposable`. – bolkay Jun 27 '19 at 15:01
  • Have you checked that the rendered URL is valid and that the machine the code is running on can both resolve and navigate to `api.usno.navy.mil`? – Jamie Taylor Jun 27 '19 at 15:11
  • 2
    This post might be helpful for explaining it. He has a good summary at the bottom explaining the life cycle of HttpClient: https://medium.com/@nuno.caneco/c-httpclient-should-not-be-disposed-or-should-it-45d2a8f568bc – Timothy Jannace Jun 27 '19 at 15:28

4 Answers4

9

I tested the API by attempting to access multiple times within 15 minutes (using this URI). For a minute or two it seemed to have DNS issues.

The GetStringAsync method throws an HttpRequestException exception if there are issues such as DNS failure (source). You could try catching this exception and implementing a retry mechanism if this exception is thrown.

zmike
  • 1,090
  • 10
  • 24
0

The problem was fixed when I made the api url 'https://www' -> 'https://'

Hasan Sahin
  • 31
  • 1
  • 2
  • This actually was a solution for me as a team member was using "https : //www. localhost" instead of "https : // localhost" – jharr100 Nov 23 '21 at 20:30
0

I'll chime in though I expect at this point you've solved this. HttpClient has a couple of known problems when it comes to how to instantiate and use it, especially with Dependency Injection in .Net Core and .Net 5+. One of these has to do with Sockets. I'll let you google that, but in general, an HttpClient should be created once and reused.

The second problem has to do with DNS, but again, I'll defer that to google.

Basically, there are 2 patterns you want to follow in your Startup/Program (one or the other). AddHttpClient is the one I use. HttpClientFactory is the other.

Here is a .Net 6 Console application I wrote in Fiddler:

using System;
using System.Net.Http;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;

public class Program
{
    public class Data
    {
        public string Name {get; set;}
        public string Address {get; set;}
    }
    
    public class PostResponse
    {
        public string Id {get; set;}
        public string Message {get; set;}
    }
    
    public interface IMyHttpClient
    {
        Task<PostResponse> PostAsync(Data data, CancellationToken cancellationToken);
    }
    
    public class MyHttpClient : IMyHttpClient
    {
        private readonly HttpClient _httpClient;

        public MyHttpClient(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
        
        public async Task<PostResponse> PostAsync(Data data, CancellationToken cancellationToken)
        {
            HttpResponseMessage response = null;
            
            // Newtonsoft here but feel free to use whatever serializer. I have generally moved to the System.Text.Json.
            var requestBody = JsonConvert.SerializeObject(data);
            HttpContent content = new StringContent(requestBody, Encoding.UTF8, "application/json");

            var request = new HttpRequestMessage()
            {
                RequestUri = new System.Uri("https://my.url.com"),
                Method = HttpMethod.Post,
                Content = content
            };
            
            response = await _httpClient.SendAsync(request, cancellationToken);
            
            //Check if they canceled before doing an expensive operation
            cancellationToken.ThrowIfCancellationRequested();

            if (!response.IsSuccessStatusCode)
            {
                // throw or log or whatever.
                throw new Exception($"{response.StatusCode.ToString()}:{response.ReasonPhrase}");
            }
            
            var responseContent = response.Content != null? await response.Content.ReadAsStringAsync(): null;
            var postResponse = JsonConvert.DeserializeObject<PostResponse>(responseContent);

            return postResponse;
        }
    }
    
    public static async Task Main()
    {
        var services = new ServiceCollection();
        
        // configure logging
        services.AddLogging(builder =>
        {
            builder.AddConsole();
        });

        // HERE IS YOUR PATTERN 1. Use AddHttpClient.
        services.AddHttpClient<IMyHttpClient, MyHttpClient>();
        
        var serviceProvider = services.BuildServiceProvider();

        var logger = serviceProvider.GetService<ILoggerFactory>()
            .CreateLogger<Program>();
        
        logger.LogDebug("Starting application");
        
        var data = new Data()
        { 
            Name = "Shirley",
            Address = "123 Main St, Anytown, Anystate, Anyzip"
        };

        var client = serviceProvider.GetService<IMyHttpClient>();
        var cancellationToken = new CancellationToken();
        
        try
        {
            var response = await client.PostAsync(data, cancellationToken);
            
            Console.WriteLine($"{response.Id}:{response.Message}");
        }
        catch(Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }

        logger.LogDebug("All done!");
    }
}
Matt
  • 1
  • 3
-1

What? these comments are not completely right, have you read the docs? If you are using Httpclient in net core, you should be using a HttpFactory or a named or typed client.

Using a factory for example

services.AddHttpClient();

And then

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

There are also named clients and typed clients. Try to use those instead your HttpClient directly, this also help with the DNS cache problems.

rekiem87
  • 1,565
  • 18
  • 33
  • 2
    Answers with partial code are the best... "Use a factory but I won't show you how to actually build the thing or anything..." – Ortund Nov 13 '20 at 11:26
  • There is the important part, what to add to startup and how to inject in the class, what is missing?, If you have doubts you can see the docs. – rekiem87 Nov 13 '20 at 16:15
  • 3
    how is this related to host not found, can someone please pitch in? – winterishere Jun 22 '21 at 11:52