Calling Asynchronous API in ASP.Net Application

2019-03-04 10:11发布

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?

3条回答
倾城 Initia
2楼-- · 2019-03-04 10:43

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.

查看更多
混吃等死
3楼-- · 2019-03-04 10:45

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

查看更多
叛逆
4楼-- · 2019-03-04 11:01

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

查看更多
登录 后发表回答