Calling Asynchronous API in ASP.Net Application - c#

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?

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

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.

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

I had a very similar issue:
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:
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;
}
The code was not working (was stuck on the lowest level await) until
I started to add .ConfigureAwait(false) to each await call.
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).
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.

Related

ContinueWith doesn't work in Controller to log

I can't find a solution to the problem despite many similar questions.
There is a Web API. On POST I need
read DB
make a HTTP call to other service to subscribe on notification (let's say it takes 5s)
return the data from the DB
In the step 2, I don't need to wait, I don't need to block the client (for 5sec), so the client should not wait for the response.
However, the server have to wait on result from 2 and log it. So far I've tried
[HttpPost("{callId}")]
public async Task<IActionResult> CreateSubs([FromRoute] string callId)
{
var data = await ...// read the DB
_ = SubscribeForUpdates(callId);
return Ok(data);
}
private async Task SubscribeForUpdates(string callId)
{
_logger.LogInformation("Subscribe client {ConnectionId} notifications", callId);
var requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(_httpClient.BaseAddress, $"subscribe/{callId}"),
Method = HttpMethod.Get,
};
var result = await SendAsync<SubscriptionResponse>(requestMessage);
if (result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
}
SendAsync is from some library and so smth like _httpClient.SendAsync
In this case the request will not be blocked, the internal HTTP request is successful but I there is no Success from Console.WriteLine("Success");. Only if I put a breakpoint there it logs.
Could you please help me to understand why this is not log and how to fix that?
I've tried ContinueWith - no result
await SendAsync<ServerSubscriptionResponse>(requestMessage)
.ContinueWith(t =>
{
if (t.Result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
})
When I use await SubscribeForUpdates(callId) inasted of _ = SubscribeForUpdates(callId) it works and logs but the blocks a client. I need to avoid that

HttpClient PostAsync not working in CRM plugin

I am trying to send json to a web API using HttpClient.PostAsync. It works from a console application but not from my CRM plugin. Doing some research I noted that it is probably something to do with the context the plugin runs in and threading. Anyway here is my calling code:
public async void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target"))
{
if (context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "new_product")
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
if (entity.Contains("new_begindate") && entity.Contains("new_expirationdate"))
{
await OnlineSignUp(entity, service);
}
}
catch (InvalidPluginExecutionException)
{
throw;
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(OperationStatus.Failed, "Error signing up: " + e.Message);
}
}
}
}
}
And here is the relevant code for sending the json:
private async Task<HttpResponseMessage> OnlineSignUp(Entity license, IOrganizationService service)
{
...
var json = JsonConvert.Serialize(invitation);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "token=7d20f3f09ef24067ae64f4323bc95163");
Uri uri = new Uri("http://signup.com/api/v1/user_invitations");
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
int n = 1;
return response;
}
}
The exception is thrown with a message "Thread was being aborted". Can anyone tell me what I am doing wrong?
I would guess this is going to fail somewhat randomly based on use of async/await. I wouldn't think CRM really supports plugins returning control before they are complete. When it fails, it looks like the thread was in the process of being cleaned up behind the scenes.
CRM is already handling multi-threading of plugins and supports registering plugin steps as asynchronous if they are long running (or don't need to be run in the synchronous pipeline). It would make more sense to use a synchronous HTTP call here like:
var response = httpClient.PostAsync(uri, content).Result;
EDIT: To illustrate, this is an overly-trivialized example of what is most likely happening when CRM goes to kickoff your plugin and you're using async/await (from LinqPad).
static async void CrmInternalFunctionThatKicksOffPlugins()
{
var plugin = new YourPlugin();
//NOTE Crm is not going to "await" your plugin execute method here
plugin.Execute();
"CRM is Done".Dump();
}
public class YourPlugin
{
public async void Execute()
{
await OnlineSignUp();
}
private async Task<HttpResponseMessage> OnlineSignUp()
{
var httpClient = new HttpClient();
var r = await httpClient.PostAsync("http://www.example.com", null);
"My Async Finished".Dump();
return r;
}
}
Which will print:
CRM is Done My Async Finished
looks like you are using Json.NET, when you use external assemblies there are some things to take care of (merging) and not always the result works.
Try to serialize using DataContractJsonSerializer
example: http://www.crmanswers.net/2015/02/json-and-crm-sandbox-plugins.html

ASP.NET Web Api SendAsync keeps server waiting

I have seen other very similar questions, but I still haven't found a solution, I have the Postal nuget package installed to handle email, and I have a web method that sends email asynchronously (I suppose). Based on other examples, here is my code:
[ActionName("PostEnviarCorreoReserva")]
[HttpPost]
public async Task<IHttpActionResult> PostEnviarCorreoReserva(
[FromBody] ReservaEmail vermodel,
String ver_gkey)
{
var ReservaId = Convert.ToInt32(vermodel.Reserva);
CultureInfo es = new CultureInfo("es-ES");
Thread.CurrentThread.CurrentCulture = es;
DtContex = new DTPPublicDataContext();
var RSPD = DtContex.res_reservas_usuario_det.First(i => i.reserva_gkey == ReservaId);
dynamic emailReserva = new Email(TipoEmail);
emailReserva.To = RSPD.email_reserva;
emailReserva.CodReserva = RSPD.reserva_gkey.ToString();
...
await emailReserva.SendAsync();
return Ok();
}
So I'm still a newbie, but I understand that this code should execute asynchronously, so I can later perform other operations to the Web API, but until it return the Ok response, Web API is busy handling this threat, what exactly Im doing wrong? Sending email takes a really long time
If you don't want to wait, while sendout will be completed, execute code in new thread.
new Thread(async () =>
{
await emailReserva.SendAsync();
}).Start();

Mvc Application Async Methods Are Hanging

We have SOA for our solution. We are using .net framework 4.5.1, asp.net mvc 4.6, sql server, windows server and thinktecture identity server 3 ( for token based webapi calls. )
Solution structure looks like;
Our mvc frontend application talks with our webapi application via a httpClient wrapper. Here is the generic http client wrapper code;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace Cheetah.HttpClientWrapper
{
public class ResourceServerRestClient : IResourceServerRestClient
{
private readonly ITokenProvider _tokenProvider;
public ResourceServerRestClient(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public string BaseAddress { get; set; }
public Task<T> GetAsync<T>(string uri, string clientId)
{
return CheckAndInvokeAsync(async token =>
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
});
}
private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
{
if (!string.IsNullOrEmpty(resourceServiceClientName))
{
client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
}
if (string.IsNullOrEmpty(BaseAddress))
{
throw new Exception("BaseAddress is required!");
}
client.BaseAddress = new Uri(BaseAddress);
client.Timeout = new TimeSpan(0, 0, 0, 10);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
}
private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
catch (Exception ex)
{
if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized)
{
string token = await _tokenProvider.GetTokenAsync();
if (!string.IsNullOrEmpty(token))
{
return await method(token);
}
}
throw;
}
}
public void ThrowResourceServerException(List<string> messages)
{
string message = messages.Aggregate((p, q) => q + " - " + p);
var exception = new Exception(message);
exception.Data.Add("ServiceOperationException", message);
throw exception;
}
}
}
Also, sometimes this http client wrapper using with NitoAsync manager ( Call async methods as sync. ), and sometimes we are using this generic method directly with await - async task wait like;
var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id);
So here is our problem:
When we test our mvc application with jmeter (for making some-kind-of load test / 10 threads per 1 sec), after a couple of minutes, mvc application stops working [ exception is task canceled due to timeout ] ( maybe only 1-2 requests timeouts ) on this line: HttpResponseMessage response = await client.GetAsync(uri);. But after that request, all requests will be failed like they are in row. So mvc application is hanging for 2-15 minutes ( randomly ) but in that time I can send new requests from postman to webapi. They are ok, I mean webapi is responding well. After a couple of minutes mvc application turnback to normal.
Note: We have load-balancer for mvc-ui and webapi. Because sometimes we get 120K requests in a minute in a busy day. But it gives same error if there is no load balancer in front of webapi or mvc application. So it's not LB problem.
Note2: We tried to use RestSharp for mvc-ui and webapi communication. We got same error here. When a reuqest is failing, all requests will be failed in a row. It looks like it's a network error but we can't find a proof for it.
Can you see any error on my httpClient wrapper ? or better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
Update1: We moved projects .net 4.5.1 to 4.6.1. Same deadlock happened again. And than we temporary moved all source codes of the layer: "Business & Repository" as dll level. There is no webapi between business & presentation level now. Dead lock solved. We are still searching why httpClientWrapper codes are not working properly when we called webapi methods from our webapplication controllers.
better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?
A best practice here is for the client (browser in your case) to directly retrieve data from the Web API Controllers and for the MVC controllers to only serve pure HTML views which include layout, styles (css), visual structure, scripts (ie. javascript) etc and not the data.
Image credit: Ode to Code. Incidentally the author on that site also does not recommend your approach although it is listed as an option.
This servers as a good SOC between your views and your data allowing you more easily to make changes to either part.
It allows for the client (browser) to retrieve data asynchronously which creates for a better user experience.
By not doing this and adding a network request step in the call stack you have created an unnecessary expensive step in the flow of data (call from MVC Controller(s) to Web API deployment). The more boundaries are crossed during executing the slower the execution.
The fast solution, as you have already figured out, is to call your business code library directly from your MVC project. This will avoid the additional and unnecessary network step. There is nothing wrong with doing this and many more traditional sites serve both the view (html) and data in the same call. It makes for a more tightly coupled design but it is better than what you had.
The best long term solution is to change the MVC views so they call your Web API deployment directly. This can be done using frameworks like Angular, React, Backbone, etc. If the Web API method calls are limited and are not expected to grow you can also use JQuery or pure javascript BUT I would not try to build a complex application on this, there is a reason why frameworks like Angular have become so popular.
As to the actual underlying technical problem in this case we can't be sure without a memory dump to see what resources are causing the deadlock. It might be something as simple as making sure your MVC Action Methods are also returning async Task<ActionResult> (instead of just ActionResult which, I am guessing, is how you have them structured now) so they can call the HttpClient using an actual async/await pattern. Honestly, because its a bad design, I would not spend any time into trying to get this to work.
I'm not exactly sure whu, but I'll start by refactoring the GetAsync() method
public async Task<T> GetAsync<T>(string uri, string clientId)
{
try
{
string token = await _tokenProvider.IsTokenNullOrExpired();
if (!string.IsNullOrEmpty(token))
{
using (var client = new HttpClient())
{
ConfigurateHttpClient(client, token, clientId);
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsAsync<T>();
}
var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
exception.Data.Add("StatusCode", response.StatusCode);
throw exception;
}
}
else
{
var exception = new Exception();
exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
throw exception;
}
}
catch (Exception ex)
{
throw;
}
}
You should put .ConfigureAwait(false) to your inner awaits statements:
HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false);
(...)
return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
(...)
string token = await _tokenProvider.IsTokenNullOrExpired().ConfigureAwait(false);
(...)
return await method(token).ConfigureAwait(false);;
(...)
string token = await _tokenProvider.GetTokenAsync().ConfigureAwait(false);;
(...)
return await method(token).ConfigureAwait(false);
This way you will avoid to capture the synchronization context before the await is done. Otherwise the continuation will be done in this context, which might result in a lock if this one is in use by other threads.
Doing so will allow the continuation to be done whithin the context of the task which is awaited.

null reference error in task.run async method

I am using the following code for sign in in a web api. I get null reference exception on FormsAuthentication.SetAuthCookie(authUser.UserId.ToString(), false);
call. Please guide me what I am doing wrong...
[AllowAnonymous]
[HttpPost]
public async Task<string> SignIn(JObject credentails)
{
string returnVal = "";
await Task.Run(() =>
{
string userName = (string)credentails.SelectToken("Username");
string password = (string)credentails.SelectToken("Password");
UserService userSvc = new UserService(new SqlConnection(_conStr));
var authUser = userSvc.Authenticate(userName, password);
if (authUser != null)
{
FormsAuthentication.SetAuthCookie(userName, false);
HttpContext.Current.Session.Add("DR_CLIENT_ID", authUser.DRClientId);
HttpContext.Current.Session.Add("USER_ID", authUser.UserId);
returnVal = authUser.FullName;
}
else
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Invalid Credentials!"),
ReasonPhrase = "Error"
});
}
});
return returnVal;
}
UPDATE-1
in this case no value is actually null as I can see it in the debug mode. but when I remove wait Task.Run(() = {}); block from this code, it works fine without any issue.
The problem is Task.Run. In ASP.NET, when an incoming request arrives, it assigns a thread pool thread to handle that request, and this thread runs your code. What your code then does is use Task.Run to move to another thread pool thread without a request context, and then assumes it has a request context. FormsAuthentication.SetAuthCookie (and HttpContext.Current) will simply not work without a request context.
To resolve this, remove the call to Task.Run. You should (almost) never use Task.Run on ASP.NET.

Categories