Calling Asynchronous API in ASP.Net Application

I’m a little new to ASP.Net and Asynchronous coding so bear with me. I have written an asynchronous wrapper in C# for a web API that I would like to use in a ASP.Net application.

Here is one of the functions in the C# API wrapper:

public async Task<string> getProducts()
{
    Products products = new Products();
    products.data = new List<Item>();

    string URL = client.BaseAddress + "/catalog/products";
    string additionalQuery = "include=images";
    HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery);
    if (response.IsSuccessStatusCode)
    {
        Products p = await response.Content.ReadAsAsync<Products>();
        products.data.AddRange(p.data);

        while (response.IsSuccessStatusCode && p.meta.pagination.links.next != null)
        {
            response = await client.GetAsync(URL + p.meta.pagination.links.next + "&" + additionalQuery);
            if (response.IsSuccessStatusCode)
            {
                p = await response.Content.ReadAsAsync<Products>();
                products.data.AddRange(p.data);
            }
        }
    }
    return JsonConvert.SerializeObject(products, Formatting.Indented);
}

I then have a WebMethod in my ASP.Net application (which will be called using Ajax from a Javascript file) which should call the getProducts() function.

[WebMethod]
public static string GetProducts()
{
    BigCommerceAPI api = getAPI();
    return await api.getProducts();
}

Now of course this will not work as the WebMethod is not an async method. I have tried to change it to an async method which looked like:

[WebMethod]
public static async Task<string> GetProducts()
{
    BigCommerceAPI api = getAPI();
    return await api.getProducts();
}

This code does run, but as soon as it gets to the HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); line in the getProducts() function the debugger will stop without any errors or data being returned.

What am I missing? How can I get call this asynchronous API from my ASP application?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

So I actually resolved an issue very similar to this last night. It’s odd because the call worked in .net 4.5. But we moved to 4.5.2 and the method started deadlocking.

I found these enlightening articles (here, here, and here) on async and asp.net.

So I modified my code to this

    public async Task<Member> GetMemberByOrganizationId(string organizationId)
    {
        var task =
            await
                // ReSharper disable once UseStringInterpolation
                _httpClient.GetAsync(string.Format("mdm/rest/api/members/member?accountId={0}", organizationId)).ConfigureAwait(false);

        task.EnsureSuccessStatusCode();

        var payload = task.Content.ReadAsStringAsync();

        return JsonConvert.DeserializeObject<Member>(await payload.ConfigureAwait(false),
            new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
    }

which resolved my deadlocking issue.

So TLDR: from the Stephen Cleary article

In the overview, I mentioned that when you await a built-in awaitable,
then the awaitable will capture the current “context” and later apply
it to the remainder of the async method. What exactly is that
“context”?

Simple answer:

If you’re on a UI thread, then it’s a UI context. If you’re responding
to an ASP.NET request, then it’s an ASP.NET request context.
Otherwise, it’s usually a thread pool context. Complex answer:

If SynchronizationContext.Current is not null, then it’s the current
SynchronizationContext. (UI and ASP.NET request contexts are
SynchronizationContext contexts). Otherwise, it’s the current
TaskScheduler (TaskScheduler.Default is the thread pool context).

and the solution

In this case, you want to tell the awaiter to not capture the current
context by calling ConfigureAwait and passing false

Method 2

I am not sure what is [WebMethod] in ASP.NET. I remember it used to be SOAP web services but no one does it anymore as we have Web API with controllers where you can use async/await in action methods.

One way to test your code would be to execute async method synchronously using .Result:

[WebMethod]
public static string GetProducts()
{
    BigCommerceAPI api = getAPI();
    return api.getProducts().Result;
}

As maccettura pointed out in the comment, it’s a synchronous call and it locks the thread. To make sure you don’t have dead locks, follow Fran’s advice and add .ConfigureAwait(false) at the end of each async call in getProducts() method.

Method 3

First by convention GetProducts() should be named GetProductsAsync().

Second, async does not magically allocate a new thread for it’s method invocation. async-await is mainly about taking advantage of naturally asynchronous APIs, such as a network call to a database or a remote web-service.
When you use Task.Run, you explicitly use a thread-pool thread to execute your delegate.

[WebMethod]
public static string GetProductsAsync()
{
    BigCommerceAPI api = getAPI();
    return Task.Run(() => api.getProductsAsync().Result);
}

Check this link It’s a project sample about how to implement Asynchronous web services call in ASP.NET

Method 4

I had a very similar issue:

  1. Main webapp is a ASP.NET 4.5 Web forms, but many of its functions implemented as AJAX calls from UI to a [webMethod] decorated function in the aspx.cs code-behind:
  2. The webmethod makes an async call to a proxy. This call was
    originally implemented with Task.Run() and I tried to rewrite with
    just await …
[WebMethod]
public static async Task<OperationResponse<CandidatesContainer>> GetCandidates(string currentRoleName, string customerNameFilter, string countryFilter, string currentQuarter)
{
            string htmlResult = String.Empty;
            List<CandidateEntryDTO> entries = new List<CandidateEntryDTO>();
            try
            {
                entries = await GetCandiatesFromProxy(currentUser, currentRoleName, customerNameFilter, countryFilter, currentQuarter)
                    .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                log.Error("Error .....", ex);
            }
            CandidatesContainer payloadContainer = new CandidatesContainer { 
               CountryMappedCandiates = ..., 
               GridsHtml = htmlResult };
            return new OperationResponse<CandidatesContainer>(payloadContainer, true);
}

3) The call GetCandiatesFromProxy(…) is the top of a chain of several async methods and at the bottom there’s finally a HttpClient.GetAsync(…) call:

private async Task<B2PSResponse<string>> GetResponseFromB2PService(string serviceURI)
{
            string jsonResultString = String.Empty;

            if (_httpClientHandler == null)
            {
                _httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
            }
            if (_client == null)
            {
                _client = new HttpClient(_httpClientHandler);
            }
            HttpResponseMessage response = await _client.GetAsync(serviceURI).ConfigureAwait(false);
            HttpContent content = response.Content;
            string json = String.Empty;
            if (response.StatusCode == HttpStatusCode.OK)
            {
                json = await content.ReadAsStringAsync().ConfigureAwait(false);
            }
            B2PSResponse<string> b2psResponse = new B2PSResponse<string>(response.StatusCode, response.ReasonPhrase, json);
            return b2psResponse;
}
  1. The code was not working (was stuck on the lowest level await) until
    I started to add .ConfigureAwait(false) to each await call.
  2. Interesting, that I had to add these .ConfigureAwait(false) to all await calls on the chain – all the way to the top call in the webMethod. Removing any of them would break the code – it would hang after the await that does not have the .ConfigureAwait(false).
  3. The last point: I had to modify the Ajax call’s SUCCESS path. The default Jason serialization for webmethods makes the result sent to AJAX call as
{data.d.MyObject}
  • i.e. inserts the {d} field containing the actual payload. After the webmethod return value was changed from MyObject to Task – this no longer worked – my payload was not found in the {data.d}. The result now contains
{data.d.Result.MyObject}

This is simply the result of serializing the Task object – which has the .Result field.
With one small change to the AJAX call is now working.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x