Search code examples
c#async-awaithttpclientvisual-studio-2022httpresponsemessage

HttpClient for calling REST API service is not working


This is the obsolete function - call the function with :

string requestData = RestClient.JiraRequest(buildFilter());

Function:

public static string JiraRequest(string api)
{
    string data = string.Empty;
    
    try
    {
        string requestUrl = @"https://jira.xxxxx.org/rest/api/2/" + api;
        string authType = "Basic";
        string httpMethod = "GET";

        string authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(Config.UserName + ":" + Config.UserPassword));
        _ = Encoding.UTF8.GetBytes(authHeader);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
        request.Method = httpMethod;
        request.Headers.Add("Authorization", authType + " " + authHeader);
        request.ContentType = "application/json";

        HttpWebResponse response = (HttpWebResponse)request.GetResponse();

        if (response.StatusCode != HttpStatusCode.OK)
        {
            throw new ApplicationException("Exeption error: " + response.StatusCode.ToString());
        }

        using (Stream responseStream = response.GetResponseStream())
        {
            if (responseStream != null)
            {
                using (StreamReader reader = new StreamReader(responseStream))
                {
                    data = reader.ReadToEnd();
                }
            }
        }
    }
    catch (Exception ex)
    {
        HandleExeption.Exception(ex);
        return data;
    }

    return data;
}

This is the new function - call the function with :

string requestData = await RestClient.JiraRequest(buildFilter());

Function:

public static async Task<string> JiraRequest(string api)
{
    string data = string.Empty;

    try
    {
        string requestUrl = @"https://jira.xxxxx.org/rest/api/2/" + api;
        string authType = "Basic";

        using (HttpClient client = new())
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authType, Convert.ToBase64String(Encoding.ASCII.GetBytes(Config.UserName + ":" + Config.UserPassword)));

            HttpResponseMessage responseMessage = await client.GetAsync(requestUrl);

            if (!responseMessage.IsSuccessStatusCode)
            {
                throw new ApplicationException("HttClient exception: " + responseMessage.StatusCode.ToString());
            }

            data = await responseMessage.Content.ReadAsStringAsync();
            return data;
        }
    }
    catch (Exception ex)
    {
        HandleExeption.Exception(ex);
        return data;
    }
}

Description and history: with the old code, I got two messages that this approach is obsolete, so I coded the new kind of HttpClient.

With the obsolete function, it works perfectly, but with the new function, it doesn't work anymore..

Error in the new function: it crashes after calling

HttpResponseMessage responseMessage = await client.GetAsync(requestUrl);

No exception is shown, I have also tried with Debug.Writeline, but nothing shows up either.

After calling the this, the program simply exits (crashes)....

Exception(Exception ex):

public static void Exception(Exception ex)
        {
            StackTrace stackTrace = new(ex, true);

#nullable enable
            StackFrame? stackFrame = stackTrace.GetFrame(stackTrace.FrameCount - 1);
#nullable disable

            Debug.WriteLine($"Exception message: {ex.Message ?? "No message"}");

            if (stackFrame != null)
            {
                var method = stackFrame.GetMethod();
                Debug.WriteLine($"Exception in method: {method}");

                var lineNumber = stackFrame.GetFileLineNumber();
                Debug.WriteLine($"Exception in line: {lineNumber}");
            }
        }

Solution

  • I doubt your program is "crashing" at that particular line of code, because you have exception handling there, which would catch any error thrown in that method.

    The only notable difference between the old and the new variant, is that the new one is asynchronous. And somewhere in the callstack that leads to this particular method to be called, you probably don't properly await the result of the asynchronous method. Ie you have something like this

    public static void Main(string[] args) {
      ...
      methodA();
    }
    
    void methodA() {
      ...
      methodB();
    }
    
    
    async Task methodB() {
      ...
      await methodC();
    }
    
    async Task<...> methodC() {
      try {
        HttpResponseMessage responseMessage = await client.GetAsync(requestUrl);
        ...
    
        return ...
      } catch (Exception ex) {
        ...
      }
    }
    

    So when you now call methodA() from a synchronous context, it calls methodB() without awaiting its result. That's perfectly valid (eventhough it will generate a warning in Visual Studio), but at this point you lose your asynchronous context. This means, that methodA() will finish before methodB() and this will bubble up the callstack to the Main() method, which will also reach its end before methodB() is finished.

    And when the Main method ends, the program terminates. Regardless of whether there are still asynchronous operations pending or not.

    This probably happend when you refactored JiraRequest(string api) from being synchronous to asynchonous.

    You can try to attach a debugger to your program and setting breakpoints

    1. directly before await client.GetAsync(requestUrl)
    2. directly after await client.GetAsync(requestUrl)
    3. at the very last statement of your Main method

    I assume your code will hit breakpoints 1 and 3, but not 2. If that's the case, your program isn't crashing but terminating normally, because the callstack is empty.

    To resolve that issue, make sure

    • all methods in that callstack are async
    • all async methods are properly awaited