1

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?

9
  • As far as I am aware you don't need to pass the base address in again, if you just do await client.GetAsync("/catalog/products?" + additionalQuery) that should work (since youve already set base address) Commented Jun 29, 2017 at 15:01
  • So it's hanging on that line still WaitingActivation? Commented Jun 29, 2017 at 15:03
  • @maccettura You are correct in most cases, however in this instance the base address is http://website.com/store/v3/ so if I just call client.GetAsync("/catalog/products?" + additionalQuery' it changes the request to http://website.com/catalog/products which is missing the store/v3 section so any request returns a 404. Commented Jun 29, 2017 at 15:07
  • @Fran It does't hang per se, the debugging just stops without error and the rest of the getProducts() function does not get called. The WebMethod does also not return any data. Commented Jun 29, 2017 at 15:09
  • 1
    try adding .ConfigureAwait(false) to all your GetAsync statements you are awaiting. If that works I'll write up a longer answer with links and an explanation. Commented Jun 29, 2017 at 15:11

4 Answers 4

2

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

Sign up to request clarification or add additional context in comments.

2 Comments

That worked perfectly, thank you! I also had to add .ConfigAwait(false) to the api.getProducts() call inside the WebMethod but that was pretty trivial.
My goodness, those sources were over 4 years old when the question was answered. Stack overflow could use a feature that marks answers as outdated at this point. This information is almost a decade old at this point and the language features and libraries around async and threading has evolved tremendously.
0

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.

5 Comments

of course it would. It's a synchronous call. Once this works then OP can start looking into making it async. But I am not sure if it is supported with [WebMethod] services.
[WebMethod] is needed so that I can call this function from a JQuery Ajax call. I tested your solution though and the program still 'stops' on the line HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); so the return api.getProducts().Result; is never called.
@ConorWatson you know you literally just said that it actually was called. It looks like you have some deadlocks. Add .ConfigureAwait(false) as I said in the answer. Also consider using modern Web API services instead of legacy that you use now.
@Andrei What Modern Web API Services would you suggest?
@ConorWatson It's called ASP.NET Web API. Here is a link for you: asp.net/web-api
0

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

3 Comments

thanks for this answer. This now passes the HttpResponseMessage response = await client.GetAsync(URL + "?" + additionalQuery); line without issue but then stops again on the ReadAsAsync<Products>() call. Any ideas?
Can you provide more details about the error/exception/stacktrace. Also can you accept the answer since it solved the original issue that you posted so other users might get a clue how to handle this.
p = await response.Content.ReadAsAsync<Products>().Result;
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.