Click here to Skip to main content
16,015,218 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Some time ago, I wrote a C# console app to call an API using HttpClient, and it works fine. It sends an XML request and deconstructs an XML reply. Now I need to call the same API from a webform browser app (.NET 4.7.2), so when the user clicks the button, the call is made, and when the answer is received, this is posted back to the page. Seems like a simple requirement, but using the working code from the console app fails here, perhaps thanks to asynchronous processing? What happens is the code gets to call the ApiClient.PostAsync(), then immediately returns out of the block, not running any subsequent code and, of course, not returning any value to the page.

What I have tried:

On the aspx page, I simply have a button handler like this:
C#
protected void UltraSimpleButton_Click(object sender, EventArgs e)
{
    // Fetch certificate credentials...

    // Format XML for call...

    // Initialize HttpClient
    MyApp.App_Code.API_Handler.InitializeClient(CertificateUserName, CertificatePassword);

    var Reply = MakeRequest(XMLString);

    // Do something with the reply object...

}

The InitializeClient looks like this...
C#
public static void InitializeClient(string CertificateUserName, string CertificatePassword)
    {
        //-------------------------------------------
        // Set up client handler, with certificates.
        //-------------------------------------------
        System.Net.Http.HttpClientHandler MyHandler = 
                   new System.Net.Http.HttpClientHandler();

        //---------------------
        // Set up http client.
        //---------------------
        ApiClient = new System.Net.Http.HttpClient(MyHandler);
        ApiClient.BaseAddress = new Uri("https://test.someurl.com/services");
        ApiClient.DefaultRequestHeaders.Accept.Clear();
        ApiClient.DefaultRequestHeaders.Accept.Add
        (new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("text/xml"));
        }

MakeRequest() looks like this...
C#
private async System.Threading.Tasks.Task<string> MakeRequest(string XMLString)
{
    string ResponseString = "";
    try
    {
        MyApp.App_Code.API_Communicator API_Class = 
                       new MyApp.App_Code.API_Communicator();

        ResponseString = await API_Class.SendRequest(XMLString);

        // Handle whatever value comes back from API.
    }
    catch (Exception ex)
    {
        return "Error " + ex.Message;
    }

    return ResponseString;
}

And the API_Class.SendRequest looks like this...
C#
public async Task<string> SendRequest(string XMLString)
{
    try
    {
        var CallContent = new StringContent(XMLString, Encoding.UTF8, "text/xml");

	// This line is run, and successfully calls the API.
    HttpResponseMessage ResponseReceived = 
                await API_Handler.ApiClient.PostAsync("", CallContent);

	// This line never gets run!
    if (ResponseReceived.IsSuccessStatusCode)
    {
	    // Process response from API and return value.
	}
	else
	{
	    // Handle API error.
	}
    }
    catch (Exception ex)
    {
        throw new Exception("An error was detected while sending card details. 
                             Error was '" + ex.Message + "'");
    }
}

The PostAsync line is run, but control then immediately returns to the calling proc, and never to the next line. I know the call to the API is successful, but what I can't get it to do is wait until a reply is received, nor to continue to the next bit of code (so I can postback the reply). I have tried putting this in MakeRequest()...
C#
var task = API_Class.SendRequest(XMLString);
task.Wait();

...but still, it does not wait! All the internet says is using APIs should be asynchronous, but that makes no sense in this situation, so how do I get it to wait for a reply?

Thanks in advance!
Posted
Updated 30-Mar-22 3:58am
v2

Quote:
All the internet says is using APIs should be asynchronous, but that makes no sense in this situation
It makes perfect sense. You're making a network request to an external resource, which could take hundreds, thousands, or even millions of milliseconds to complete. A synchronous call would have the current thread sat in a tight loop asking "is it ready yet?" over and over again until the call completes. An asynchronous call lets the current thread get on with servicing other requests, and grabs an available thread to finish the current request once the network call has completed.

Using .Wait on a Task can and will lead to deadlocks in your code, and should be avoided at all costs.

You need to mark your page as Async, and use Page.RegisterAsyncTask to call a Task-returning async method which makes the request:
C#
protected void UltraSimpleButton_Click(object sender, EventArgs e)
{
    Page.RegisterAsyncTask(new PageAsyncTask(SendRequestAsync));
}

private async Task SendRequestAsync()
{
    // Fetch certificate credentials...
    // Format XML for call...
    // Initialize HttpClient
    MyApp.App_Code.API_Handler.InitializeClient(CertificateUserName, CertificatePassword);
    
    var reply = await MakeRequest(XMLString);

    // Do something with the reply object...
}
NB: Your static InitializeClient method is almost certainly another mistake. Web applications can serve multiple simultaneous requests from different users, and any static state will be shared between all requests from all users. Unless you've written that method very carefully, a request from one user is going to overwrite the state of another user's request.

Using Asynchronous Methods in ASP.NET 4.5 | Microsoft Docs[^]
 
Share this answer
 
v2
Hi Richard

Thanks. This now works, and thanks for highlighting the issue with static too. I will change that.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900