Calling and getting results from asynchronous methods in encapsulated class

Thomas Lu 1 Reputation point
2021-12-24T15:05:57.27+00:00

I am developing a GUI application that interacts with a control system's REST-based API via an HttpClient, but in trying to encapsulate the HttpClient and its related functions (GET/POST/PATCH) in a separate class, I'm finding that the GUI form cannot get any of the values or results that were supposed to be set or returned from the separate class's asynchronous methods.

If I instantiate the HttpClient in the GUI form's class and place its related functions in there too, the form is able to get the values, but this results in redundant code in every form needing to make calls to the API, not to mention wrecking any semblance of MVC/MVVM.

Is there any way to get the result from these asynchronous methods that were encapsulated in another class?

My partial code is as follows:

Encapsulated HttpClient Class

public class ControlClient
{
     public static HttpClient Client = new HttpClient()
     {
          BaseAddress = new Uri($"{ipAddress}:{port}/api/v1/")
     };

     private string sessionID = "";

     public string SessionID {
          get { return sessionID; }
          set { sessionID = value; }
     }

     public ControlClient()
     {
          RunAsync().ConfigureAwait(false);
     }

     public async Task RunAsync()
     {
          Client.DefaultRequestHeaders.Accept.Clear();
          Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

          var loginBody = new StringContent(LOGIN_BODY, Encoding.UTF8, "application/json");

          using (HttpResponseMessage loginResponse = await Client.PostAsync("authentication/login", loginBody))
          {
               var loginJSon = await loginResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
               var loginJObject = JObject.Parse(loginJSon).ConfigureAwait(false);
               SessionID = loginJObject.GetValue("UserID").ToString();
          } 
     }

     public async Task<string> Get(string requestUri, Dictionary<string, string> headerValues)
     {
          using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri))
          {
               headerValues.ToList().ForEach(x => requestMessage.Headers.Add(x.Key, x.Value));
               var responseMessage = await Client.SendAsync(requestMessage).ConfigureAwait(false);
               return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
          }
     }
}

GUI Form Class

public partial class Form1: Form
{
     private ControlClient client = new ControlClient();

     public Form1()
     {
          InitializeComponent();
     }

     private void Form1_Load(object sender, EventArgs e)
     {
          MessageBox.Show(client.SessionID);
     }

     private void button1_Click(object sender, EventArgs e)
     {
          var users = JsonConvert.DeserializeObject<List<UserDto>>(client.Get("config/user?includedProperties=All", headers).Result);
     }
}
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,320 questions
ASP.NET API
ASP.NET API
ASP.NET: A set of technologies in the .NET Framework for building web applications and XML web services.API: A software intermediary that allows two applications to interact with each other.
303 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 57,241 Reputation points
    2021-12-24T16:54:15.47+00:00

    You have a couple issues with your code.

    First you start an async operation, to update class properties, but have no synchronization before you use them. Starting an async operation in a constructor is poor design. Add a method that returns a task. Then in form load you can call the method and use a completion routine to show the value.

    To fix remove RunAsync from the constructor. Then change form load to

    private void Form1_Load(object sender, EventArgs e)
    {
           client.RunAsync().ContinueWith(() => {
                   MessageBox.Show(client.SessionID);
           });
    }
    

    Second you created a httpclient, but don’t depose. But actually you should just use one static httpclient for the crest api you are calling.


  2. Bruce (SqlWork.com) 57,241 Reputation points
    2021-12-26T17:17:47.45+00:00

    That means your async operation creates a new thread rather than running on the current thread. This then requires a using a semaphore or similar to return to the calling thread. That is, the calling thread awaits a semaphore, and the async thread sets it on completion
    .

    0 comments No comments