This question already has an answer here:
Attempting To Understand "Common" async Deadlock with ASP.NET and HttpClient()
(1 answer)
Closed 4 years ago.
I have 2 separate applications, an MVC5 UI and a Asp.net WebApi2 (not asp.net core). I can read data from the API just fine. Now I want to send data from a form in the UI to the API, so it can write it to it's database.
For internal reasons, the MVC controller receives a POST request from a form and then is supposed to update the data in the database by sending it to the API and display some form of result (error or confirmation). The API receives the data, does it's work and responds with a 200 and the desired answer ("updated").
Sadly, the UI does somehow not realize this answer and keeps on waiting forever... I have read there would be a timeout at 90 seconds, but that doesn't seem to be the case. I have to emphasize that I am a newbie, so I guess I made some weird mistake :D
The code I used worked fine from my console application, but either I messed it up or it does not work at all from WebApi? I tried setting the timeout, but it doesn't change anything :/
The last log entry is: sending update request to api...
Controller Code:
[HttpPost]
public ActionResult EditTicket(ServiceManagementTicket postedTicket)
{
if (!ModelState.IsValid)
{
// some error handling code
}
string TicketUpdateResult = updateTicket(postedTicket).Result;
if (TicketUpdateResult == "updated")
{
return View("SingleTicketView", postedTicket);
}
else
{
Log.Verbose("ticket could not be updated. The error should have been logged");
return View("Views/Pages/InternalServerError");
}
}
Task Code:
public static async Task<string> updateTicket(ServiceManagementTicket postedTicket)
{
string FinalEndpoint = "api url here"
String jsonToExport = JsonConvert.SerializeObject(postedTicket);
StringContent jsonToExportConverted =
new StringContent(jsonToExport, Encoding.UTF8, "application/json");
Log.Verbose("Trying to update ticket at: " + FinalEndpoint);
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromSeconds(5);
Log.Verbose("sending update request to api...");
HttpResponseMessage response = await client.PutAsync(
FinalEndpoint,
jsonToExportConverted);
Log.Verbose("Updating the Ticket at the webapi was: " + response.ReasonPhrase);
if (response.ReasonPhrase == "updated")
{
Log.Verbose("api confirmed the update");
return response.Content.ReadAsStringAsync().Result;
}
Log.Verbose("something ent wrong :/");
return "error";
}
}
catch (Exception e)
{
// error handling
}
}
Sending the data to the api in a synchronous call would be fine as well, I just couldn't find an example that worked for me.
The lines of code below will deadlock in ASP.NET applications, never use Task.Result in an ASP.NET application.
string TicketUpdateResult = updateTicket(postedTicket).Result;
return response.Content.ReadAsStringAsync().Result;
Those lines of code should be:
string TicketUpdateResult = await updateTicket(postedTicket);
return await response.Content.ReadAsStringAsync();
Related
Hi I am a Web Api beginner am trying to call one api in another api in asp.net core 3.1. I created two solutions with diffrent port numbers. From httpclient method breakpoint is not going to external api methode.its returning a response as bad request please someone tell what i did wrong here?
I tried your all httpclient method but i am getting the same error bad request..let me explain what i was trying..
I have 2 solutions here with having diffrent port numbers..
in 1st solution i have a method
[HttpPost("api/Product/Check1st")]
public async Task Check([FromBody] CheckInput input)
{
var result = await _productqualityChecker.CheckQualityAsync(input);
return new JsonResult(result);
}
I will keep running this solution . The url here api/Product/Check1st" i will trying to call using httpclient in 2nd solution.so this url i am not giving in postman .
In my 2nd solution I have a methode [HttpPost("api/ProductQuality/Checkin2nd")]{}
this 2nd url i will give to postman and getting the input(arguments to call external api) and then it will call the httpclient as shown as below
In postman I issue a request with the following body against
`http://localhost:2000/api/ProductQuality/Checkin2nd`:
This is the body/input/argument i am passing in the postman
{
"productid": "2274",
"productcorenumber": "1321",
"value": "AE1C1F-0363-D6F-B3D-AE0029A8",
"contacts": [
15998,
1012
],
"ispassed": "0",
"isok": "0",
"isgood": "false"
}
and this will come to here(I have one method in my controller)
[HttpPost("api/ProductQuality/Checkin2nd")]
public async Task Check([FromBody] CheckInput input)
{
var result = await _Checker.Checkercall(input);
}
in Checkercall implementation I am calling another API(as of now calling the same for testing purposes)
public async Task Checkercall(CheckInput input)
{
QualityCheckResults QualityCheckInputResult = new QualityCheckResults();
using (var httpClient = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8,
"application/json");
using (var response = await
httpClient.PostAsync("https://localhost:2001/api/ProductQuality/Check1st", content))
//hope i have to give https here
{
string apiResponse = await response.Content.ReadAsStringAsync();
QualityCheckInputResult = JsonConvert.DeserializeObject(apiResponse);
}
}
return QualityCheckInputResult;
}
This is what i am trying to do. in httpclient mtd CheckInput is the class i have created .
QualityCheckResults QualityCheckInputResult = new QualityCheckResults(); is the response class.So i can assign to this object only..May be that is the issue? i dont know
Is there anything wrong in my 2 solution call?
I am testing a REST API post, and it works well when I try it on Postman. However, in some scenario (related to the posting XML data) if I post with HttpClient API, I would receive the following error:
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
But the same XML content works fine on Postman with status OK and proper response.
What is the differences between using the C# HttpClient API and the postman testing? How can I configure my API call to match with the behavior on postman?
Here I attached the source code, and the Postman screenshot
public void createLoan()
{
string baseCreateLoanUrl = #"https://serverhost/create?key=";
var strUCDExport = XDocument.Load(#"C:\CreateLoan_testcase.xml");
using (var client = new HttpClient())
{
var content = new StringContent(strUCDExport.ToString(), Encoding.UTF8, Mediatype);
string createLoanApi = string.Concat(baseCreateLoanUrl, APIKey);
try
{
var response = client.PostAsync(createLoanApi, content).Result;
}
catch (Exception ex)
{
MessageBox.Show("Error Happened here...");
throw;
}
if (response.IsSuccessStatusCode)
{
// Access variables from the returned JSON object
string responseString = response.Content.ReadAsStringAsync().Result;
JObject jObj = JObject.Parse(responseString);
if (jObj.SelectToken("failure") == null)
{
// First get the authToken
string LoanID = jObj["loanStatus"]["id"].ToString();
MessageBox.Show("Loan ID: " + LoanID);
}
else
{
string getTokenErrorMsg = string.Empty;
JArray errorOjbs = (JArray) jObj["failure"]["errors"];
foreach (var errorObj in errorOjbs)
{
getTokenErrorMsg += errorObj["message"].ToString() + Environment.NewLine;
}
getTokenErrorMsg.Dump();
}
}
}
Thanks for Nard's comment, after comparing the header, I found the issue my client header has this:
Expect: 100-continue
While postman doesn't has.
Once I removed this by using the ServicePointManager:
ServicePointManager.Expect100Continue = false;
Everything seems fine now. Thanks all the input!
My gut tells me it's something simple. First, we know the API works, so I'm thinking it's down to how you are using the HttpClient.
First things first, try as suggested by this SO answer, creating it as a singleton and drop the using statement altogether since the consensus is that HttpClient doesn't need to be disposed:
private static readonly HttpClient HttpClient = new HttpClient();
I would think it would be either there or an issue with your content encoding line that is causing issues with the API. Is there something you are missing that it doesn't like, I bet there is a difference in the requests in Postman vs here. Maybe try sending it as JSON ala:
var json = JsonConvert.SerializeObject(strUCDExport.ToString());
var content = new StringContent(json, Encoding.UTF8, Mediatype);
Maybe the header from Postman vs yours will show something missing, I think the real answer will be there. Have fiddler running in the background, send it via Postman, check it, then run your code and recheck. Pay close attention to all the attribute tags on the header from Postman, the API works so something is missing. Fiddler will tell you.
I was struggling with this for 2 days when I stumbled over Fiddler which lets you record the traffic to the service. After comparing the calls I saw that I had missed a header in my code.
I have an MVC application that calls a WebAPI async, both on POST and GET. When running both the WebAPI and MVC applications locally, the WebAPI response shows successful but the SendAsync request returns 500. Also, Fiddler doesn't show the API call. I have a suspicion that it has to do with how the async request is being handled, but I'm not sure what I'm missing.
MVC Controller call to the API:
public Model UploadFile(Model formCollection)
{
var documentModel = formCollection.ToString();
var client = new HttpClient();
var uri = new Uri(BaseUri + "document/");
var content = new StringContent(documentModel);
var request = new HttpRequestMessage(HttpMethod.Post, uri) {Content = content};
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = client.SendAsync(request);
response.Wait();
try
{
var returned = response.Result;
if (returned.StatusCode != HttpStatusCode.OK)
{
throw new Exception(returned.RequestMessage.ToString());
}
var model = JsonConvert.DeserializeObject<Model> (returned.Content.ReadAsStringAsync().Result);
model.FileContents = "";
return model;
}
catch(Exception e)
{
var error = new Exception("Service failure - ", e);
throw error;
}
}
The WebAPI Post:
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody]Model model)
{
var response = await SubmitNew(model);
return Request.CreateResponse(response);
}
Setting the breakpoint on the return in the Post shows a valid response, but on the response.Result in the MVC Controller it shows 500. I've even tried returning an OK request no matter the response as below:
return Request.CreateResponse(HttpStatusCode.OK, result);
Any ideas as to why the client is showing 500?
In your Global.asax.cs
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Try adding the following lines:
var configuration = GlobalConfiguration.Configuration;
var formatters = configuration.Formatters;
formatters.Clear();
formatters.Add(new JsonMediaTypeFormatter());
}
// A good idea to have this:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
// Set a breakpoint here or do something to log the exception
}
The code added to Application_Start will ensure that serialization is only to/from JSON. While it may not be what you want in your final code, it may be helpful as a temporary measure to isolate the cause of your problem.
Adding Application_Error should help to catch issues which occur within the WebAPI layer, such as when it serializes the object you've returned from your controller method.
My suspicion is that SubmitNew is returning something which cannot be serialized such as an infinitely recursive reference (example: parent/child structure with mutual references).
I found the issue, there was a timeout error in the WebAPI's logging service that was happening after the initial POST and GET calls result.. Issue is resolved.
I had a similar issue with some code that I "improved" - I saw HttpResponseMessage implements the IDisposable interface so decided to wrap the Return Request.CreateResponse method is a using statement, leading to a 500 error.
Removing the using statement fixed the issue for me.
We are using an HttpClient to post json to a restful web service. In one instance, we are running into something that has us baffled. Using tools like postman, fiddler etc, we can post to an endpoint and see that it is working. When we do the same with HttpClient.PostAsJsonAsync, we can verify in the software we are posting to that it received the data just fine. However, our PostAsJsonAsync will always eventually time out rather than give us a response.
We have worked with the team that created the service we are consuming, plus our additional testing on our side, and we have not yet been able to truly time out that service.
Every time we do a post with HttpClient, we then can verify that the target software we post to does indeed get the data. Any time we do a post to that target software from any other tool, we always very quickly see a response with status code of 200. Something about HttpClient is failing to accept the response from this particular service. Does anyone have an idea what we can look at from here?
Here's the code (though it is so cookie cutter that I hardly feel it is needed)
public string PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = client.PostAsJsonAsync(useUrl, o).Result;
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return response.Content.ReadAsStringAsync().Result;
}
return "";
}
}
This:
var response = client.PostAsJsonAsync(useUrl, o).Result;
Is causing you code to deadlock. This is often the case when blocking on async API's, and that's why you're experiencing the "I don't see any response coming back" effect.
How is this causing a deadlock? The fact that you are executing this request in an environment that contains a synchronization context, perhaps one which belongs to the UI. It's executing the async request, and when the response arrives, it continues via an IO completion thread which attempts to post the continuation onto that same UI context, which is currently blocked by your .Result call.
If you want to make an HTTP request synchronously, use WebClient instead. If you want to take advantage of the async API properly, then await instead of block with .Result.
I had the same issue and this SO answer fixed it for me.
In a nutshell, you have to use the ConfigureAwait(false) extension to avoid the deadlock:
var response = await client.PostAsJsonAsync(useUrl, o).ConfigureAwait(false);
Is there a reason why you're not following the async await pattern? You're calling an async method, but not awaiting it. You didn't say if the code calling your REST service was a Windows Forms or ASP.NET application, but that .Result is probably causing you issues.
Can you restructure your method like this:
public async Task<string> PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
This is a slight modification to #Justin Helgerson's solution. There are 2 blocking .Result calls in your method; once you go async you should fix them both.
public async Task<string> PostDataAsync(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
Note I've also renamed the method to PostDataAsync in accordance with the TAP pattern.
System.Net.ServicePointManager.Expect100Continue = false;
That one line of code in our case fixed the problem. A developer from a different team offered that suggestion, and it works. I have yet to google it and read up on it enough to offer an explanation of what that is addressing.
I'm posting some data to an web api controller method from an MVC controller with this method..
private static async Task<HttpResponseMessage> SendDataToApi (List<TogglRow> input)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:****/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsJsonAsync("api/service", input);
//if (response.StatusCode == HttpStatusCode.OK)
//{
// var resultUri = response.Headers.Location;
//}
return response;
}
}
This is the Web Api method i'm posting to..
public HttpResponseMessage Post(HttpRequestMessage request, List<Dagsrapport> value)
{
if (value != null)
{
var rapporter = value.ToList();
//send rapporter to DB
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
}
return request.CreateResponse(HttpStatusCode.BadRequest);
}
Now, the post works fine and i'm returning HttpStatusCode.OK. But i'm not beeing redirected back to the method i'm performing the post from (SendDataToApi()). I'm beeing returned back to the page from wich the post was triggered. I can see the page is working (waiting for localhost..) but nothing happens.
I should probably mention that this is two separate projects (MVC & WebApi), but in the same solution.
What am i missing?
EDIT - Solved
The problem I had was due to the method that ran the task "SendDataToApi" was not set to async. Therefore, it did not wait for an results from the post, but instead ran synchronously and the control never returned to the method that ran SendDataToApi, instead it returned to the original caller - the UI.
Here is the method that is runnig the SendDataToApi task..
[HttpPost]
public async Task<ActionResult> Edit(IEnumerable<TogglRow> tr)
{
var listToExport = tr.Where(x => x.Export.Equals(true));
var result = listToExport.ToList();
var response = await SendDataToApi(result);
return RedirectToAction("Index", "Home",
response.StatusCode == HttpStatusCode.OK ? new { message = "Record(s) were successfully stored." } : new { message = "No records selected." });
}
It seems you have some fundamental misunderstandings about how all this works. MVC actions and WebAPI actions work very differently, which is why they're actually in entirely different namespaces, even though they both implement similar components.
If you need to connect to a Web API from an MVC action, you shouldn't be receiving the response as an HttpResponseMessage. That's a return value for a WebAPI action, similar to how a ViewResult is a return value for an MVC action. It has no meaning to anything in MVC. Rather, your actual response from HttpClient, for example, will be a string (technically a byte array) with a content type indicating that it should be interpreted as plain text, JSON, XML, etc.
Based on the content type, you'll process this response accordingly. If it's JSON, for example, then you can use something like JObject from Newtonsoft.Json (default JSON interpreter in MVC). Then, you could use this data object to construct your response for your MVC action. If you have something indicating that a redirect should be made, then the MVC action can return on of the Redirect* family of results. Importantly, you can't just make the redirect the response of the Web API action, because that merely affects the HttpClient response object.